New Upstream Release - ruby-unparser

Ready changes

Summary

Merged new upstream version: 0.6.8 (was: 0.4.7).

Diff

diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 226ac39..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-defaults: &defaults
-  working_directory: ~/unparser
-  docker:
-    - image: circleci/ruby:2.6.5
-version: 2
-jobs:
-  unit_specs:
-    <<: *defaults
-    steps:
-      - checkout
-      - run: bundle install
-      - run: bundle exec rspec spec/unit
-  integration_specs:
-    <<: *defaults
-    steps:
-      - checkout
-      - run: bundle install
-      - run: bundle exec rspec spec/integration
-  metrics:
-    <<: *defaults
-    steps:
-      - checkout
-      - run: bundle install
-      - run: bundle exec rake metrics:rubocop
-      - run: bundle exec rake metrics:reek
-      - run: bundle exec rake metrics:flay
-      - run: bundle exec rake metrics:flog
-  mutant:
-    <<: *defaults
-    steps:
-      - checkout
-      - run: bundle install
-      - run: |
-          bundle                                                  \
-            exec                                                  \
-            mutant                                                \
-            --ignore-subject 'Unparser::AST::LocalVariableScope*' \
-            --since origin/master                                 \
-            --zombie                                              \
-            --                                                    \
-            'Unparser*'
-workflows:
-  version: 2
-  test:
-    jobs:
-      - unit_specs
-      - integration_specs
-      - metrics
-      - mutant
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 3a9c57e..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,37 +0,0 @@
-## MAC OS
-.DS_Store
-
-## TEXTMATE
-*.tmproj
-tmtags
-
-## EMACS
-*~
-\#*
-.\#*
-
-## VIM
-*.sw[op]
-
-## Rubinius
-*.rbc
-.rbx
-
-## PROJECT::GENERAL
-*.gem
-coverage
-profiling
-turbulence
-rdoc
-pkg
-tmp
-doc
-log
-.yardoc
-measurements
-
-## BUNDLER
-.bundle
-
-## PROJECT::SPECIFIC
-/vendor
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 2ac7934..0000000
--- a/.rspec
+++ /dev/null
@@ -1,4 +0,0 @@
---color
---format progress
---warnings
---order random
diff --git a/.rubocop.yml b/.rubocop.yml
deleted file mode 100644
index edae650..0000000
--- a/.rubocop.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-AllCops:
-  Include:
-    - 'Gemfile'
-  Exclude:
-    - 'Gemfile.devtools'
-    - 'vendor/**/*'
-    - 'benchmarks/**/*'
-    - 'tmp/**/*'
-  TargetRubyVersion: 2.5
diff --git a/Changelog.md b/Changelog.md
deleted file mode 100644
index eca2e64..0000000
--- a/Changelog.md
+++ /dev/null
@@ -1,156 +0,0 @@
-# v0.4.7 2020-01-03
-
-* Add support for endless ranges
-* Change to allow parser 2.7, even while syntax is not yet supported.
-  This reduces downstream complexity.
-
-# v0.4.6 2020-01-02
-
-* Upgrades to allow parser dependency to ~> 2.6.5
-
-# v0.4.5 2019-05-10
-
-* Bump parser dependency to ~> 2.6.3
-
-# v0.4.4 2019-03-27
-
-* Bump parser dependency to ~> 2.6.2
-
-# v0.4.3 2019-02-24
-
-* Bump parser dependency to ~> 2.6.0
-
-# v0.4.2 2018-12-04
-
-* Drop hard ruby version requirement. Still officially I'll only support 2.5.
-
-# v0.4.1 2018-12-03
-
-* Fix unparsing of `def foo(bar: bar())`
-
-# v0.4.0 2018-12-03
-
-* Change to modern AST format.
-* Add experimental `Unparser.{parser,parse,parse_with_comments}`
-
-# v0.3.0 2018-11-16
-
-* Drop support for Ruby < 2.5
-
-# v0.2.7 2018-07-18
-
-* Add emitters for `__FILE__` and `__LINE__`
-  https://github.com/mbj/unparser/pull/70
-
-# v0.2.7 2018-02-09
-
-* Allow ruby_parser 2.5
-
-# v0.2.6 2017-05-30
-
-* Reduce memory consumption via not requirering all possible parsers
-* Allow ruby 2.4
-* Update parser dependency
-
-# v0.2.5 2016-01-24
-
-* Add support for ruby 2.3
-* Bump parser dependency to ~>2.3.0
-* Trade uglier for more correct dstring / dsyms
-* Drop support for ruby < 2.1
-
-# v0.2.4 2015-05-30
-
-* Relax parser dependency to ~>2.2.2
-
-# v0.2.3 2015-04-28
-
-* Compatibility with parser ~>2.2.2, >2.2.2.2
-
-# v0.2.2 2015-01-14
-
-* Really add back unofficial support for 1.9.3
-
-# v0.2.1 2015-01-14
-
-* Add back unofficial support for 1.9.3
-
-# v0.2.0 2015-01-12
-
-* Bump required ruby version to 2.0.0
-
-# v0.1.17 2015-01-10
-
-* Fix jruby complex / rational generation edge case
-* Support generation under MRI 2.2
-
-# v0.1.16 2014-11-07
-
-* Add emitter for complex and rational literals
-* Fix edge cases for MLHS
-* Fix differencies from 2.2.pre7 series of parser
-
-# v0.1.15 2014-09-24
-
-* Handle syntax edge case for MRI 2.1.3 parser.
-
-# v0.1.14 2014-06-15
-
-* Fix emitter to correctly unparse foo[] = 1
-
-# v0.1.13 2014-06-08
-
-* Add support for rubinius.
-
-# v0.1.12 2014-04-13
-
-* Add support for 2.1 kwsplat
-
-# v0.1.11 2014-04-11
-
-* Fix performance on local variable scope inspection
-
-# v0.1.10 2014-04-06
-
-* Fix emit of inline rescues in combination with control flow keywords.
-* Begin corpus testing on rake ci against rubyspec
-
-# v0.1.9 2014-01-14
-
-* Fix emit of proc { |(a)| }
-
-# v0.1.8 2014-01-11
-
-* Fix all bugs found while round tripping rubyspec.
-
-# v0.1.7 2014-01-03
-
-* Add back support for root nodes of type resbody https://github.com/mbj/unparser/issues/24
-
-# v0.1.6 2013-12-31
-
-* Emit 1.9 style hashes where possible: https://github.com/mbj/unparser/pull/23
-* Fix invalid quoting of hash keys: https://github.com/mbj/unparser/issues/22
-* Fix crash on take before introduced by code refactorings: https://github.com/mbj/unparser/issues/20
-* Fix crash on comment reproduction https://github.com/mbj/unparser/issues/17
-
-# v0.1.5 2013-11-01
-
-* Fix crash with comment reproduction.
-
-# v0.1.4 2013-11-01
-
-* Code cleanups.
-* Remove warnings.
-
-# v0.0.3 2013-06-17
-
-* Adjust to changes in parser 2.0.0.beta5 => beta6
-
-# v0.0.2 2013-06-17
-
-Crappy release
-
-# v0.0.1 2013-06-15
-
-Initial release
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index 1419592..0000000
--- a/Gemfile
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-source 'https://rubygems.org'
-
-gemspec
-
-source 'https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev' do
-  gem 'mutant-license'
-end
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 18e6ee0..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,181 +0,0 @@
-PATH
-  remote: .
-  specs:
-    unparser (0.4.7)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2.0)
-      concord (~> 0.1.5)
-      diff-lcs (~> 1.3)
-      equalizer (~> 0.0.9)
-      parser (>= 2.6.5)
-      procto (~> 0.0.2)
-
-GEM
-  remote: https://rubygems.org/
-  remote: https://oss:Px2ENN7S91OmWaD5G7MIQJi1dmtmYrEh@gem.mutant.dev/
-  specs:
-    abstract_type (0.0.7)
-    adamantium (0.2.0)
-      ice_nine (~> 0.11.0)
-      memoizable (~> 0.4.0)
-    anima (0.3.1)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2)
-      equalizer (~> 0.0.11)
-    ast (2.4.0)
-    axiom-types (0.1.1)
-      descendants_tracker (~> 0.0.4)
-      ice_nine (~> 0.11.0)
-      thread_safe (~> 0.3, >= 0.3.1)
-    codeclimate-engine-rb (0.4.1)
-      virtus (~> 1.0)
-    coercible (1.0.0)
-      descendants_tracker (~> 0.0.1)
-    concord (0.1.5)
-      adamantium (~> 0.2.0)
-      equalizer (~> 0.0.9)
-    descendants_tracker (0.0.4)
-      thread_safe (~> 0.3, >= 0.3.1)
-    devtools (0.1.24)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2.0)
-      anima (~> 0.3.0)
-      concord (~> 0.1.5)
-      flay (~> 2.12.0)
-      flog (~> 4.6.2)
-      procto (~> 0.0.3)
-      rake (~> 12.3.0)
-      reek (~> 5.3.0)
-      rspec (~> 3.8.0)
-      rspec-core (~> 3.8.0)
-      rspec-its (~> 1.2.0)
-      rubocop (~> 0.61.1)
-      simplecov (~> 0.16.1)
-      yard (~> 0.9.16)
-      yardstick (~> 0.9.9)
-    diff-lcs (1.3)
-    docile (1.3.2)
-    equalizer (0.0.11)
-    erubis (2.7.0)
-    flay (2.12.1)
-      erubis (~> 2.7.0)
-      path_expander (~> 1.0)
-      ruby_parser (~> 3.0)
-      sexp_processor (~> 4.0)
-    flog (4.6.4)
-      path_expander (~> 1.0)
-      ruby_parser (~> 3.1, > 3.1.0)
-      sexp_processor (~> 4.8)
-    ice_nine (0.11.2)
-    jaro_winkler (1.5.4)
-    json (2.3.0)
-    kwalify (0.7.2)
-    memoizable (0.4.2)
-      thread_safe (~> 0.3, >= 0.3.1)
-    morpher (0.2.6)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2.0)
-      anima (~> 0.3.0)
-      ast (~> 2.2)
-      concord (~> 0.1.5)
-      equalizer (~> 0.0.9)
-      ice_nine (~> 0.11.0)
-      procto (~> 0.0.2)
-    mprelude (0.1.0)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2.0)
-      concord (~> 0.1.5)
-      equalizer (~> 0.0.9)
-      ice_nine (~> 0.11.1)
-      procto (~> 0.0.2)
-    mutant (0.9.4)
-      abstract_type (~> 0.0.7)
-      adamantium (~> 0.2.0)
-      anima (~> 0.3.1)
-      ast (~> 2.2)
-      concord (~> 0.1.5)
-      diff-lcs (~> 1.3)
-      equalizer (~> 0.0.9)
-      ice_nine (~> 0.11.1)
-      memoizable (~> 0.4.2)
-      mprelude (~> 0.1.0)
-      parser (~> 2.6.5)
-      procto (~> 0.0.2)
-      unparser (~> 0.4.6)
-    mutant-license (0.1.0)
-    mutant-rspec (0.9.4)
-      mutant (~> 0.9.4)
-      rspec-core (>= 3.8.0, < 4.0.0)
-    parallel (1.19.1)
-    parser (2.6.5.0)
-      ast (~> 2.4.0)
-    path_expander (1.1.0)
-    powerpack (0.1.2)
-    procto (0.0.3)
-    psych (3.1.0)
-    rainbow (3.0.0)
-    rake (12.3.3)
-    reek (5.3.2)
-      codeclimate-engine-rb (~> 0.4.0)
-      kwalify (~> 0.7.0)
-      parser (>= 2.5.0.0, < 2.7, != 2.5.1.1)
-      psych (~> 3.1.0)
-      rainbow (>= 2.0, < 4.0)
-    rspec (3.8.0)
-      rspec-core (~> 3.8.0)
-      rspec-expectations (~> 3.8.0)
-      rspec-mocks (~> 3.8.0)
-    rspec-core (3.8.2)
-      rspec-support (~> 3.8.0)
-    rspec-expectations (3.8.6)
-      diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.8.0)
-    rspec-its (1.2.0)
-      rspec-core (>= 3.0.0)
-      rspec-expectations (>= 3.0.0)
-    rspec-mocks (3.8.2)
-      diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.8.0)
-    rspec-support (3.8.3)
-    rubocop (0.61.1)
-      jaro_winkler (~> 1.5.1)
-      parallel (~> 1.10)
-      parser (>= 2.5, != 2.5.1.1)
-      powerpack (~> 0.1)
-      rainbow (>= 2.2.2, < 4.0)
-      ruby-progressbar (~> 1.7)
-      unicode-display_width (~> 1.4.0)
-    ruby-progressbar (1.10.1)
-    ruby_parser (3.14.1)
-      sexp_processor (~> 4.9)
-    sexp_processor (4.13.0)
-    simplecov (0.16.1)
-      docile (~> 1.1)
-      json (>= 1.8, < 3)
-      simplecov-html (~> 0.10.0)
-    simplecov-html (0.10.2)
-    thread_safe (0.3.6)
-    unicode-display_width (1.4.1)
-    virtus (1.0.5)
-      axiom-types (~> 0.1)
-      coercible (~> 1.0)
-      descendants_tracker (~> 0.0, >= 0.0.3)
-      equalizer (~> 0.0, >= 0.0.9)
-    yard (0.9.22)
-    yardstick (0.9.9)
-      yard (~> 0.8, >= 0.8.7.2)
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  anima (~> 0.3.1)
-  devtools (~> 0.1.23)
-  morpher (~> 0.2.6)
-  mutant (~> 0.9.4)
-  mutant-license!
-  mutant-rspec (~> 0.9.4)
-  unparser!
-
-BUNDLED WITH
-   1.17.3
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 44863d7..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2013 Markus Schirp
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 4d171ba..368ca18 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,24 @@
 unparser
 ========
 
-[![Build Status](https://secure.travis-ci.org/mbj/unparser.svg?branch=master)](http://travis-ci.org/mbj/unparser)
-[![Code Climate](https://codeclimate.com/github/mbj/unparser.svg)](https://codeclimate.com/github/mbj/unparser)
+![CI](https://github.com/mbj/unparser/workflows/CI/badge.svg)
 [![Gem Version](https://img.shields.io/gem/v/unparser.svg)](https://rubygems.org/gems/unparser)
 
-Generate equivalent source for ASTs from whitequarks [parser](https://github.com/whitequark/parser).
+Generate equivalent source for ASTs from [parser](https://github.com/whitequark/parser).
 
 The following constraints apply:
 
 * No support for macruby extensions
 * Only support for the [modern AST](https://github.com/whitequark/parser/#usage) format
-* Only support for Ruby >= 2.5
+* Only support for Ruby >= 2.7
 
-It serves well for [mutant](https://github.com/mbj/mutant) mutators and the in-memory vendoring for self hosting,
-and other tooling.
+Notable Users:
+
+* [mutant](https://github.com/mbj/mutant) - Code review engine via mutation testing.
+* [ruby-next](https://github.com/ruby-next/ruby-next) - Ruby Syntax Backports.
+* Many other [reverse-dependencies](https://rubygems.org/gems/unparser/reverse_dependencies).
+
+(if you want your tool to be mentioned here please PR the addition with a TLDR of your use case).
 
 Public API:
 -----------
@@ -87,16 +91,26 @@ RUBY
 
 generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w[foo bar] !
 
-code == generated                            # false, not identical code
+code == generated                 # false, not identical code
 Unparser.parse(generated) == node # true, but identical AST
 ```
 
 Summary: unparser does not reproduce your source! It produces equivalent source.
 
+Ruby Versions:
+--------------
+
+Unparsers primay reason for existance is mutant and its
+supported [Ruby-Versions](https://github.com/mbj/mutant#ruby-versions).
+
+Basically: All non EOL MRI releases.
+
+If you need to generate Ruby Syntax outside of this band feel free to contact me (email in gemspec).
+
 Testing:
 --------
 
-Unparser currently successfully round trips almost all ruby code around. Using MRI-2.5.x.
+Unparser currently successfully round trips almost all ruby code around. Using Ruby >= 2.6.
 If there is a non round trippable example that is NOT subjected to known [Limitations](#limitations).
 please report a bug.
 
@@ -154,6 +168,18 @@ People
 
 Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors).
 
+Included Libraries
+------------------
+
+For dependency reduction reasons unparser ships vendored (and reduced) versions of:
+
+* [abstract_type](https://github.com/mbj/concord) -> Unparser::AbstractType
+* [adamantium](https://github.com/dkubb/adamantium) -> Unparser::Adamantium
+* [anima](https://github.com/mbj/concord) -> Unparser::Anima
+* [concord](https://github.com/mbj/concord) -> Unparser::Concord
+* [memoizable](https://github.com/dkubb/memoizable) -> Unparser::Adamantium
+* [mprelude](https://github.com/dkubb/memoizable) -> Unparser::Either
+
 Contributing
 -------------
 
@@ -161,10 +187,15 @@ Contributing
 * Make your feature addition or bug fix.
 * Add tests for it. This is important so I don't break it in a
   future version unintentionally.
-* Commit, do not mess with Rakefile or version
+* Commit, do not mess with version
   (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
 * Send me a pull request. Bonus points for topic branches.
 
+Known Users
+-------------
+
+* [RailsRocket](https://www.railsrocket.app) - A no-code app builder that creates Rails apps
+
 License
 -------
 
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index 4e68916..0000000
--- a/Rakefile
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'devtools'
-Devtools.init_rake_tasks
-
-Rake.application.load_imports
-task('metrics:mutant').clear
-
-namespace :metrics do
-  task mutant: :coverage do
-    args = %w[
-      bundle exec mutant
-      --ignore-subject Unparser::Buffer#initialize
-      --include lib
-      --require unparser
-      --use rspec
-      --zombie
-      --since HEAD~1
-    ]
-    args.concat(%w[--jobs 4]) if ENV.key?('CIRCLECI')
-
-    system(*args.concat(%w[-- Unparser*])) or fail "Mutant task failed"
-  end
-end
diff --git a/bin/unparser b/bin/unparser
index 1258efa..26d049e 100755
--- a/bin/unparser
+++ b/bin/unparser
@@ -2,9 +2,9 @@
 # frozen_string_literal: true
 
 trap('INT') do |status|
-  exit! 128 + status
+  exit! status + 128
 end
 
-require 'unparser/cli'
+require 'unparser'
 
 exit Unparser::CLI.run(ARGV)
diff --git a/config/devtools.yml b/config/devtools.yml
deleted file mode 100644
index f7e1f61..0000000
--- a/config/devtools.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-unit_test_timeout: 1.0
diff --git a/config/flay.yml b/config/flay.yml
deleted file mode 100644
index 7b5e3d1..0000000
--- a/config/flay.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-threshold: 13
-total_score: 638
diff --git a/config/flog.yml b/config/flog.yml
deleted file mode 100644
index 1174a8f..0000000
--- a/config/flog.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-threshold: 21.3
diff --git a/config/mutant.yml b/config/mutant.yml
deleted file mode 100644
index 8c4cfe4..0000000
--- a/config/mutant.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-includes:
-- lib
-integration: rspec
-requires:
-- unparser
diff --git a/config/reek.yml b/config/reek.yml
deleted file mode 100644
index 3826d7f..0000000
--- a/config/reek.yml
+++ /dev/null
@@ -1,98 +0,0 @@
----
-detectors:
-  Attribute:
-    enabled: false
-    exclude: []
-  BooleanParameter:
-    enabled: true
-    exclude: []
-  ClassVariable:
-    enabled: true
-    exclude: []
-  ControlParameter:
-    enabled: true
-    exclude: []
-  DataClump:
-    enabled: true
-    exclude: []
-    max_copies: 2
-    min_clump_size: 2
-  DuplicateMethodCall:
-    enabled: false
-    exclude: []
-    max_calls: 1
-    allow_calls: []
-  FeatureEnvy:
-    enabled: false
-  # Buggy smell detector
-  IrresponsibleModule:
-    enabled: false
-    exclude: []
-  LongParameterList:
-    enabled: true
-    exclude: []
-    max_params: 2
-  LongYieldList:
-    enabled: true
-    exclude: []
-    max_params: 2
-  NestedIterators:
-    enabled: true
-    exclude: []
-    max_allowed_nesting: 1
-    ignore_iterators: []
-  NilCheck:
-    enabled: false
-  RepeatedConditional:
-    enabled: true
-    exclude: []
-    max_ifs: 1
-  TooManyInstanceVariables:
-    enabled: true
-    exclude: []
-    max_instance_variables: 3
-  TooManyMethods:
-    enabled: true
-    exclude: []
-    max_methods: 10
-  TooManyStatements:
-    enabled: true
-    exclude: []
-    max_statements: 7
-  UncommunicativeMethodName:
-    enabled: true
-    exclude: []
-    reject:
-    - '/^[a-z]$/'
-    - '/[0-9]$/'
-    - '/[A-Z]/'
-    accept: []
-  UncommunicativeModuleName:
-    enabled: true
-    exclude: []
-    reject:
-    - '/^.$/'
-    - '/[0-9]$/'
-    accept: []
-  UncommunicativeParameterName:
-    enabled: true
-    exclude: []
-    reject:
-    - '/^.$/'
-    - '/[0-9]$/'
-    - '/[A-Z]/'
-    accept: []
-  UncommunicativeVariableName:
-    enabled: true
-    exclude: []
-    reject:
-    - '/^.$/'
-    - '/[0-9]$/'
-    - '/[A-Z]/'
-    accept: ['force_utf32']
-  UnusedParameters:
-    enabled: true
-    exclude: []
-  UtilityFunction:
-    enabled: true
-    exclude: []
diff --git a/config/rubocop.yml b/config/rubocop.yml
deleted file mode 100644
index eb0647a..0000000
--- a/config/rubocop.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-inherit_from: ../.rubocop.yml
-
-AllCops:
-  Include:
-    - 'lib/unparser.rb'
-    - 'lib/unparser/**/*.rb'
-    - '**/*.rake'
-    - 'Gemfile'
-    - 'Gemfile.triage'
-
-# Avoid parameter lists longer than five parameters.
-ParameterLists:
-  Max: 3
-  CountKeywordArgs: true
-
-MethodLength:
-  CountComments: false
-  Max: 17
-
-AbcSize:
-  Max: 18
-
-# Avoid more than `Max` levels of nesting.
-BlockNesting:
-  Max: 3
-
-# Align with the style guide.
-CollectionMethods:
-  PreferredMethods:
-    collect:  'map'
-    inject:   'reduce'
-    find:     'detect'
-    find_all: 'select'
-
-# Limit line length
-LineLength:
-  Max: 113  # TODO: lower to 79 once the rubocop branch in shared/Gemfile is removed
-
-ClassLength:
-  Max: 204
-
-# Prefer modifiers and explicit if statements over returning early for small methods
-GuardClause:
-  Enabled: false
-
-Metrics/BlockLength:
-  Exclude:
-  # Ignore RSpec DSL
-  - spec/**/*
-
-# Flags freezes for singletons that could still be mutated like Regexps
-RedundantFreeze:
-  Enabled: false
-
-# Allow Fixnum and Bignum. This Gem supports versions before 2.4
-UnifiedInteger:
-  Enabled: false
-
-# Disabled because of indenting with private keyword in class bodies.
-IndentationWidth:
-  Enabled: false
-
-# I like raise more
-SignalException:
-  Enabled: false
-
-# False positive in unparser source
-OneLineConditional:
-  Enabled: false
-
-Documentation:
-  Enabled: false
-
-# Disable documentation checking until a class needs to be documented once
-Documentation:
-  Enabled: false
-
-# Do not favor modifier if/unless usage when you have a single-line body
-IfUnlessModifier:
-  Enabled: false
-
-# Allow case equality operator (in limited use within the specs)
-CaseEquality:
-  Enabled: false
-
-# Constants do not always have to use SCREAMING_SNAKE_CASE
-ConstantName:
-  Enabled: false
-
-# Not all trivial readers/writers can be defined with attr_* methods
-TrivialAccessors:
-  Enabled: false
-
-# I like to have an empty line before closing the currently opened body
-EmptyLinesAroundBlockBody:
-  Enabled: false
-
-EmptyLinesAroundClassBody:
-  Enabled: false
-
-EmptyLinesAroundModuleBody:
-  Enabled: false
-
-# I like my style more
-AccessModifierIndentation:
-  Enabled: false
-
-Style/CommentedKeyword:
-  Enabled: false
-
-Style/MixinGrouping:
-  Enabled: false
-
-Lint/BooleanSymbol:
-  Enabled: false
-
-Style/AccessModifierDeclarations:
-  Enabled: false
-
-Layout/AlignHash:
-  EnforcedColonStyle: table
-  EnforcedHashRocketStyle: table
diff --git a/config/yardstick.yml b/config/yardstick.yml
deleted file mode 100644
index a6b63e8..0000000
--- a/config/yardstick.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-threshold: 100
diff --git a/debian/changelog b/debian/changelog
index 377444c..29b5824 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-unparser (0.6.8-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 28 Jun 2023 01:26:01 -0000
+
 ruby-unparser (0.4.7-2) unstable; urgency=medium
 
   * Source-only upload.
diff --git a/lib/unparser.rb b/lib/unparser.rb
index 18fd26a..0938a81 100644
--- a/lib/unparser.rb
+++ b/lib/unparser.rb
@@ -1,22 +1,50 @@
 # frozen_string_literal: true
 
-require 'set'
-require 'abstract_type'
-require 'procto'
-require 'concord'
+require 'diff/lcs'
+require 'diff/lcs/hunk'
+require 'optparse'
 require 'parser/current'
+require 'set'
+
+require 'unparser/equalizer'
+require 'unparser/adamantium'
+require 'unparser/adamantium/method_builder'
+require 'unparser/abstract_type'
+
+require 'unparser/concord'
+require 'unparser/either'
+require 'unparser/anima'
+require 'unparser/anima/attribute'
+require 'unparser/anima/error'
 
 # Library namespace
 module Unparser
   # Unparser specific AST builder defaulting to modern AST format
   class Builder < Parser::Builders::Default
     modernize
+
+    def initialize
+      super
+
+      self.emit_file_line_as_literals = false
+    end
   end
 
   EMPTY_STRING = ''.freeze
   EMPTY_ARRAY  = [].freeze
 
-  private_constant(*constants(false))
+  private_constant(*constants(false) - %i[Adamantium AbstractType Anima Concord Either Equalizer Memoizable])
+
+  # Error raised when unparser encounters an invalid AST
+  class InvalidNodeError < RuntimeError
+    attr_reader :node
+
+    def initialize(message, node)
+      super(message)
+      @node = node
+      freeze
+    end
+  end
 
   # Unparse an AST (and, optionally, comments) into a string
   #
@@ -25,26 +53,70 @@ module Unparser
   #
   # @return [String]
   #
-  # @api private
+  # @raise InvalidNodeError
+  #   if the node passed is invalid
   #
+  # @api public
   def self.unparse(node, comment_array = [])
-    node = Preprocessor.run(node)
-    buffer = Buffer.new
-    comments = Comments.new(comment_array)
-    root = Emitter::Root.new(Parser::AST::Node.new(:root, [node]), buffer, comments)
-    Emitter.emitter(node, root).write_to_buffer
-    buffer.content
+    return '' if node.nil?
+
+    Buffer.new.tap do |buffer|
+      Emitter::Root.new(
+        buffer,
+        node,
+        Comments.new(comment_array)
+      ).write_to_buffer
+    end.content
+  end
+
+  # Unparse with validation
+  #
+  # @param [Parser::AST::Node, nil] node
+  # @param [Array] comment_array
+  #
+  # @return [Either<Validation,String>]
+  def self.unparse_validate(node, comment_array = [])
+    generated = unparse(node, comment_array)
+    validation = Validation.from_string(generated)
+
+    if validation.success?
+      Either::Right.new(generated)
+    else
+      Either::Left.new(validation)
+    end
+  end
+
+  # Unparse capturing errors
+  #
+  # This is mostly useful for writing testing tools against unparser.
+  #
+  # @param [Parser::AST::Node, nil] node
+  #
+  # @return [Either<Exception, String>]
+  def self.unparse_either(node)
+    Either.wrap_error(Exception) { unparse(node) }
   end
 
   # Parse string into AST
   #
   # @param [String] source
   #
-  # @return [Parser::AST::Node]
+  # @return [Parser::AST::Node, nil]
   def self.parse(source)
     parser.parse(buffer(source))
   end
 
+  # Parse string into either syntax error or AST
+  #
+  # @param [String] source
+  #
+  # @return [Either<Parser::SyntaxError, (Parser::ASTNode, nil)>]
+  def self.parse_either(source)
+    Either.wrap_error(Parser::SyntaxError) do
+      parser.parse(buffer(source))
+    end
+  end
+
   # Parse string into AST, with comments
   #
   # @param [String] source
@@ -59,98 +131,110 @@ module Unparser
   # @return [Parser::Base]
   #
   # @api private
-  #
-  # ignore :reek:NestedIterators
   def self.parser
     Parser::CurrentRuby.new(Builder.new).tap do |parser|
       parser.diagnostics.tap do |diagnostics|
         diagnostics.all_errors_are_fatal = true
-        diagnostics.consumer             = method(:consume_diagnostic)
       end
     end
   end
 
-  # Consume diagnostic
-  #
-  # @param [Parser::Diagnostic] diagnostic
-  #
-  # @return [undefined]
-  def self.consume_diagnostic(diagnostic)
-    Kernel.warn(diagnostic.render)
-  end
-  private_class_method :consume_diagnostic
-
   # Construct a parser buffer from string
   #
   # @param [String] source
   #
   # @return [Parser::Source::Buffer]
-  def self.buffer(source)
-    Parser::Source::Buffer.new('(string)').tap do |buffer|
-      buffer.source = source
-    end
+  def self.buffer(source, identification = '(string)')
+    Parser::Source::Buffer.new(identification, source: source)
   end
 end # Unparser
 
-require 'unparser/buffer'
 require 'unparser/node_helpers'
-require 'unparser/preprocessor'
+require 'unparser/ast'
+require 'unparser/ast/local_variable_scope'
+require 'unparser/buffer'
+require 'unparser/generation'
+require 'unparser/color'
 require 'unparser/comments'
 require 'unparser/constants'
+require 'unparser/diff'
 require 'unparser/dsl'
-require 'unparser/ast'
-require 'unparser/ast/local_variable_scope'
 require 'unparser/emitter'
-require 'unparser/emitter/literal'
-require 'unparser/emitter/literal/primitive'
-require 'unparser/emitter/literal/singleton'
-require 'unparser/emitter/literal/dynamic'
-require 'unparser/emitter/literal/regexp'
-require 'unparser/emitter/literal/array'
-require 'unparser/emitter/literal/hash'
-require 'unparser/emitter/literal/range'
-require 'unparser/emitter/literal/dynamic_body'
-require 'unparser/emitter/literal/execute_string'
-require 'unparser/emitter/meta'
-require 'unparser/emitter/send'
-require 'unparser/emitter/send/unary'
-require 'unparser/emitter/send/binary'
-require 'unparser/emitter/send/regular'
-require 'unparser/emitter/send/conditional'
-require 'unparser/emitter/send/attribute_assignment'
-require 'unparser/emitter/block'
-require 'unparser/emitter/assignment'
-require 'unparser/emitter/variable'
-require 'unparser/emitter/splat'
-require 'unparser/emitter/cbase'
+require 'unparser/emitter/alias'
+require 'unparser/emitter/args'
 require 'unparser/emitter/argument'
+require 'unparser/emitter/array'
+require 'unparser/emitter/array_pattern'
+require 'unparser/emitter/assignment'
 require 'unparser/emitter/begin'
-require 'unparser/emitter/flow_modifier'
-require 'unparser/emitter/undef'
-require 'unparser/emitter/def'
+require 'unparser/emitter/binary'
+require 'unparser/emitter/block'
+require 'unparser/emitter/case'
+require 'unparser/emitter/case_guard'
+require 'unparser/emitter/case_match'
+require 'unparser/emitter/cbase'
 require 'unparser/emitter/class'
-require 'unparser/emitter/module'
-require 'unparser/emitter/op_assign'
+require 'unparser/emitter/const_pattern'
+require 'unparser/emitter/def'
 require 'unparser/emitter/defined'
+require 'unparser/emitter/dstr'
+require 'unparser/emitter/dsym'
+require 'unparser/emitter/flipflop'
+require 'unparser/emitter/float'
+require 'unparser/emitter/flow_modifier'
+require 'unparser/emitter/for'
+require 'unparser/emitter/hash'
+require 'unparser/emitter/hash_pattern'
 require 'unparser/emitter/hookexe'
-require 'unparser/emitter/super'
-require 'unparser/emitter/retry'
-require 'unparser/emitter/redo'
 require 'unparser/emitter/if'
-require 'unparser/emitter/alias'
-require 'unparser/emitter/yield'
-require 'unparser/emitter/binary'
-require 'unparser/emitter/case'
-require 'unparser/emitter/for'
-require 'unparser/emitter/repetition'
-require 'unparser/emitter/root'
-require 'unparser/emitter/match'
-require 'unparser/emitter/empty'
-require 'unparser/emitter/flipflop'
-require 'unparser/emitter/rescue'
-require 'unparser/emitter/resbody'
-require 'unparser/emitter/ensure'
+require 'unparser/emitter/in_match'
+require 'unparser/emitter/in_pattern'
 require 'unparser/emitter/index'
+require 'unparser/emitter/kwbegin'
 require 'unparser/emitter/lambda'
+require 'unparser/emitter/masgn'
+require 'unparser/emitter/match'
+require 'unparser/emitter/match_alt'
+require 'unparser/emitter/match_as'
+require 'unparser/emitter/match_rest'
+require 'unparser/emitter/match_var'
+require 'unparser/emitter/mlhs'
+require 'unparser/emitter/module'
+require 'unparser/emitter/op_assign'
+require 'unparser/emitter/pin'
+require 'unparser/emitter/primitive'
+require 'unparser/emitter/range'
+require 'unparser/emitter/regexp'
+require 'unparser/emitter/repetition'
+require 'unparser/emitter/rescue'
+require 'unparser/emitter/root'
+require 'unparser/emitter/send'
+require 'unparser/emitter/simple'
+require 'unparser/emitter/splat'
+require 'unparser/emitter/super'
+require 'unparser/emitter/undef'
+require 'unparser/emitter/variable'
+require 'unparser/emitter/xstr'
+require 'unparser/emitter/yield'
+require 'unparser/emitter/kwargs'
+require 'unparser/emitter/pair'
+require 'unparser/emitter/find_pattern'
+require 'unparser/emitter/match_pattern'
+require 'unparser/emitter/match_pattern_p'
+require 'unparser/writer'
+require 'unparser/writer/binary'
+require 'unparser/writer/dynamic_string'
+require 'unparser/writer/resbody'
+require 'unparser/writer/rescue'
+require 'unparser/writer/send'
+require 'unparser/writer/send/attribute_assignment'
+require 'unparser/writer/send/binary'
+require 'unparser/writer/send/regular'
+require 'unparser/writer/send/unary'
+require 'unparser/node_details'
+require 'unparser/node_details/send'
+require 'unparser/cli'
+
+require 'unparser/validation'
 # make it easy for zombie
 require 'unparser/finalize'
diff --git a/lib/unparser/abstract_type.rb b/lib/unparser/abstract_type.rb
new file mode 100644
index 0000000..2a91ed4
--- /dev/null
+++ b/lib/unparser/abstract_type.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Module to allow class and methods to be abstract
+  #
+  # Original code before vendoring and reduction from: https://github.com/dkubb/abstract_type.
+  module AbstractType
+
+    # Hook called when module is included
+    #
+    # @param [Module] descendant
+    #   the module or class including AbstractType
+    #
+    # @return [undefined]
+    #
+    # @api private
+    def self.included(descendant)
+      super
+      create_new_method(descendant)
+      descendant.extend(AbstractMethodDeclarations)
+    end
+
+    private_class_method :included
+
+    # Define the new method on the abstract type
+    #
+    # Ensures that the instance cannot be of the abstract type
+    # and must be a descendant.
+    #
+    # @param [Class] abstract_class
+    #
+    # @return [undefined]
+    #
+    # @api private
+    def self.create_new_method(abstract_class)
+      abstract_class.define_singleton_method(:new) do |*args, &block|
+        if equal?(abstract_class)
+          fail NotImplementedError, "#{self} is an abstract type"
+        else
+          super(*args, &block)
+        end
+      end
+    end
+
+    private_class_method :create_new_method
+
+    module AbstractMethodDeclarations
+
+      # Create abstract instance methods
+      #
+      # @example
+      #   class Foo
+      #     include AbstractType
+      #
+      #     # Create an abstract instance method
+      #     abstract_method :some_method
+      #   end
+      #
+      # @param [Array<#to_s>] names
+      #
+      # @return [self]
+      #
+      # @api public
+      def abstract_method(*names)
+        names.each(&method(:create_abstract_instance_method))
+        self
+      end
+
+      # Create abstract singleton methods
+      #
+      # @example
+      #   class Foo
+      #     include AbstractType
+      #
+      #     # Create an abstract instance method
+      #     abstract_singleton_method :some_method
+      #   end
+      #
+      # @param [Array<#to_s>] names
+      #
+      # @return [self]
+      #
+      # @api private
+      def abstract_singleton_method(*names)
+        names.each(&method(:create_abstract_singleton_method))
+        self
+      end
+
+    private
+
+      # Create abstract singleton method
+      #
+      # @param [#to_s] name
+      #   the name of the method to create
+      #
+      # @return [undefined]
+      #
+      # @api private
+      def create_abstract_singleton_method(name)
+        define_singleton_method(name) do |*|
+          fail NotImplementedError, "#{self}.#{name} is not implemented"
+        end
+      end
+
+      # Create abstract instance method
+      #
+      # @param [#to_s] name
+      #   the name of the method to create
+      #
+      # @return [undefined]
+      #
+      # @api private
+      def create_abstract_instance_method(name)
+        define_method(name) do |*|
+          fail NotImplementedError, "#{self.class}##{name} is not implemented"
+        end
+      end
+
+    end # AbstractMethodDeclarations
+  end # AbstractType
+end # Unparser
diff --git a/lib/unparser/adamantium.rb b/lib/unparser/adamantium.rb
new file mode 100644
index 0000000..f63c446
--- /dev/null
+++ b/lib/unparser/adamantium.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Allows objects to be made immutable
+  #
+  # Original code before vendoring and reduction from: https://github.com/dkubb/adamantium.
+  module Adamantium
+    module InstanceMethods
+      # A noop #dup for immutable objects
+      #
+      # @return [self]
+      #
+      # @api public
+      def dup
+        self
+      end
+
+      # Freeze the object
+      #
+      # @return [Object]
+      #
+      # @api public
+      def freeze
+        memoized_method_cache
+        super()
+      end
+
+    private
+
+      def memoized_method_cache
+        @memoized_method_cache ||= Memory.new({})
+      end
+
+    end # InstanceMethods
+
+    # Storage for memoized methods
+    class Memory
+
+      # Initialize the memory storage for memoized methods
+      #
+      # @return [undefined]
+      #
+      # @api private
+      def initialize(values)
+        @values  = values
+        @monitor = Monitor.new
+        freeze
+      end
+
+      # Fetch the value from memory, or evaluate if it does not exist
+      #
+      # @param [Symbol] name
+      #
+      # @yieldreturn [Object]
+      #   the value to memoize
+      #
+      # @api public
+      def fetch(name)
+        @values.fetch(name) do      # check for the key
+          @monitor.synchronize do   # acquire a lock if the key is not found
+            @values.fetch(name) do  # recheck under lock
+              @values[name] = yield # set the value
+            end
+          end
+        end
+      end
+    end # Memory
+
+    # Methods mixed in to adamantium classes
+    module ClassMethods
+
+      # Instantiate a new frozen object
+      #
+      # @return [Object]
+      #
+      # @api public
+      def new(*)
+        super.freeze
+      end
+
+    end # ClassMethods
+
+    # Methods mixed in to adamantium modules
+    module ModuleMethods
+
+      # Memoize a list of methods
+      #
+      # @param [Array<#to_s>] methods
+      #   a list of methods to memoize
+      #
+      # @return [self]
+      #
+      # @api public
+      def memoize(*methods)
+        methods.each(&method(:memoize_method))
+        self
+      end
+
+      # Test if method is memoized
+      #
+      # @param [Symbol] name
+      #
+      # @return [Bool]
+      def memoized?(method_name)
+        memoized_methods.key?(method_name)
+      end
+
+      # Return unmemoized instance method
+      #
+      # @param [Symbol] name
+      #
+      # @return [UnboundMethod]
+      #   the memoized method
+      #
+      # @raise [NameError]
+      #   raised if the method is unknown
+      #
+      # @api public
+      def unmemoized_instance_method(method_name)
+        memoized_methods.fetch(method_name) do
+          fail ArgumentError, "##{method_name} is not memoized"
+        end
+      end
+
+    private
+
+      def memoize_method(method_name)
+        if memoized_methods.key?(method_name)
+          fail ArgumentError, "##{method_name} is already memoized"
+        end
+
+        memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
+      end
+
+      def memoized_methods
+        @memoized_methods ||= {}
+      end
+
+    end # ModuleMethods
+
+    def self.included(descendant)
+      descendant.class_eval do
+        include InstanceMethods
+        extend ModuleMethods
+        extend ClassMethods if instance_of?(Class)
+      end
+    end
+    private_class_method :included
+  end # Adamantium
+end # Unparser
diff --git a/lib/unparser/adamantium/method_builder.rb b/lib/unparser/adamantium/method_builder.rb
new file mode 100644
index 0000000..7ae4b22
--- /dev/null
+++ b/lib/unparser/adamantium/method_builder.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Adamantium
+    # Build the memoized method
+    class MethodBuilder
+
+      # Raised when the method arity is invalid
+      class InvalidArityError < ArgumentError
+
+        # Initialize an invalid arity exception
+        #
+        # @param [Module] descendant
+        # @param [Symbol] method
+        # @param [Integer] arity
+        #
+        # @api private
+        def initialize(descendant, method, arity)
+          super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
+        end
+
+      end # InvalidArityError
+
+      # Raised when a block is passed to a memoized method
+      class BlockNotAllowedError < ArgumentError
+
+        # Initialize a block not allowed exception
+        #
+        # @param [Module] descendant
+        # @param [Symbol] method
+        #
+        # @api private
+        def initialize(descendant, method)
+          super("Cannot pass a block to #{descendant}##{method}, it is memoized")
+        end
+
+      end # BlockNotAllowedError
+
+      # Initialize an object to build a memoized method
+      #
+      # @param [Module] descendant
+      # @param [Symbol] method_name
+      #
+      # @return [undefined]
+      #
+      # @api private
+      def initialize(descendant, method_name)
+        @descendant          = descendant
+        @method_name         = method_name
+        @original_visibility = visibility
+        @original_method     = @descendant.instance_method(@method_name)
+        assert_arity(@original_method.arity)
+      end
+
+      # Build a new memoized method
+      #
+      # @example
+      #   method_builder.call  # => creates new method
+      #
+      # @return [UnboundMethod]
+      #
+      # @api public
+      def call
+        remove_original_method
+        create_memoized_method
+        set_method_visibility
+        @original_method
+      end
+
+    private
+
+      def assert_arity(arity)
+        if arity.nonzero?
+          fail InvalidArityError.new(@descendant, @method_name, arity)
+        end
+      end
+
+      def remove_original_method
+        name = @method_name
+        @descendant.module_eval { undef_method(name) }
+      end
+
+      def create_memoized_method
+        name =   @method_name
+        method = @original_method
+        @descendant.module_eval do
+          define_method(name) do |&block|
+            fail BlockNotAllowedError.new(self.class, name) if block
+
+            memoized_method_cache.fetch(name) do
+              method.bind(self).call.freeze
+            end
+          end
+        end
+      end
+
+      def set_method_visibility
+        @descendant.__send__(@original_visibility, @method_name)
+      end
+
+      def visibility
+        if    @descendant.private_method_defined?(@method_name)   then :private
+        elsif @descendant.protected_method_defined?(@method_name) then :protected
+        else
+          :public
+        end
+      end
+
+    end # MethodBuilder
+  end # Adamantium
+end # Unparser
diff --git a/lib/unparser/anima.rb b/lib/unparser/anima.rb
new file mode 100644
index 0000000..8618c9f
--- /dev/null
+++ b/lib/unparser/anima.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Original code before vendoring and reduction from: https://github.com/mbj/anima.
+  class Anima < Module
+    include Adamantium, Equalizer.new(:attributes)
+
+    # Return names
+    #
+    # @return [AttributeSet]
+    attr_reader :attributes
+
+    # Initialize object
+    #
+    # @return [undefined]
+    #
+    # rubocop:disable Lint/MissingSuper
+    def initialize(*names)
+      @attributes = names.uniq.map(&Attribute.public_method(:new)).freeze
+    end
+    # rubocop:enable Lint/MissingSuper
+
+    # Return new anima with attributes added
+    #
+    # @return [Anima]
+    #
+    # @example
+    #   anima = Anima.new(:foo)
+    #   anima.add(:bar) # equals Anima.new(:foo, :bar)
+    #
+    def add(*names)
+      new(attribute_names + names)
+    end
+
+    # Return new anima with attributes removed
+    #
+    # @return [Anima]
+    #
+    # @example
+    #   anima = Anima.new(:foo, :bar)
+    #   anima.remove(:bar) # equals Anima.new(:foo)
+    #
+    def remove(*names)
+      new(attribute_names - names)
+    end
+
+    # Return attributes hash for instance
+    #
+    # @param [Object] object
+    #
+    # @return [Hash]
+    def attributes_hash(object)
+      attributes.each_with_object({}) do |attribute, attributes_hash|
+        attributes_hash[attribute.name] = attribute.get(object)
+      end
+    end
+
+    # Return attribute names
+    #
+    # @return [Enumerable<Symbol>]
+    def attribute_names
+      attributes.map(&:name)
+    end
+    memoize :attribute_names
+
+    # Initialize instance
+    #
+    # @param [Object] object
+    #
+    # @param [Hash] attribute_hash
+    #
+    # @return [self]
+    def initialize_instance(object, attribute_hash)
+      assert_known_attributes(object.class, attribute_hash)
+      attributes.each do |attribute|
+        attribute.load(object, attribute_hash)
+      end
+      self
+    end
+
+    # Static instance methods for anima infected classes
+    module InstanceMethods
+      # Initialize an anima infected object
+      #
+      # @param [#to_h] attributes
+      #   a hash that matches anima defined attributes
+      #
+      # @return [undefined]
+      #
+      # rubocop:disable Lint/MissingSuper
+      def initialize(attributes)
+        self.class.anima.initialize_instance(self, attributes)
+      end
+      # rubocop:enable Lint/MissingSuper
+
+      # Return a hash representation of an anima infected object
+      #
+      # @example
+      #   anima.to_h # => { :foo => : bar }
+      #
+      # @return [Hash]
+      #
+      # @api public
+      def to_h
+        self.class.anima.attributes_hash(self)
+      end
+
+      # Return updated instance
+      #
+      # @example
+      #   klass = Class.new do
+      #     include Anima.new(:foo, :bar)
+      #   end
+      #
+      #   foo = klass.new(:foo => 1, :bar => 2)
+      #   updated = foo.with(:foo => 3)
+      #   updated.foo # => 3
+      #   updated.bar # => 2
+      #
+      # @param [Hash] attributes
+      #
+      # @return [Anima]
+      #
+      # @api public
+      def with(attributes)
+        self.class.new(to_h.update(attributes))
+      end
+    end # InstanceMethods
+
+    private
+
+    # Infect the instance with anima
+    #
+    # @param [Class, Module] scope
+    #
+    # @return [undefined]
+    def included(descendant)
+      descendant.instance_exec(self, attribute_names) do |anima, names|
+        # Define anima method
+        define_singleton_method(:anima) { anima }
+
+        # Define instance methods
+        include InstanceMethods
+
+        # Define attribute readers
+        attr_reader(*names)
+
+        # Define equalizer
+        include Equalizer.new(*names)
+      end
+    end
+
+    # Fail unless keys in +attribute_hash+ matches #attribute_names
+    #
+    # @param [Class] klass
+    #   the class being initialized
+    #
+    # @param [Hash] attribute_hash
+    #   the attributes to initialize +object+ with
+    #
+    # @return [undefined]
+    #
+    # @raise [Error]
+    def assert_known_attributes(klass, attribute_hash)
+      keys = attribute_hash.keys
+
+      unknown = keys - attribute_names
+      missing = attribute_names - keys
+
+      unless unknown.empty? && missing.empty?
+        fail Error.new(klass, missing, unknown)
+      end
+    end
+
+    # Return new instance
+    #
+    # @param [Enumerable<Symbol>] attributes
+    #
+    # @return [Anima]
+    def new(attributes)
+      self.class.new(*attributes)
+    end
+  end # Anima
+end # Unparser
diff --git a/lib/unparser/anima/attribute.rb b/lib/unparser/anima/attribute.rb
new file mode 100644
index 0000000..ff6f0c4
--- /dev/null
+++ b/lib/unparser/anima/attribute.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Anima
+    # An attribute
+    class Attribute
+      include Adamantium, Equalizer.new(:name)
+
+      # Initialize attribute
+      #
+      # @param [Symbol] name
+      def initialize(name)
+        @name = name
+        @instance_variable_name = :"@#{name}"
+      end
+
+      # Return attribute name
+      #
+      # @return [Symbol]
+      attr_reader :name
+
+      # Return instance variable name
+      #
+      # @return [Symbol]
+      attr_reader :instance_variable_name
+
+      # Load attribute
+      #
+      # @param [Object] object
+      # @param [Hash] attributes
+      #
+      # @return [self]
+      def load(object, attributes)
+        set(object, attributes.fetch(name))
+      end
+
+      # Get attribute value from object
+      #
+      # @param [Object] object
+      #
+      # @return [Object]
+      def get(object)
+        object.public_send(name)
+      end
+
+      # Set attribute value in object
+      #
+      # @param [Object] object
+      # @param [Object] value
+      #
+      # @return [self]
+      def set(object, value)
+        object.instance_variable_set(instance_variable_name, value)
+
+        self
+      end
+    end # Attribute
+  end # Anima
+end # Unparser
diff --git a/lib/unparser/anima/error.rb b/lib/unparser/anima/error.rb
new file mode 100644
index 0000000..f77daa8
--- /dev/null
+++ b/lib/unparser/anima/error.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Anima
+    # Abstract base class for anima errors
+    class Error < RuntimeError
+      FORMAT = '%s attributes missing: %s, unknown: %s'.freeze
+      private_constant(*constants(false))
+
+      # Initialize object
+      #
+      # @param [Class] klass
+      #   the class being initialized
+      # @param [Enumerable<Symbol>] missing
+      # @param [Enumerable<Symbol>] unknown
+      #
+      # @return [undefined]
+      def initialize(klass, missing, unknown)
+        super(format(FORMAT, klass, missing, unknown))
+      end
+    end # Error
+  end # Anima
+end # Unparser
diff --git a/lib/unparser/ast.rb b/lib/unparser/ast.rb
index 61ec3f9..976cd9b 100644
--- a/lib/unparser/ast.rb
+++ b/lib/unparser/ast.rb
@@ -2,9 +2,7 @@
 
 module Unparser
   # Namespace for AST processing tools
-  # :reek:TooManyConstants
   module AST
-
     FIRST_CHILD = ->(node) { node.children.first }.freeze
     TAUTOLOGY   = ->(_node) { true }.freeze
 
@@ -80,7 +78,7 @@ module Unparser
 
     # AST enumerator
     class Enumerator
-      include Adamantium::Flat, Concord.new(:node, :controller), Enumerable
+      include Adamantium, Concord.new(:node, :controller), Enumerable
 
       # Return new instance
       #
diff --git a/lib/unparser/ast/local_variable_scope.rb b/lib/unparser/ast/local_variable_scope.rb
index c4e3c88..cfd029b 100644
--- a/lib/unparser/ast/local_variable_scope.rb
+++ b/lib/unparser/ast/local_variable_scope.rb
@@ -61,35 +61,22 @@ module Unparser
       #
       # @api private
       #
-      def first_assignment_in_body_and_used_in_condition?(body, condition)
-        condition_reads = AST.local_variable_reads(condition)
+      def first_assignment_in?(left, right)
+        condition_reads = AST.local_variable_reads(right)
 
-        candidates = AST.local_variable_assignments(body).select do |node|
-          name = node.children.first
-          condition_reads.include?(name)
+        candidates = AST.local_variable_assignments(left).select do |node|
+          condition_reads.include?(node.children.first)
         end
 
-        candidates.any? do |node|
-          first_assignment?(node)
-        end
+        candidates.any?(&public_method(:first_assignment?))
       end
 
     private
 
-      # Match node
-      #
-      # @param [Parser::AST::Node] needle
-      #   if block given
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
       def match(needle)
         @items.each do |node, current, before|
           return yield(current, before) if node.equal?(needle)
         end
-        false
       end
 
     end # LocalVariableScope
@@ -137,25 +124,10 @@ module Unparser
 
     private
 
-      # Return current set of local variables
-      #
-      # @return [Set<Symbol>]
-      #
-      # @api private
-      #
       def current
         @stack.last
       end
 
-      # Visit node and record local variable state
-      #
-      # @param [Parser::AST::Node] node
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      # ignore :reek:LongYieldList
       def visit(node, &block)
         before = current.dup
         enter(node)
@@ -166,75 +138,33 @@ module Unparser
         leave(node)
       end
 
-      # Record local variable state
-      #
-      # @param [Parser::AST::Node] node
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def enter(node)
         case node.type
         when *RESET_NODES
           push_reset
-        when *ASSIGN_NODES
+        when ASSIGN_NODES
           define(node.children.first)
         when *INHERIT_NODES
           push_inherit
         end
       end
 
-      # Pop from local variable state
-      #
-      # @param [Parser::AST::Node] node
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def leave(node)
         pop if CLOSE_NODES.include?(node.type)
       end
 
-      # Define a local variable on current stack
-      #
-      # @param [Symbol] name
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def define(name)
         current << name
       end
 
-      # Push reset scope on stack
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def push_reset
         @stack << Set.new
       end
 
-      # Push inherited lvar scope on stack
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def push_inherit
         @stack << current.dup
       end
 
-      # Pop lvar scope from stack
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def pop
         @stack.pop
       end
diff --git a/lib/unparser/buffer.rb b/lib/unparser/buffer.rb
index d60d80f..ebb4dca 100644
--- a/lib/unparser/buffer.rb
+++ b/lib/unparser/buffer.rb
@@ -3,8 +3,6 @@
 module Unparser
 
   # Buffer used to emit into
-  #
-  # ignore :reek:TooManyMethods
   class Buffer
 
     NL = "\n".freeze
@@ -46,7 +44,6 @@ module Unparser
     #
     def append_without_prefix(string)
       write(string)
-      self
     end
 
     # Increase indent
@@ -79,7 +76,13 @@ module Unparser
     #
     def nl
       write(NL)
-      self
+    end
+
+    def root_indent
+      before = @indent
+      @indent = 0
+      yield
+      @indent = before
     end
 
     # Test for a fresh line
@@ -102,39 +105,25 @@ module Unparser
       @content.dup.freeze
     end
 
-    # Capture the content written to the buffer within the block
+    # Write raw fragment to buffer
     #
-    # @return [String]
+    # Does not do indentation logic.
     #
-    # @api private
+    # @param [String] fragment
     #
-    def capture_content
-      size_before = @content.size
-      yield
-      @content[size_before..-1]
+    # @return [self]
+    def write(fragment)
+      @content << fragment
+      self
     end
 
   private
 
     INDENT_SPACE = '  '.freeze
 
-    # Write prefix
-    #
-    # @return [String]
-    #
-    # @api private
-    #
     def prefix
       write(INDENT_SPACE * @indent)
     end
 
-    # Write to content buffer
-    #
-    # @param [String] fragment
-    #
-    def write(fragment)
-      @content << fragment
-    end
-
   end # Buffer
 end # Unparser
diff --git a/lib/unparser/cli.rb b/lib/unparser/cli.rb
index a5b1c15..c1fa779 100644
--- a/lib/unparser/cli.rb
+++ b/lib/unparser/cli.rb
@@ -1,29 +1,61 @@
 # frozen_string_literal: true
 
-require 'unparser'
-require 'optparse'
-require 'diff/lcs'
-require 'diff/lcs/hunk'
-
-require 'unparser/cli/source'
-require 'unparser/cli/differ'
-require 'unparser/cli/color'
-
 module Unparser
   # Unparser CLI implementation
-  #
-  # :reek:InstanceVariableAssumption
-  # :reek:TooManyInstanceVariables
   class CLI
 
     EXIT_SUCCESS = 0
     EXIT_FAILURE = 1
 
+    class Target
+      include AbstractType
+
+      # Path target
+      class Path < self
+        include Concord.new(:path)
+
+        # Validation for this target
+        #
+        # @return [Validation]
+        def validation
+          Validation.from_path(path)
+        end
+
+        # Literal for this target
+        #
+        # @return [Validation]
+        def literal_validation
+          Validation::Literal.from_path(path)
+        end
+      end
+
+      # String target
+      class String
+        include Concord.new(:string)
+
+        # Validation for this target
+        #
+        # @return [Validation]
+        def validation
+          Validation.from_string(string)
+        end
+
+        # Literal for this target
+        #
+        # @return [Validation]
+        def literal_validation
+          Validation::Literal.from_string(path)
+        end
+      end # String
+    end # Target
+
+    private_constant(*constants(false))
+
     # Run CLI
     #
     # @param [Array<String>] arguments
     #
-    # @return [Fixnum]
+    # @return [Integer]
     #   the exit status
     #
     # @api private
@@ -39,22 +71,22 @@ module Unparser
     # @return [undefined]
     #
     # @api private
-    #
-    # ignore :reek:TooManyStatements
     def initialize(arguments)
-      @sources = []
-      @ignore = Set.new
+      @ignore  = Set.new
+      @targets = []
 
-      @success   = true
-      @fail_fast = false
-      @verbose   = false
+      @fail_fast  = false
+      @start_with = nil
+      @success    = true
+      @validation = :validation
+      @verbose    = false
 
       opts = OptionParser.new do |builder|
         add_options(builder)
       end
 
       opts.parse!(arguments).each do |name|
-        @sources.concat(sources(name))
+        @targets.concat(targets(name))
       end
     end
 
@@ -66,38 +98,40 @@ module Unparser
     #
     # @api private
     #
-    # ignore :reek:TooManyStatements
+    # rubocop:disable Metrics/MethodLength
     def add_options(builder)
       builder.banner = 'usage: unparse [options] FILE [FILE]'
       builder.separator('')
       builder.on('-e', '--evaluate SOURCE') do |source|
-        @sources << Source::String.new(source)
+        @targets << Target::String.new(source)
       end
-      builder.on('--start-with FILE') do |file|
-        @start_with = sources(file).first
+      builder.on('--start-with FILE') do |path|
+        @start_with = targets(path).first
       end
       builder.on('-v', '--verbose') do
         @verbose = true
       end
+      builder.on('-l', '--literal') do
+        @validation = :literal_validation
+      end
       builder.on('--ignore FILE') do |file|
-        @ignore.merge(sources(file))
+        @ignore.merge(targets(file))
       end
       builder.on('--fail-fast') do
         @fail_fast = true
       end
     end
+    # rubocop:enable Metrics/MethodLength
 
     # Return exit status
     #
-    # @return [Fixnum]
+    # @return [Integer]
     #
     # @api private
     #
     def exit_status
-      effective_sources.each do |source|
-        next if @ignore.include?(source)
-
-        process_source(source)
+      effective_targets.each do |target|
+        process_target(target)
         break if @fail_fast && !@success
       end
 
@@ -106,67 +140,41 @@ module Unparser
 
   private
 
-    # Process source
-    #
-    # @param [CLI::Source] source
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def process_source(source)
-      if source.success?
-        puts source.report if @verbose
-        puts "Success: #{source.identification}"
+    def process_target(target)
+      validation = target.public_send(@validation)
+      if validation.success?
+        puts validation.report if @verbose
+        puts "Success: #{validation.identification}"
       else
-        puts source.report
-        puts "Error: #{source.identification}"
+        puts validation.report
+        puts "Error: #{validation.identification}"
         @success = false
       end
     end
 
-    # Return effective sources
-    #
-    # @return [Enumerable<CLI::Source>]
-    #
-    # @api private
-    #
-    def effective_sources
+    def effective_targets
       if @start_with
         reject = true
-        @sources.reject do |source|
-          if reject && source.eql?(@start_with)
+        @targets.reject do |targets|
+          if reject && targets.eql?(@start_with)
             reject = false
           end
 
           reject
         end
       else
-        @sources
-      end
+        @targets
+      end.reject(&@ignore.method(:include?))
     end
 
-    # Return sources for file name
-    #
-    # @param [String] file_name
-    #
-    # @return [Enumerable<CLI::Source>]
-    #
-    # @api private
-    #
-    # ignore :reek:UtilityFunction
-    def sources(file_name)
-      files =
-        if File.directory?(file_name)
-          Dir.glob(File.join(file_name, '**/*.rb')).sort
-        elsif File.file?(file_name)
-          [file_name]
-        else
-          Dir.glob(file_name).sort
-        end
-
-      files.map(&Source::File.method(:new))
+    def targets(file_name)
+      if File.directory?(file_name)
+        Dir.glob(File.join(file_name, '**/*.rb')).sort
+      elsif File.file?(file_name)
+        [file_name]
+      else
+        Dir.glob(file_name).sort
+      end.map { |file| Target::Path.new(Pathname.new(file)) }
     end
-
   end # CLI
 end # Unparser
diff --git a/lib/unparser/cli/differ.rb b/lib/unparser/cli/differ.rb
deleted file mode 100644
index cf6beb4..0000000
--- a/lib/unparser/cli/differ.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class CLI
-    # Class to create diffs from source code
-    class Differ
-      include Adamantium::Flat, Concord.new(:old, :new), Procto.call(:colorized_diff)
-
-      CONTEXT_LINES = 5
-
-      # Return new object
-      #
-      # @param [String] old
-      # @param [String] new
-      #
-      # @return [Differ]
-      #
-      # @api private
-      #
-      def self.build(old, new)
-        new(lines(old), lines(new))
-      end
-
-      # Return colorized diff line
-      #
-      # @param [String] line
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def self.colorize_line(line)
-        case line[0]
-        when '+'
-          Color::GREEN
-        when '-'
-          Color::RED
-        else
-          Color::NONE
-        end.format(line)
-      end
-
-      # Break up source into lines
-      #
-      # @param [String] source
-      #
-      # @return [Array<String>]
-      #
-      # @api private
-      #
-      def self.lines(source)
-        source.lines.map(&:chomp)
-      end
-      private_class_method :lines
-
-      # Return hunks
-      #
-      # @return [Array<Diff::LCS::Hunk>]
-      #
-      # @api private
-      #
-      def hunks
-        file_length_difference = new.length - old.length
-        diffs.map do |piece|
-          hunk = Diff::LCS::Hunk.new(old, new, piece, CONTEXT_LINES, file_length_difference)
-          file_length_difference = hunk.file_length_difference
-          hunk
-        end
-      end
-
-      # Return collapsed hunks
-      #
-      # @return [Enumerable<Diff::LCS::Hunk>]
-      #
-      # @api private
-      #
-      def collapsed_hunks
-        hunks.each_with_object([]) do |hunk, output|
-          last = output.last
-
-          if last && hunk.merge(last)
-            output.pop
-          end
-
-          output << hunk
-        end
-      end
-
-      # Return source diff
-      #
-      # @return [String]
-      #   if there is a diff
-      #
-      # @return [nil]
-      #   otherwise
-      #
-      # @api private
-      #
-      def diff
-        output = +''
-
-        collapsed_hunks.each do |hunk|
-          output << hunk.diff(:unified) << "\n"
-        end
-
-        output
-      end
-      memoize :diff
-
-      # Return colorized source diff
-      #
-      # @return [String]
-      #   if there is a diff
-      #
-      # @return [nil]
-      #   otherwise
-      #
-      # @api private
-      #
-      def colorized_diff
-        diff.lines.map do |line|
-          self.class.colorize_line(line)
-        end.join
-      end
-      memoize :colorized_diff
-
-    private
-
-      # Return diffs
-      #
-      # @return [Array<Array>]
-      #
-      # @api private
-      #
-      def diffs
-        Diff::LCS.diff(old, new)
-      end
-      memoize :diffs
-
-      # Return max length
-      #
-      # @return [Fixnum]
-      #
-      # @api private
-      #
-      def max_length
-        [old, new].map(&:length).max
-      end
-
-    end # CLI
-  end # Differ
-end # Unparser
diff --git a/lib/unparser/cli/source.rb b/lib/unparser/cli/source.rb
deleted file mode 100644
index 7195960..0000000
--- a/lib/unparser/cli/source.rb
+++ /dev/null
@@ -1,267 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class CLI
-    # Source representation for CLI sources
-    #
-    # ignore :reek:TooManyMethods
-    class Source
-      include AbstractType, Adamantium::Flat, NodeHelpers
-
-      # Source state generated after first unparse
-      class Generated
-        include Concord::Public.new(:source, :ast, :error)
-
-        # Test if source was generated successfully
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def success?
-          !error
-        end
-
-        # Build generated source
-        #
-        # @param [Parser::AST::Node] ast
-        #
-        # @api private
-        #
-        def self.build(ast)
-          source = Unparser.unparse(ast)
-          new(source, ast, nil)
-        rescue StandardError => exception
-          new(nil, ast, exception)
-        end
-      end
-
-      # Test if source could be unparsed successfully
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def success?
-        generated.success? && original_ast && generated_ast && original_ast.eql?(generated_ast)
-      end
-
-      # Return error report
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def report
-        if original_ast && generated_ast
-          report_with_ast_diff
-        elsif !original_ast
-          report_original
-        elsif !generated.success?
-          report_unparser
-        elsif !generated_ast
-          report_generated
-        else
-          raise
-        end
-      end
-      memoize :report
-
-    private
-
-      # Return generated source
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def generated
-        Source::Generated.build(original_ast)
-      end
-      memoize :generated
-
-      # Return stripped source
-      #
-      # @param [String] source
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      # ignore :reek:UtilityFunction
-      def strip(source)
-        source = source.rstrip
-        indent = source.scan(/^\s*/).first
-        source.gsub(/^#{indent}/, '')
-      end
-
-      # Return error report for parsing original
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def report_original
-        strip(<<-MESSAGE)
-          Parsing of original source failed:
-          #{original_source}
-        MESSAGE
-      end
-
-      # Report unparser bug
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def report_unparser
-        message = ['Unparsing parsed AST failed']
-        error = generated.error
-        message << error
-        error.backtrace.take(20).each(&message.method(:<<))
-        message << 'Original-AST:'
-        message << original_ast.inspect
-        message.join("\n")
-      end
-
-      # Return error report for parsing generated
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def report_generated
-        strip(<<-MESSAGE)
-          Parsing of generated source failed:
-          Original-source:
-          #{original_source}
-          Original-AST:
-          #{original_ast.inspect}
-          Source:
-          #{generated.source}
-        MESSAGE
-      end
-
-      # Return error report with AST difference
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def report_with_ast_diff
-        strip(<<-MESSAGE)
-          #{ast_diff}
-          Original-Source:\n#{original_source}
-          Original-AST:\n#{original_ast.inspect}
-          Generated-Source:\n#{generated.source}
-          Generated-AST:\n#{generated_ast.inspect}
-        MESSAGE
-      end
-
-      # Return ast diff
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def ast_diff
-        Differ.call(
-          original_ast.inspect.lines.map(&:chomp),
-          generated_ast.inspect.lines.map(&:chomp)
-        )
-      end
-
-      # Return generated AST
-      #
-      # @return [Parser::AST::Node]
-      #   if parser was sucessful for generated ast
-      #
-      # @return [nil]
-      #   otherwise
-      #
-      # @api private
-      #
-      def generated_ast
-        generated.success? && Preprocessor.run(Unparser.parse(generated.source))
-      rescue Parser::SyntaxError
-        nil
-      end
-      memoize :generated_ast
-
-      # Return original AST
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
-      def original_ast
-        Preprocessor.run(Unparser.parse(original_source))
-      rescue Parser::SyntaxError
-        nil
-      end
-      memoize :original_ast
-
-      # CLI source from string
-      class String < self
-        include Concord.new(:original_source)
-
-        # Return identification
-        #
-        # @return [String]
-        #
-        # @api private
-        #
-        def identification
-          '(string)'
-        end
-
-      end # String
-
-      # CLI source from file
-      class File < self
-        include Concord.new(:file_name)
-
-        # Return identification
-        #
-        # @return [String]
-        #
-        # @api private
-        #
-        def identification
-          "(#{file_name})"
-        end
-
-      private
-
-        # Return original source
-        #
-        # @return [String]
-        #
-        # @api private
-        #
-        def original_source
-          ::File.read(file_name)
-        end
-        memoize :original_source
-
-      end # File
-
-      # Source passed in as node
-      class Node < self
-        include Concord.new(:original_ast)
-
-        # Return original source
-        #
-        # @return [String]
-        #
-        # @api private
-        #
-        def original_source
-          Unparser.unparse(original_ast)
-        end
-        memoize :original_source
-      end # Node
-
-    end # Source
-  end # CLI
-end # Unparser
diff --git a/lib/unparser/cli/color.rb b/lib/unparser/color.rb
similarity index 66%
rename from lib/unparser/cli/color.rb
rename to lib/unparser/color.rb
index 9a1ca63..9120f8c 100644
--- a/lib/unparser/cli/color.rb
+++ b/lib/unparser/color.rb
@@ -3,16 +3,13 @@
 module Unparser
   # Class to colorize strings
   class Color
-    include Adamantium::Flat, Concord.new(:code)
+    include Adamantium, Concord.new(:code)
 
     # Format text with color
     #
     # @param [String] text
     #
     # @return [String]
-    #
-    # @api private
-    #
     def format(text)
       "\e[#{code}m#{text}\e[0m"
     end
@@ -25,28 +22,23 @@ module Unparser
       #
       # @return [String]
       #   the argument string
-      #
-      # @api private
-      #
       def format(text)
         text
       end
 
     private
 
-      # Initialize null color
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
+      # Well rubocop you are static so you do not have a clue here ;)
+      # rubocop:disable Style/RedundantInitialize
+      # rubocop:disable Style/MissingSuper
       def initialize; end
+      # rubocop:enable Style/RedundantInitialize
+      # rubocop:enable Style/MissingSuper
 
     end.new
 
     RED   = Color.new(31)
     GREEN = Color.new(32)
-    BLUE  = Color.new(34)
 
   end # Color
 end # Unparser
diff --git a/lib/unparser/comments.rb b/lib/unparser/comments.rb
index 1c97668..5a7696b 100644
--- a/lib/unparser/comments.rb
+++ b/lib/unparser/comments.rb
@@ -3,8 +3,6 @@
 module Unparser
 
   # Holds the comments that remain to be emitted
-  #
-  # ignore :reek:RepeatedConditional
   class Comments
 
     # Proxy to singleton
@@ -113,39 +111,15 @@ module Unparser
 
   private
 
-    # Take comments while the provided block returns true
-    #
-    # @yield [Parser::Source::Comment]
-    #
-    # @return [Array]
-    #
-    # @api private
-    #
     def take_while
       number_to_take = @comments.index { |comment| !yield(comment) } || @comments.size
       @comments.shift(number_to_take)
     end
 
-    # Take comments up to the line number
-    #
-    # @param [Fixnum] line
-    #
-    # @return [Array]
-    #
-    # @api private
-    #
     def take_up_to_line(line)
       take_while { |comment| comment.location.expression.line <= line }
     end
 
-    # Unshift document comments and return the rest
-    #
-    # @param [Array] comments
-    #
-    # @return [Array]
-    #
-    # @api private
-    #
     def unshift_documents(comments)
       doc_comments, other_comments = comments.partition(&:document?)
       doc_comments.reverse_each { |comment| @comments.unshift(comment) }
diff --git a/lib/unparser/concord.rb b/lib/unparser/concord.rb
new file mode 100644
index 0000000..f43227d
--- /dev/null
+++ b/lib/unparser/concord.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Unparser
+  # A mixin to define a composition
+  #
+  # Original code before vendoring and reduction from: https://github.com/mbj/concord.
+  class Concord < Module
+    include Adamantium, Equalizer.new(:names)
+
+    # The maximum number of objects the hosting class is composed of
+    MAX_NR_OF_OBJECTS = 3
+
+    # Return names
+    #
+    # @return [Enumerable<Symbol>]
+    #
+    # @api private
+    #
+    attr_reader :names
+
+    private
+
+    # Initialize object
+    #
+    # @return [undefined]
+    #
+    # @api private
+    #
+    # rubocop:disable Lint/MissingSuper
+    def initialize(*names)
+      if names.length > MAX_NR_OF_OBJECTS
+        fail "Composition of more than #{MAX_NR_OF_OBJECTS} objects is not allowed"
+      end
+
+      @names = names
+      define_initialize
+      define_readers
+      define_equalizer
+    end
+    # rubocop:enable Lint/MissingSuper
+
+    # Define equalizer
+    #
+    # @return [undefined]
+    #
+    # @api private
+    #
+    def define_equalizer
+      include(Equalizer.new(*names))
+    end
+
+    # Define readers
+    #
+    # @return [undefined]
+    #
+    # @api private
+    #
+    def define_readers
+      attribute_names = names
+      attr_reader(*attribute_names)
+
+      protected(*attribute_names) if attribute_names.any?
+    end
+
+    # Define initialize method
+    #
+    # @return [undefined]
+    #
+    # @api private
+    #
+    #
+    def define_initialize
+      ivars = instance_variable_names
+      size = names.size
+
+      define_method :initialize do |*args|
+        args_size = args.size
+        unless args_size.equal?(size)
+          fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
+        end
+
+        ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
+      end
+    end
+
+    # Return instance variable names
+    #
+    # @return [String]
+    #
+    # @api private
+    #
+    def instance_variable_names
+      names.map { |name| "@#{name}" }
+    end
+
+    # Mixin for public attribute readers
+    class Public < self
+
+      # Hook called when module is included
+      #
+      # @param [Class,Module] descendant
+      #
+      # @return [undefined]
+      #
+      # @api private
+      #
+      def included(descendant)
+        names.each do |name|
+          descendant.__send__(:public, name)
+        end
+      end
+    end # Public
+  end # Concord
+end # Unparser
diff --git a/lib/unparser/constants.rb b/lib/unparser/constants.rb
index eb789e0..41a7fb8 100644
--- a/lib/unparser/constants.rb
+++ b/lib/unparser/constants.rb
@@ -2,66 +2,19 @@
 
 module Unparser
   # All unparser constants maybe included in other libraries.
-  #
-  # False positive since constants are frozen dynamically
-  # to avoid duplication of `.freeze` calls
-  #
-  # :reek:TooManyConstants
   module Constants
 
-    # Return frozen symbol set from enumerable
-    #
-    # @param [Enumerable] enumerable
-    #
-    # @return [Set<Symbol>]
-    #
-    # @api private
-    #
-    def self.symbol_set(enumerable)
-      enumerable.map(&:to_sym).freeze
-    end
-    private_class_method :symbol_set
-
-    BRACKETS_CURLY  = IceNine.deep_freeze(%w[{ }])
-    BRACKETS_ROUND  = IceNine.deep_freeze(%w[( )])
-    BRACKETS_SQUARE = IceNine.deep_freeze(%w([ ]))
-
     # All unary operators of the ruby language
-    UNARY_OPERATORS = symbol_set %w[
+    UNARY_OPERATORS = %i[
       ! ~ -@ +@
-    ]
+    ].to_set.freeze
 
     # All binary operators of the ruby language
-    BINARY_OPERATORS = symbol_set %w[
+    BINARY_OPERATORS = %i[
       + - * / & | && || << >> ==
       === != <= < <=> > >= =~ !~ ^
       ** %
-    ]
-
-    COMMENT = '#'
-
-    WS       = ' '
-    NL       = "\n"
-    T_DOT    = '.'
-    T_LT     = '<'
-    T_DLT    = '<<'
-    T_AMP    = '&'
-    T_ASN    = '='
-    T_SPLAT  = '*'
-    T_DSPLAT = '**'
-    T_ASR    = '=>'
-    T_PIPE   = '|'
-    T_DCL    = '::'
-    T_NEG    = '!'
-    T_OR     = '||'
-    T_AND    = '&&'
-    T_COLON  = ':'
-
-    M_PO  = '('
-    M_PC  = ')'
-
-    SNGL_QUOTE = "'"
-    DBL_QUOTE  = '"'
+    ].to_set.freeze
 
     # Keywords
     K_DO       = 'do'
@@ -107,8 +60,6 @@ module Unparser
     K_FILE     = '__FILE__'
     K_THEN     = 'then'
 
-    DEFAULT_DELIMITER = ', '.freeze
-
     KEYWORDS = constants.each_with_object([]) do |name, keywords|
       value = const_get(name).freeze
       next unless name.to_s.start_with?('K_')
diff --git a/lib/unparser/diff.rb b/lib/unparser/diff.rb
new file mode 100644
index 0000000..52a2904
--- /dev/null
+++ b/lib/unparser/diff.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Class to create diffs from source code
+  class Diff
+    include Adamantium, Concord.new(:old, :new)
+
+    ADDITION = '+'
+    DELETION = '-'
+    NEWLINE  = "\n"
+
+    # Unified source diff between old and new
+    #
+    # @return [String]
+    #   if there is exactly one diff
+    #
+    # @return [nil]
+    #   otherwise
+    def diff
+      return if diffs.empty?
+
+      minimized_hunk.diff(:unified) + NEWLINE
+    end
+    memoize :diff
+
+    # Colorized unified source diff between old and new
+    #
+    # @return [String]
+    #   if there is a diff
+    #
+    # @return [nil]
+    #   otherwise
+    def colorized_diff
+      return unless diff
+
+      diff.lines.map(&self.class.method(:colorize_line)).join
+    end
+    memoize :colorized_diff
+
+    # Build new object from source strings
+    #
+    # @param [String] old
+    # @param [String] new
+    #
+    # @return [Diff]
+    def self.build(old, new)
+      new(lines(old), lines(new))
+    end
+
+    # Break up source into lines
+    #
+    # @param [String] source
+    #
+    # @return [Array<String>]
+    def self.lines(source)
+      source.lines.map(&:chomp)
+    end
+    private_class_method :lines
+
+  private
+
+    def diffs
+      ::Diff::LCS.diff(old, new)
+    end
+
+    def hunks
+      diffs.map do |diff|
+        ::Diff::LCS::Hunk.new(old.map(&:dup), new, diff, max_length, 0)
+      end
+    end
+
+    def minimized_hunk
+      head, *tail = hunks
+
+      tail.reduce(head) do |left, right|
+        right.merge(left)
+        right
+      end
+    end
+
+    def max_length
+      [old, new].map(&:length).max
+    end
+
+    def self.colorize_line(line)
+      case line[0]
+      when ADDITION
+        Color::GREEN
+      when DELETION
+        Color::RED
+      else
+        Color::NONE
+      end.format(line)
+    end
+    private_class_method :colorize_line
+
+  end # Diff
+end # Unparser
diff --git a/lib/unparser/dsl.rb b/lib/unparser/dsl.rb
index 77eb220..48b2927 100644
--- a/lib/unparser/dsl.rb
+++ b/lib/unparser/dsl.rb
@@ -6,14 +6,6 @@ module Unparser
 
   private
 
-    # Define remaining children
-    #
-    # @param [Enumerable<Symbol>] names
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
     def define_remaining_children(names)
       range = names.length..-1
       define_method(:remaining_children) do
@@ -22,15 +14,6 @@ module Unparser
       private :remaining_children
     end
 
-    # Define named child
-    #
-    # @param [Symbol] name
-    # @param [Fixnum] index
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
     def define_child(name, index)
       define_method(name) do
         children.at(index)
@@ -38,15 +21,6 @@ module Unparser
       private name
     end
 
-    # Define a group of children
-    #
-    # @param [Symbol] name
-    # @param [Range] range
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
     def define_group(name, range)
       define_method(name) do
         children[range]
@@ -55,12 +29,6 @@ module Unparser
       memoize(name)
     end
 
-    # Create name helpers
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
     def children(*names)
       define_remaining_children(names)
 
diff --git a/lib/unparser/either.rb b/lib/unparser/either.rb
new file mode 100644
index 0000000..924f731
--- /dev/null
+++ b/lib/unparser/either.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+module Unparser
+  module RequireBlock
+
+  private
+
+    # Raise error unless block is provided
+    #
+    # @raise [MissingBlockError]
+    #   if no block is given
+    #
+    # @return [self]
+    def require_block
+      fail LocalJumpError unless block_given?
+
+      self
+    end
+  end # RequireBLock
+
+  class Either
+    include(
+      Adamantium,
+      Concord.new(:value),
+      RequireBlock
+    )
+
+    # Execute block and wrap error in left
+    #
+    # @param [Class<Exception>] exception
+    #
+    # @return [Either<Exception, Object>]
+    def self.wrap_error(*exceptions)
+      Right.new(yield)
+    rescue *exceptions => error
+      Left.new(error)
+    end
+
+    # Test for left constructor
+    #
+    # @return [Boolean]
+    def left?
+      instance_of?(Left)
+    end
+
+    # Test for right constructor
+    #
+    # @return [Boolean]
+    def right?
+      instance_of?(Right)
+    end
+
+    class Left < self
+      # Evaluate functor block
+      #
+      # @return [Either::Left<Object>]
+      def fmap(&block)
+        require_block(&block)
+      end
+
+      # Evaluate applicative block
+      #
+      # @return [Either::Left<Object>]
+      def bind(&block)
+        require_block(&block)
+      end
+
+      # Unwrap value from left
+      #
+      # @return [Object]
+      def from_left
+        value
+      end
+
+      # Unwrap value from right
+      #
+      # @return [Object]
+      #
+      def from_right
+        if block_given?
+          yield(value)
+        else
+          fail "Expected right value, got #{inspect}"
+        end
+      end
+
+      # Map over left value
+      #
+      # @return [Either::Right<Object>]
+      def lmap
+        Left.new(yield(value))
+      end
+
+      # Evaluate left side of branch
+      #
+      # @param [#call] left
+      # @param [#call] _right
+      def either(left, _right)
+        left.call(value)
+      end
+    end # Left
+
+    class Right < self
+      # Evaluate functor block
+      #
+      # @return [Either::Right<Object>]
+      def fmap
+        Right.new(yield(value))
+      end
+
+      # Evaluate applicative block
+      #
+      # @return [Either<Object>]
+      def bind
+        yield(value)
+      end
+
+      # Unwrap value from left
+      #
+      # @return [Object]
+      #
+      def from_left
+        if block_given?
+          yield(value)
+        else
+          fail "Expected left value, got #{inspect}"
+        end
+      end
+
+      # Unwrap value from right
+      #
+      # @return [Object]
+      def from_right
+        value
+      end
+
+      # Map over left value
+      #
+      # @return [Either::Right<Object>]
+      def lmap(&block)
+        require_block(&block)
+      end
+
+      # Evaluate right side of branch
+      #
+      # @param [#call] _left
+      # @param [#call] right
+      def either(_left, right)
+        right.call(value)
+      end
+    end # Right
+  end # Either
+end # Unparser
diff --git a/lib/unparser/emitter.rb b/lib/unparser/emitter.rb
index 8aa9c3c..aedc517 100644
--- a/lib/unparser/emitter.rb
+++ b/lib/unparser/emitter.rb
@@ -1,36 +1,23 @@
 # frozen_string_literal: true
 
 module Unparser
+  UnknownNodeError = Class.new(ArgumentError)
 
   # Emitter base class
-  #
-  # buggy, argument values are sends to self
-  #
-  # ignore :reek:TooManyMethods
   class Emitter
-    include Adamantium::Flat, AbstractType, Constants, NodeHelpers
-    include Concord.new(:node, :parent)
-    extend DSL
+    include Adamantium, AbstractType, Constants, Generation, NodeHelpers
+    include Anima.new(:buffer, :comments, :node, :local_variable_scope)
 
-    # Registry for node emitters
-    REGISTRY = {} # rubocop:disable MutableConstant
+    public :node
 
-    NOINDENT = %i[rescue ensure].to_set.freeze
+    extend DSL
 
-    module Unterminated
-      def terminated?
-        false
-      end
-    end
+    # Registry for node emitters
+    REGISTRY = {} # rubocop:disable Style/MutableConstant
 
-    module Terminated
-      def terminated?
-        true
-      end
-    end
+    NO_INDENT = %i[ensure rescue].freeze
 
     module LocalVariableRoot
-
       # Return local variable root
       #
       # @return [Parser::AST::Node]
@@ -46,33 +33,8 @@ module Unparser
           memoize :local_variable_scope
         end
       end
-
     end # LocalVariableRoot
 
-    # Return local variable root
-    #
-    # @return [Parser::AST::Node]
-    #
-    # @api private
-    #
-    def local_variable_scope
-      parent.local_variable_scope
-    end
-
-    # Return assigned lvars
-    #
-    # @return [Array<Symbol>]
-    #
-    # @api private
-    #
-    abstract_method :local_variables
-
-    # Return node type
-    #
-    # @return [Symbol]
-    #
-    # @api private
-    #
     def node_type
       node.type
     end
@@ -87,25 +49,16 @@ module Unparser
     #
     def self.handle(*types)
       types.each do |type|
+        fail "Handler for type: #{type} already registered" if REGISTRY.key?(type)
+
         REGISTRY[type] = self
       end
     end
     private_class_method :handle
 
-    # Trigger write to buffer
-    #
-    # @return [self]
-    #
-    # @api private
-    #
-    def write_to_buffer
-      emit_comments_before if buffer.fresh_line?
+    def emit_mlhs
       dispatch
-      comments.consume(node)
-      emit_eof_comments if parent.is_a?(Root)
-      self
     end
-    memoize :write_to_buffer
 
     # Return emitter
     #
@@ -113,384 +66,30 @@ module Unparser
     #
     # @api private
     #
-    def self.emitter(node, parent)
+    # rubocop:disable Metrics/ParameterLists
+    def self.emitter(buffer:, comments:, node:, local_variable_scope:)
       type = node.type
-      klass = REGISTRY.fetch(type) do
-        raise ArgumentError, "No emitter for node: #{type.inspect}"
-      end
-      klass.new(node, parent)
-    end
-
-    # Dispatch node
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    abstract_method :dispatch
-
-    # Test if node is emitted as terminated expression
-    #
-    # @return [Boolean]
-    #
-    # @api private
-    #
-    abstract_method :terminated?
 
-  protected
-
-    # Return buffer
-    #
-    # @return [Buffer] buffer
-    #
-    # @api private
-    #
-    def buffer
-      parent.buffer
-    end
-    memoize :buffer, freezer: :noop
-
-    # Return comments
-    #
-    # @return [Comments] comments
-    #
-    # @api private
-    #
-    def comments
-      parent.comments
-    end
-    memoize :comments, freezer: :noop
-
-  private
-
-    # Emit contents of block within parentheses
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def parentheses(open = M_PO, close = M_PC)
-      write(open)
-      yield
-      write(close)
-    end
-
-    # Visit node
-    #
-    # @param [Parser::AST::Node] node
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def visit_plain(node)
-      emitter = emitter(node)
-      emitter.write_to_buffer
-    end
-
-    # Visit ambiguous node
-    #
-    # @param [Parser::AST::Node] node
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def visit(node)
-      emitter = emitter(node)
-      conditional_parentheses(!emitter.terminated?) do
-        emitter.write_to_buffer
-      end
-    end
-
-    # Visit within parentheses
-    #
-    # @param [Parser::AST::Node] node
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def visit_parentheses(node, *arguments)
-      parentheses(*arguments) do
-        visit_plain(node)
-      end
-    end
-
-    # Call block in optional parentheses
-    #
-    # @param [true, false] flag
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    # ignore :reek:ControlParameter
-    def conditional_parentheses(flag)
-      if flag
-        parentheses { yield }
-      else
-        yield
-      end
-    end
-
-    # Return emitter for node
-    #
-    # @param [Parser::AST::Node] node
-    #
-    # @return [Emitter]
-    #
-    # @api private
-    #
-    def emitter(node)
-      self.class.emitter(node, self)
-    end
-
-    # Emit delimited body
-    #
-    # @param [Enumerable<Parser::AST::Node>] nodes
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def delimited_plain(nodes)
-      delimited(nodes, &method(:visit_plain))
-    end
-
-    # Emit delimited body
-    #
-    # @param [Enumerable<Parser::AST::Node>] nodes
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def delimited(nodes, &block)
-      return if nodes.empty?
-
-      block ||= method(:visit)
-      head, *tail = nodes
-      block.call(head)
-      tail.each do |node|
-        write(DEFAULT_DELIMITER)
-        block.call(node)
-      end
-    end
-
-    # Return children of node
-    #
-    # @return [Array<Parser::AST::Node>]
-    #
-    # @api private
-    #
-    def children
-      node.children
-    end
-
-    # Write newline
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def nl
-      emit_eol_comments
-      buffer.nl
-    end
-
-    # Write comments that appeared before source_part in the source
-    #
-    # @param [Symbol] source_part
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def emit_comments_before(source_part = :expression)
-      comments_before = comments.take_before(node, source_part)
-      return if comments_before.empty?
-
-      emit_comments(comments_before)
-      buffer.nl
-    end
-
-    # Write end-of-line comments
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def emit_eol_comments
-      comments.take_eol_comments.each do |comment|
-        write(WS, comment.text)
-      end
-    end
-
-    # Write end-of-file comments
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def emit_eof_comments
-      emit_eol_comments
-      comments_left = comments.take_all
-      return if comments_left.empty?
-
-      buffer.nl
-      emit_comments(comments_left)
-    end
-
-    # Write each comment to a separate line
-    #
-    # @param [Array] comments
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def emit_comments(comments)
-      max = comments.size - 1
-      comments.each_with_index do |comment, index|
-        if comment.type.equal?(:document)
-          buffer.append_without_prefix(comment.text.chomp)
-        else
-          write(comment.text)
-        end
-        buffer.nl if index < max
-      end
-    end
-
-    # Write strings into buffer
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def write(*strings)
-      strings.each do |string|
-        buffer.append(string)
-      end
-    end
-
-    # Write end keyword
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def k_end
-      buffer.indent
-      emit_comments_before(:end)
-      buffer.unindent
-      write(K_END)
-    end
-
-    # Return first child
-    #
-    # @return [Parser::AST::Node]
-    #   if present
-    #
-    # @return [nil]
-    #   otherwise
-    #
-    # @api private
-    #
-    def first_child
-      children.first
-    end
-
-    # Write whitespace
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def ws
-      write(WS)
-    end
-
-    # Call emit contents of block indented
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    # False positive:
-    #
-    def indented
-      buffer = buffer()
-      buffer.indent
-      nl
-      yield
-      nl
-      buffer.unindent
-    end
-
-    # Emit non nil body
-    #
-    # @param [Parser::AST::Node] body
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    # rubocop:disable MethodCallWithoutArgsParentheses
-    def emit_body(body = body())
-      unless body
-        buffer.indent
-        nl
-        buffer.unindent
-        return
-      end
-      visit_indented(body)
-    end
-    # rubocop:enable MethodCallWithoutArgsParentheses
-
-    # Visit indented node
-    #
-    # @param [Parser::AST::Node] node
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def visit_indented(node)
-      if NOINDENT.include?(node.type)
-        visit_plain(node)
-      else
-        indented { visit_plain(node) }
+      klass = REGISTRY.fetch(type) do
+        fail UnknownNodeError, "Unknown node type: #{type.inspect}"
       end
-    end
 
-    # Return parent type
-    #
-    # @return [Symbol]
-    #   if parent is present
-    #
-    # @return [nil]
-    #   otherwise
-    #
-    # @api private
-    #
-    def parent_type
-      parent.node_type
+      klass.new(
+        buffer:               buffer,
+        comments:             comments,
+        local_variable_scope: local_variable_scope,
+        node:                 node
+      )
     end
+    # rubocop:enable Metrics/ParameterLists
 
-    # Delegate to emitter
-    #
-    # @param [Class:Emitter] emitter
+    # Dispatch node write as statement
     #
     # @return [undefined]
     #
     # @api private
     #
-    # rubocop:disable MethodCallWithoutArgsParentheses
-    def run(emitter, node = node())
-      emitter.new(node, self).write_to_buffer
-    end
-    # rubocop:enable MethodCallWithoutArgsParentheses
+    abstract_method :dispatch
 
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/alias.rb b/lib/unparser/emitter/alias.rb
index f622920..9b9189d 100644
--- a/lib/unparser/emitter/alias.rb
+++ b/lib/unparser/emitter/alias.rb
@@ -11,16 +11,10 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_ALIAS, WS)
+        write('alias ')
         visit(target)
-        write(WS)
+        ws
         visit(source)
       end
 
diff --git a/lib/unparser/emitter/args.rb b/lib/unparser/emitter/args.rb
new file mode 100644
index 0000000..5bc34d7
--- /dev/null
+++ b/lib/unparser/emitter/args.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Arguments emitter
+    class Args < self
+      def emit_block_arguments
+        delimited(normal_arguments)
+
+        write(',') if normal_arguments.one? && n_arg?(normal_arguments.first)
+
+        emit_shadowargs
+      end
+
+      def emit_def_arguments
+        delimited(normal_arguments)
+      end
+
+      def emit_lambda_arguments
+        delimited(normal_arguments)
+        emit_shadowargs
+      end
+
+    private
+
+      def emit_shadowargs
+        return if shadowargs.empty?
+
+        write('; ')
+        delimited(shadowargs)
+      end
+
+      def normal_arguments
+        children.reject(&method(:n_shadowarg?))
+      end
+      memoize :normal_arguments
+
+      def shadowargs
+        children.select(&method(:n_shadowarg?))
+      end
+      memoize :shadowargs
+
+    end # Arguments
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/argument.rb b/lib/unparser/emitter/argument.rb
index 260af9b..0a47bfd 100644
--- a/lib/unparser/emitter/argument.rb
+++ b/lib/unparser/emitter/argument.rb
@@ -2,121 +2,21 @@
 
 module Unparser
   class Emitter
-
-    # Arg expr (pattern args) emitter
-    class ArgExpr < self
-
-      handle :arg_expr
-
-      children :body
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        visit_parentheses(body)
-      end
-    end # ArgExpr
-
-    # Arguments emitter
-    class Arguments < self
-      include Terminated
-
-      handle :args
-
-      SHADOWARGS = ->(node) { node.type.equal?(:shadowarg) }.freeze
-      ARG        = ->(node) { node.type.equal?(:arg) }.freeze
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        delimited(normal_arguments)
-
-        write(', ') if procarg_disambiguator?
-
-        return if shadowargs.empty?
-
-        write('; ')
-        delimited(shadowargs)
-      end
-
-      # Test for procarg_disambiguator
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def procarg_disambiguator?
-        regular_block? && normal_arguments.all?(&ARG) && normal_arguments.one?
-      end
-
-      # Test for regular block
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def regular_block?
-        parent_type.equal?(:block) && !parent.node.children.first.type.equal?(:lambda)
-      end
-
-      # Return normal arguments
-      #
-      # @return [Enumerable<Parser::AST::Node>]
-      #
-      # @api private
-      #
-      def normal_arguments
-        children.reject(&SHADOWARGS)
-      end
-      memoize :normal_arguments
-
-      # Return shadow args
-      #
-      # @return [Enumerable<Parser::AST::Node>]
-      #
-      # @api private
-      #
-      def shadowargs
-        children.select(&SHADOWARGS)
-      end
-      memoize :shadowargs
-
-    end # Arguments
-
-    # Emitter for block and kwrestarg arguments
-    class Morearg < self
-      include Terminated
-
+    # Emitter for forwarding arguments
+    class ForwardArg < self
       MAP = {
-        blockarg:  T_AMP,
-        kwrestarg: T_DSPLAT
+        blockarg:            '&',
+        forwarded_kwrestarg: '**',
+        forwarded_restarg:   '*',
+        kwrestarg:           '**'
       }.freeze
 
-      handle :blockarg
-      handle :kwrestarg
+      handle(*MAP.keys)
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write(MAP.fetch(node_type), name.to_s)
       end
@@ -125,44 +25,28 @@ module Unparser
 
     # Optional argument emitter
     class Optarg < self
-      include Terminated
-
       handle :optarg
 
       children :name, :value
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(name.to_s, WS, T_ASN, WS)
+        write(name.to_s, ' = ')
         visit(value)
       end
     end
 
     # Optional keyword argument emitter
     class KeywordOptional < self
-      include Terminated
-
       handle :kwoptarg
 
       children :name, :value
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(name.to_s, T_COLON, WS)
+        write(name.to_s, ': ')
         visit(value)
       end
 
@@ -170,64 +54,40 @@ module Unparser
 
     # Keyword argument emitter
     class Kwarg < self
-      include Terminated
-
       handle :kwarg
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(name.to_s, T_COLON)
+        write(name.to_s, ':')
       end
 
     end # Restarg
 
     # Rest argument emitter
     class Restarg < self
-      include Terminated
-
       handle :restarg
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(T_SPLAT, name.to_s)
+        write('*', name.to_s)
       end
 
     end # Restarg
 
     # Argument emitter
     class Argument < self
-      include Terminated
-
       handle :arg, :shadowarg
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write(name.to_s)
       end
@@ -236,20 +96,12 @@ module Unparser
 
     # Progarg emitter
     class Procarg < self
-      include Terminated
-
       handle :procarg0
 
       PARENS = %i[restarg mlhs].freeze
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         if needs_parens?
           parentheses do
@@ -269,23 +121,15 @@ module Unparser
 
     # Block pass node emitter
     class BlockPass < self
-      include Terminated
-
       handle :block_pass
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(T_AMP)
-        visit(name)
+        write('&')
+        visit(name) if name
       end
 
     end # BlockPass
diff --git a/lib/unparser/emitter/array.rb b/lib/unparser/emitter/array.rb
new file mode 100644
index 0000000..6f710cb
--- /dev/null
+++ b/lib/unparser/emitter/array.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Array literal emitter
+    class Array < self
+      handle :array
+
+      def emit_heredoc_reminders
+        emitters.each(&:emit_heredoc_reminders)
+      end
+
+    private
+
+      def dispatch
+        parentheses('[', ']') do
+          delimited(emitters, &:write_to_buffer)
+        end
+      end
+
+      def emitters
+        children.map(&method(:emitter))
+      end
+      memoize :emitters
+    end # Array
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/array_pattern.rb b/lib/unparser/emitter/array_pattern.rb
new file mode 100644
index 0000000..ae1833d
--- /dev/null
+++ b/lib/unparser/emitter/array_pattern.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for array patterns
+    class ArrayPattern < self
+
+      handle :array_pattern
+      handle :array_pattern_with_tail
+
+    private
+
+      def dispatch
+        write('[')
+        delimited(children, &method(:emit_member))
+        write(', ') if node_type.equal?(:array_pattern_with_tail)
+        write(']')
+      end
+
+      def emit_member(node)
+        if n_match_rest?(node)
+          writer_with(MatchRest, node).emit_array_pattern
+        else
+          visit(node)
+        end
+      end
+    end # Pin
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/assignment.rb b/lib/unparser/emitter/assignment.rb
index 508648d..d907c87 100644
--- a/lib/unparser/emitter/assignment.rb
+++ b/lib/unparser/emitter/assignment.rb
@@ -5,163 +5,72 @@ module Unparser
 
     # Base class for assignment emitters
     class Assignment < self
+      BINARY_OPERATOR = %i[and or].freeze
+
+      def symbol_name
+        true
+      end
+
+      def emit_heredoc_reminders
+        return unless right
+
+        emitter(right).emit_heredoc_reminders
+      end
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         emit_left
         emit_right
       end
 
-      # Single assignment emitter
-      class Single < self
-
-        # Test for terminated emit
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def terminated?
-          right.nil?
-        end
-
-      private
+      def emit_right
+        return unless right
 
-        # Emit right
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_right
-          return unless right
+        write(' = ')
 
-          write(WS, T_ASN, WS)
+        if BINARY_OPERATOR.include?(right.type)
+          writer_with(Writer::Binary, right).emit_operator
+        else
           visit(right)
         end
+      end
 
-        abstract_method :emit_left
-
-        # Variable assignment emitter
-        class Variable < self
-
-          handle :lvasgn, :ivasgn, :cvasgn, :gvasgn
-
-          children :name, :right
-
-        private
-
-          # Emit left
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def emit_left
-            write(name.to_s)
-          end
-
-        end # Variable
-
-        # Constant assignment emitter
-        class Constant < self
-
-          handle :casgn
-
-          children :base, :name, :right
-
-        private
-
-          # Emit left
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def emit_left
-            if base
-              visit(base)
-              write(T_DCL) if base.type != :cbase
-            end
-            write(name.to_s)
-          end
+      abstract_method :emit_left
 
-        end # Constant
-      end # Single
+      # Variable assignment emitter
+      class Variable < self
 
-      # Multiple assignment
-      class Multiple < self
-        include Unterminated
+        handle :lvasgn, :ivasgn, :cvasgn, :gvasgn
 
-        handle :masgn
+        children :name, :right
 
       private
 
-        # Emit left
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def emit_left
-          visit_plain(first_child)
+          write(name.to_s)
         end
 
-        # Emit right
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_right
-          write(WS, T_ASN, WS)
-          visit(children.last)
-        end
+      end # Variable
 
-      end # Multiple
+      # Constant assignment emitter
+      class Constant < self
 
-      # Emitter for multiple assignment left hand side
-      class MLHS < Emitter
-        include Unterminated
+        handle :casgn
 
-        handle :mlhs
+        children :base, :name, :right
 
       private
 
-        NO_COMMA = %i[splat restarg].to_set.freeze
-        PARENT_MLHS = %i[mlhs masgn].freeze
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          delimited(children)
-
-          write(',') if children.one? && mlhs?
-        end
-
-        # Test for mlhs context
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def mlhs?
-          !NO_COMMA.include?(first_child.type) && PARENT_MLHS.include?(parent_type)
+        def emit_left
+          if base
+            visit(base)
+            write('::') unless n_cbase?(base)
+          end
+          write(name.to_s)
         end
 
-      end # MLHS
-
+      end # Constant
     end # Assignment
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/begin.rb b/lib/unparser/emitter/begin.rb
index 0749668..8767dbe 100644
--- a/lib/unparser/emitter/begin.rb
+++ b/lib/unparser/emitter/begin.rb
@@ -5,97 +5,22 @@ module Unparser
 
     # Emitter for begin nodes
     class Begin < self
-
+      handle :begin
       children :body
 
-    private
-
-      # Emit inner nodes
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_inner
-        children.each_with_index do |child, index|
-          visit_plain(child)
-          write(NL) if index < children.length - 1
+      def emit_heredoc_reminders
+        children.each do |child|
+          emitter(child).emit_heredoc_reminders
         end
       end
 
-      # Emitter for implicit begins
-      class Implicit < self
-
-        handle :begin
-
-        # Test if begin is terminated
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def terminated?
-          children.empty?
-        end
-
-        TERMINATING_PARENT = %i[root interpolated dyn_str_body].to_set.freeze
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          if terminated? && !TERMINATING_PARENT.include?(parent_type)
-            write('()')
-          else
-            emit_inner
-          end
-        end
-
-      end # Implicit
-
-      # Emitter for explicit begins
-      class Explicit < self
-        include Terminated
-
-        handle :kwbegin
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          write(K_BEGIN)
-          emit_body
-          k_end
-        end
+    private
 
-        # Emit body
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_body
-          if body.nil?
-            nl
-          elsif NOINDENT.include?(body.type)
-            emit_inner
-          else
-            indented { emit_inner }
-          end
+      def dispatch
+        parentheses do
+          delimited(children, '; ')
         end
-
-      end # Explicit
-
+      end
     end # Begin
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/binary.rb b/lib/unparser/emitter/binary.rb
index 26025dc..8ad482e 100644
--- a/lib/unparser/emitter/binary.rb
+++ b/lib/unparser/emitter/binary.rb
@@ -2,33 +2,20 @@
 
 module Unparser
   class Emitter
-    # Base class for binary emitters
+    # Non send binary operator / keyword emitter
     class Binary < self
-      include Unterminated
-
-      children :left, :right
-
-      MAP = {
-        or:  T_OR,
-        and: T_AND
-      }.freeze
-
-      handle(*MAP.keys)
+      handle :and, :or
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        visit(left)
-        write(WS, MAP.fetch(node.type), WS)
-        visit(right)
+        writer.dispatch
       end
 
+      def writer
+        writer_with(Writer::Binary, node)
+      end
+      memoize :writer
     end # Binary
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/block.rb b/lib/unparser/emitter/block.rb
index f360ada..15e9367 100644
--- a/lib/unparser/emitter/block.rb
+++ b/lib/unparser/emitter/block.rb
@@ -4,66 +4,82 @@ module Unparser
   class Emitter
 
     # Block emitter
-    #
-    # ignore :reek:RepeatedConditional
     class Block < self
-      include Terminated
-
-      handle :block
+      handle :block, :numblock
 
       children :target, :arguments, :body
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         emit_target
-        write(WS, K_DO)
-        emit_block_arguments unless stabby_lambda?
-        emit_body
-        k_end
+        ws
+        write_open
+        emit_block_arguments unless n_lambda?(target)
+        target_writer.emit_heredoc_reminders if n_send?(target)
+        emit_optional_body_ensure_rescue(body)
+        write_close
       end
 
-      # Emit target
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_target
-        visit(target)
+      def need_do?
+        body && (n_rescue?(body) || n_ensure?(body))
+      end
+
+      def write_open
+        if need_do?
+          write('do')
+        else
+          write('{')
+        end
+      end
 
-        if stabby_lambda?
-          parentheses { visit(arguments) }
+      def write_close
+        if need_do?
+          k_end
+        else
+          write('}')
         end
       end
 
-      # Test if we are emitting a stabby lambda
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def stabby_lambda?
-        target.type.equal?(:lambda)
+      def target_writer
+        writer_with(Writer::Send::Regular, target)
+      end
+      memoize :target_writer
+
+      def emit_target
+        case target.type
+        when :send
+          emit_send_target
+        when :lambda
+          visit(target)
+          emit_lambda_arguments unless node.type.equal?(:numblock)
+        else
+          visit(target)
+        end
+      end
+
+      def emit_send_target
+        target_writer.emit_receiver
+        target_writer.emit_selector
+        target_writer.emit_arguments_without_heredoc_body
+      end
+
+      def emit_lambda_arguments
+        parentheses { writer_with(Args, arguments).emit_lambda_arguments }
+      end
+
+      def numblock?
+        node.type.equal?(:numblock)
       end
 
-      # Emit arguments
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_block_arguments
-        return if arguments.children.empty?
+        return if numblock? || arguments.children.empty?
 
         ws
-        visit_parentheses(arguments, T_PIPE, T_PIPE)
+
+        parentheses('|', '|') do
+          writer_with(Args, arguments).emit_block_arguments
+        end
       end
 
     end # Block
diff --git a/lib/unparser/emitter/case.rb b/lib/unparser/emitter/case.rb
index 0bc0949..6d0a511 100644
--- a/lib/unparser/emitter/case.rb
+++ b/lib/unparser/emitter/case.rb
@@ -4,8 +4,6 @@ module Unparser
   class Emitter
     # Emitter for case nodes
     class Case < self
-      include Terminated
-
       handle :case
 
       children :condition
@@ -13,89 +11,49 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_CASE)
+        write('case')
         emit_condition
         emit_whens
         emit_else
         k_end
       end
 
-      # Emit else
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_else
         else_branch = children.last
         return unless else_branch
 
-        write(K_ELSE)
-        visit_indented(else_branch)
+        write('else')
+        emit_body(else_branch)
       end
 
-      # Emit whens
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_whens
         nl
         whens.each(&method(:visit))
       end
 
-      # Emit condition
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_condition
         return unless condition
 
-        write(WS)
+        ws
         visit(condition)
       end
-
     end # Case
 
     # Emitter for when nodes
     class When < self
-      include Terminated
-
       handle :when
 
       define_group :captures, 0..-2
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_WHEN, WS)
+        write('when ')
         emit_captures
-        body = children.last
-        emit_body(body)
+        emit_optional_body(children.last)
       end
 
-      # Emit captures
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_captures
         delimited(captures)
       end
diff --git a/lib/unparser/emitter/case_guard.rb b/lib/unparser/emitter/case_guard.rb
new file mode 100644
index 0000000..b507a9e
--- /dev/null
+++ b/lib/unparser/emitter/case_guard.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for case guards
+    class CaseGuard < self
+
+      handle :if_guard, :unless_guard
+
+      MAP = {
+        if_guard:     'if',
+        unless_guard: 'unless'
+      }.freeze
+
+      children :condition
+
+    private
+
+      def dispatch
+        write(MAP.fetch(node_type))
+        ws
+        visit(condition)
+      end
+
+    end # UnlessGuard
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/case_match.rb b/lib/unparser/emitter/case_match.rb
new file mode 100644
index 0000000..5714f2e
--- /dev/null
+++ b/lib/unparser/emitter/case_match.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for case matches
+    class CaseMatch < self
+
+      handle :case_match
+
+      children :target
+
+      define_group :patterns, 1..-2
+
+    private
+
+      def else_branch
+        children.last
+      end
+
+      def dispatch
+        write('case ')
+        visit(target)
+        nl
+        patterns.each(&method(:visit))
+        nl unless buffer.fresh_line?
+        emit_else_branch
+        k_end
+      end
+
+      def emit_else_branch
+        if else_branch
+          write('else')
+          emit_body(else_branch) unless n_empty_else?(else_branch)
+          nl unless buffer.fresh_line?
+        end
+      end
+
+    end # CaseMatch
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/cbase.rb b/lib/unparser/emitter/cbase.rb
index 0ca9f22..219c87a 100644
--- a/lib/unparser/emitter/cbase.rb
+++ b/lib/unparser/emitter/cbase.rb
@@ -4,8 +4,6 @@ module Unparser
   class Emitter
     # Emitter for toplevel constant reference nodes
     class CBase < self
-      include Terminated
-
       handle :cbase
 
     private
@@ -17,7 +15,7 @@ module Unparser
       # @api private
       #
       def dispatch
-        write(T_DCL)
+        write('::')
       end
 
     end # CBase
diff --git a/lib/unparser/emitter/class.rb b/lib/unparser/emitter/class.rb
index d92f465..ccdd26b 100644
--- a/lib/unparser/emitter/class.rb
+++ b/lib/unparser/emitter/class.rb
@@ -4,7 +4,7 @@ module Unparser
   class Emitter
     # Emitter for class nodes
     class Class < self
-      include LocalVariableRoot, Terminated
+      include LocalVariableRoot
 
       handle :class
 
@@ -12,30 +12,18 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_CLASS, WS)
+        write('class ')
         visit(name)
         emit_superclass
-        emit_body
+        emit_optional_body(body)
         k_end
       end
 
-      # Emit superclass
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_superclass
         return unless superclass
 
-        write(WS, T_LT, WS)
+        write(' < ')
         visit(superclass)
       end
 
@@ -43,24 +31,16 @@ module Unparser
 
     # Emitter for sclass nodes
     class SClass < self
-      include Terminated
-
       handle :sclass
 
       children :object, :body
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_CLASS, WS, T_DLT, WS)
+        write('class << ')
         visit(object)
-        emit_body
+        emit_optional_body(body)
         k_end
       end
 
diff --git a/lib/unparser/emitter/const_pattern.rb b/lib/unparser/emitter/const_pattern.rb
new file mode 100644
index 0000000..f5fcce4
--- /dev/null
+++ b/lib/unparser/emitter/const_pattern.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for const pattern node
+    class ConstPattern < self
+
+      handle :const_pattern
+
+      children :const, :pattern
+
+    private
+
+      def dispatch
+        visit(const)
+        if n_hash_pattern?(pattern)
+          emitter(pattern).emit_const_pattern
+        else
+          visit(pattern)
+        end
+      end
+    end # ConstPattern
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/def.rb b/lib/unparser/emitter/def.rb
index a10b37c..7d75ee1 100644
--- a/lib/unparser/emitter/def.rb
+++ b/lib/unparser/emitter/def.rb
@@ -4,70 +4,40 @@ module Unparser
   class Emitter
     # Emitter for def node
     class Def < self
-      include LocalVariableRoot, Terminated
+      include LocalVariableRoot
 
     private
 
-      # Emit name
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       abstract_method :emit_name
       private :emit_name
 
-      # Return body node
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
       abstract_method :body
       private :body
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_DEF, WS)
+        write('def ')
         emit_name
-        comments.consume(node, :name)
         emit_arguments
-        emit_body
+        emit_optional_body_ensure_rescue(body)
         k_end
       end
 
-      # Emit arguments
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_arguments
         return if arguments.children.empty?
 
-        visit_parentheses(arguments)
+        parentheses do
+          writer_with(Args, arguments).emit_def_arguments
+        end
       end
 
       # Instance def emitter
       class Instance < self
-
         handle :def
 
         children :name, :arguments, :body
 
       private
 
-        # Emit name
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def emit_name
           write(name.to_s)
         end
@@ -83,25 +53,13 @@ module Unparser
 
       private
 
-        # Return mame
-        #
-        # @return [String]
-        #
-        # @api private
-        #
         def emit_name
           conditional_parentheses(!subject_without_parens?) do
             visit(subject)
           end
-          write(T_DOT, name.to_s)
+          write('.', name.to_s)
         end
 
-        # Test if subject needs parentheses
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
         def subject_without_parens?
           case subject.type
           when :self
@@ -111,8 +69,6 @@ module Unparser
           when :send
             receiver, _selector, *arguments = *subject
             !receiver && arguments.empty?
-          else
-            false
           end
         end
 
diff --git a/lib/unparser/emitter/defined.rb b/lib/unparser/emitter/defined.rb
index ffa6115..c848741 100644
--- a/lib/unparser/emitter/defined.rb
+++ b/lib/unparser/emitter/defined.rb
@@ -4,25 +4,15 @@ module Unparser
   class Emitter
     # Emitter for defined? nodes
     class Defined < self
-      include Terminated
-
       handle :defined?
 
       children :subject
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_DEFINED)
-        parentheses do
-          visit(subject)
-        end
+        write('defined?')
+        parentheses { visit(subject) }
       end
 
     end # Defined
diff --git a/lib/unparser/emitter/dstr.rb b/lib/unparser/emitter/dstr.rb
new file mode 100644
index 0000000..0095787
--- /dev/null
+++ b/lib/unparser/emitter/dstr.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Dynamic string emitter
+    class DStr < self
+
+      handle :dstr
+
+      def emit_heredoc_reminders
+        writer_with(Writer::DynamicString, node).emit_heredoc_reminder
+      end
+
+    private
+
+      def dispatch
+        writer_with(Writer::DynamicString, node).dispatch
+      end
+
+    end # DStr
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/dsym.rb b/lib/unparser/emitter/dsym.rb
new file mode 100644
index 0000000..f8ee4c8
--- /dev/null
+++ b/lib/unparser/emitter/dsym.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Dynamic symbol literal emitter
+    class DSym < self
+      handle :dsym
+
+    private
+
+      def dispatch
+        write(':"')
+        children.each do |child|
+          case child.type
+          when :str
+            emit_str_child(child)
+          when :begin
+            emit_begin_child(child)
+          end
+        end
+        write('"')
+      end
+
+      def emit_str_child(value)
+        string = value.children.first
+        if string.end_with?("\n")
+          write(string.inspect[1..-4])
+          nl
+        else
+          write(string.inspect[1..-2])
+        end
+      end
+
+      def emit_begin_child(component)
+        write('#{')
+        visit(unwrap_single_begin(component))
+        write('}')
+      end
+    end # DSym
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/empty.rb b/lib/unparser/emitter/empty.rb
deleted file mode 100644
index 2837e97..0000000
--- a/lib/unparser/emitter/empty.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-
-    # Emitter for artifical empty node
-    class Empty < self
-
-      handle :empty
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch; end
-
-    end
-  end
-end # Unparser
diff --git a/lib/unparser/emitter/ensure.rb b/lib/unparser/emitter/ensure.rb
deleted file mode 100644
index 320937b..0000000
--- a/lib/unparser/emitter/ensure.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-
-    # Emitter for ensure nodes
-    class Ensure < self
-
-      handle :ensure
-
-      children :body, :ensure_body
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        if body
-          visit_indented(body)
-        else
-          nl
-        end
-        write(K_ENSURE)
-        if ensure_body
-          visit_indented(ensure_body)
-        else
-          nl
-        end
-      end
-
-    end # Ensure
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/find_pattern.rb b/lib/unparser/emitter/find_pattern.rb
new file mode 100644
index 0000000..751519b
--- /dev/null
+++ b/lib/unparser/emitter/find_pattern.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class FindPattern < self
+      handle :find_pattern
+
+    private
+
+      def dispatch
+        write('[')
+        delimited(children)
+        write(']')
+      end
+    end # FindPattern
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/flipflop.rb b/lib/unparser/emitter/flipflop.rb
index 07da6f2..d7fd717 100644
--- a/lib/unparser/emitter/flipflop.rb
+++ b/lib/unparser/emitter/flipflop.rb
@@ -4,12 +4,19 @@ module Unparser
   class Emitter
     # Emitter for flip flops
     class FlipFlop < self
-      include Unterminated
-
-      MAP = IceNine.deep_freeze(
+      MAP = {
         iflipflop: '..',
         eflipflop: '...'
-      ).freeze
+      }.freeze
+
+      SYMBOLS = {
+        eflipflop: :tDOT3,
+        iflipflop: :tDOT2
+      }.freeze
+
+      def symbol_name
+        true
+      end
 
       handle(*MAP.keys)
 
@@ -17,12 +24,6 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         visit(left)
         write(MAP.fetch(node.type))
diff --git a/lib/unparser/emitter/float.rb b/lib/unparser/emitter/float.rb
new file mode 100644
index 0000000..e03c934
--- /dev/null
+++ b/lib/unparser/emitter/float.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emiter for float literals
+    class Float < self
+      handle :float
+
+      children :value
+
+      INFINITY     = ::Float::INFINITY
+      NEG_INFINITY = -::Float::INFINITY
+
+    private
+
+      def dispatch
+        case value
+        when INFINITY
+          write('10e1000000000000000000')
+        when NEG_INFINITY
+          write('-10e1000000000000000000')
+        else
+          write(value.inspect)
+        end
+      end
+
+    end # Float
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/flow_modifier.rb b/lib/unparser/emitter/flow_modifier.rb
index 6fec151..ef58c97 100644
--- a/lib/unparser/emitter/flow_modifier.rb
+++ b/lib/unparser/emitter/flow_modifier.rb
@@ -4,77 +4,39 @@ module Unparser
   class Emitter
     # Emitter control flow modifiers
     class FlowModifier < self
-
       MAP = {
-        return: K_RETURN,
-        next:   K_NEXT,
-        break:  K_BREAK
+        return: 'return',
+        next:   'next',
+        break:  'break'
       }.freeze
 
+      private_constant(*constants(false))
+
       handle(*MAP.keys)
 
-      def terminated?
-        children.empty?
+      def emit_heredoc_reminders
+        children.each do |node|
+          emitter(node).emit_heredoc_reminders
+        end
       end
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write(MAP.fetch(node.type))
-        case children.length
-        when 0 # rubocop:disable Lint/EmptyWhen
-        when 1
-          emit_single_argument
+
+        if children.one? && n_if?(children.first)
+          ws
+          emitter(children.first).emit_ternary
         else
-          emit_arguments
+          emit_arguments unless children.empty?
         end
       end
 
-      # Emit break or return arguments
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_arguments
         ws
-        head, *tail = children
-        emit_argument(head)
-        tail.each do |node|
-          write(DEFAULT_DELIMITER)
-          emit_argument(node)
-        end
+        delimited(children)
       end
-
-      PARENS = %i[if case begin].to_set.freeze
-
-      # Emit argument
-      #
-      # @param [Parser::AST::Node] node
-      #
-      # @api private
-      #
-      def emit_argument(node)
-        visit_plain(node)
-      end
-
-      # Emit single argument
-      #
-      # @api private
-      #
-      def emit_single_argument
-        ws
-        conditional_parentheses(PARENS.include?(first_child.type)) do
-          visit_plain(first_child)
-        end
-      end
-
     end # Return
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/for.rb b/lib/unparser/emitter/for.rb
index 1e61db2..5532eb5 100644
--- a/lib/unparser/emitter/for.rb
+++ b/lib/unparser/emitter/for.rb
@@ -4,38 +4,24 @@ module Unparser
   class Emitter
     # Emitter for for nodes
     class For < self
-      include Terminated
-
       handle :for
 
       children :condition, :assignment, :body
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_FOR, WS)
+        write('for ')
         emit_condition
-        emit_body
+        emit_optional_body(body)
         k_end
       end
 
-      # Emit assignment
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_condition
-        visit_plain(condition)
-        write(WS, K_IN, WS)
+        visit(condition)
+        write(' in ')
         visit(assignment)
-        write(WS, K_DO)
+        write(' do')
       end
 
     end # For
diff --git a/lib/unparser/emitter/hash.rb b/lib/unparser/emitter/hash.rb
new file mode 100644
index 0000000..bd0d3f6
--- /dev/null
+++ b/lib/unparser/emitter/hash.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for Hash literals
+    class Hash < self
+      handle :hash
+
+      def emit_heredoc_reminders
+        children.each(&method(:emit_heredoc_reminder_member))
+      end
+
+    private
+
+      def dispatch
+        if children.empty?
+          write('{}')
+        else
+          parentheses('{', '}') do
+            write(' ')
+            emit_hash_body
+            write(' ')
+          end
+        end
+      end
+
+      def emit_heredoc_reminder_member(node)
+        emitter(node.children.last).emit_heredoc_reminders
+      end
+
+      def emit_hash_body
+        delimited(children)
+      end
+    end # Hash
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/hash_pattern.rb b/lib/unparser/emitter/hash_pattern.rb
new file mode 100644
index 0000000..c03fa91
--- /dev/null
+++ b/lib/unparser/emitter/hash_pattern.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for hash patterns
+    class HashPattern < self
+
+      handle :hash_pattern
+
+      def emit_const_pattern
+        parentheses do
+          emit_hash_body
+        end
+      end
+
+    private
+
+      def dispatch
+        parentheses('{', '}') do
+          emit_hash_body
+        end
+      end
+
+      def emit_hash_body
+        delimited(children, &method(:emit_member))
+      end
+
+      def emit_member(node)
+        case node.type
+        when :pair
+          emit_pair(node)
+        when :match_var
+          emit_match_var(node)
+        when :match_rest
+          writer_with(MatchRest, node).emit_hash_pattern
+        else
+          visit(node)
+        end
+      end
+
+      def emit_match_var(node)
+        write_symbol_body(node.children.first)
+        write(':')
+      end
+
+      def emit_pair(node)
+        key, value = node.children
+
+        if n_sym?(key)
+          write_symbol_body(key.children.first)
+        else
+          visit(s(:dstr, *key))
+        end
+
+        write(':')
+
+        ws
+
+        visit(value)
+      end
+
+      def write_symbol_body(symbol)
+        write(symbol.inspect[1..])
+      end
+    end # Pin
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/hookexe.rb b/lib/unparser/emitter/hookexe.rb
index bb71d1e..de2a2e8 100644
--- a/lib/unparser/emitter/hookexe.rb
+++ b/lib/unparser/emitter/hookexe.rb
@@ -6,8 +6,8 @@ module Unparser
     class Hookexe < self
 
       MAP = {
-        preexe:  K_PREEXE,
-        postexe: K_POSTEXE
+        preexe:  'BEGIN',
+        postexe: 'END'
       }.freeze
 
       handle(*MAP.keys)
@@ -16,16 +16,10 @@ module Unparser
 
     private
 
-      # Perfrom dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(MAP.fetch(node.type), WS)
-        parentheses(*BRACKETS_CURLY) do
-          emit_body
+        write(MAP.fetch(node.type), ' ')
+        parentheses('{', '}') do
+          emit_body(body)
         end
       end
 
diff --git a/lib/unparser/emitter/if.rb b/lib/unparser/emitter/if.rb
index 11827c3..4919a0f 100644
--- a/lib/unparser/emitter/if.rb
+++ b/lib/unparser/emitter/if.rb
@@ -3,25 +3,21 @@
 module Unparser
   class Emitter
     # Emitter if nodes
-    #
-    # ignore :reek:RepeatedConditional
     class If < self
       handle :if
 
       children :condition, :if_branch, :else_branch
 
-      def terminated?
-        !postcondition?
+      def emit_ternary
+        visit(condition)
+        write(' ? ')
+        visit(if_branch)
+        write(' : ')
+        visit(else_branch)
       end
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         if postcondition?
           emit_postcondition
@@ -30,105 +26,53 @@ module Unparser
         end
       end
 
-      # Test for postcondition
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
       def postcondition?
         return false unless if_branch.nil? ^ else_branch.nil?
 
         body = if_branch || else_branch
 
-        local_variable_scope.first_assignment_in_body_and_used_in_condition?(body, condition)
+        local_variable_scope.first_assignment_in?(body, condition)
       end
 
-      # Emit in postcondition style
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_postcondition
-        visit_plain(if_branch || else_branch)
-        write(WS, keyword, WS)
+        visit(if_branch || else_branch)
+        write(' ', keyword, ' ')
         emit_condition
       end
 
-      # Emit in normal style
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_normal
-        write(keyword, WS)
+        write(keyword, ' ')
         emit_condition
         emit_if_branch
         emit_else_branch
         k_end
       end
 
-      # Test if AST can be emitted as unless
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
       def unless?
         !if_branch && else_branch
       end
 
-      # Return keyword
-      #
-      # @return [String]
-      #
-      # @api private
-      #
       def keyword
-        unless? ? K_UNLESS : K_IF
+        unless? ? 'unless' : 'if'
       end
 
-      # Emit condition
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_condition
-        if condition.type.equal?(:match_current_line)
-          visit_plain(condition)
-        else
-          visit(condition)
-        end
+        visit(condition)
       end
 
-      # Emit if branch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_if_branch
         if if_branch
-          visit_indented(if_branch)
+          emit_body(if_branch)
         end
 
         nl if !if_branch && !else_branch
       end
 
-      # Emit else branch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_else_branch
         return unless else_branch
 
-        write(K_ELSE) unless unless?
-        visit_indented(else_branch)
+        write('else') unless unless?
+        emit_body(else_branch)
       end
 
     end # If
diff --git a/lib/unparser/emitter/in_match.rb b/lib/unparser/emitter/in_match.rb
new file mode 100644
index 0000000..5027fa0
--- /dev/null
+++ b/lib/unparser/emitter/in_match.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class InMatch < self
+
+      handle :in_match
+
+      children :target, :pattern
+
+    private
+
+      def dispatch
+        visit(target)
+        write(' in ')
+        visit(pattern)
+      end
+    end # InMatch
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/in_pattern.rb b/lib/unparser/emitter/in_pattern.rb
new file mode 100644
index 0000000..b1f0d43
--- /dev/null
+++ b/lib/unparser/emitter/in_pattern.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class InPattern < self
+
+      handle :in_pattern
+
+      children :target, :unless_guard, :branch, :else_branch
+
+    private
+
+      def dispatch
+        write('in')
+
+        ws
+
+        visit(target)
+
+        if unless_guard
+          ws
+          visit(unless_guard)
+        end
+
+        if branch
+          ws
+          write('then')
+          emit_body(branch)
+        else
+          nl
+        end
+      end
+    end # InPattern
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/index.rb b/lib/unparser/emitter/index.rb
index 42efeeb..dd7a3a7 100644
--- a/lib/unparser/emitter/index.rb
+++ b/lib/unparser/emitter/index.rb
@@ -3,61 +3,29 @@
 module Unparser
   class Emitter
     # Emitter for send to index references
-    #
-    # ignore :reek:RepeatedConditional
     class Index < self
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
+    private
+
       def dispatch
         emit_receiver
         emit_operation
       end
 
-    private
-
-      # Emit receiver
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_receiver
         visit(first_child)
       end
 
-      # Test for mlhs
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def mlhs?
-        parent_type.equal?(:mlhs)
-      end
-
       class Reference < self
-        include Terminated
-
         define_group(:indices, 1..-1)
 
         handle :index
 
       private
 
-        # Emit arguments
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def emit_operation
-          parentheses(*BRACKETS_SQUARE) do
-            delimited_plain(indices)
+          parentheses('[', ']') do
+            delimited(indices)
           end
         end
       end # Reference
@@ -68,67 +36,32 @@ module Unparser
         handle :indexasgn
 
         VALUE_RANGE     = (1..-2).freeze
-        NO_VALUE_PARENT = IceNine.deep_freeze(%i[and_asgn op_asgn or_asgn].to_set)
-
-        # Test if assign will be emitted terminated
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def terminated?
-          !emit_value?
-        end
+        NO_VALUE_PARENT = %i[and_asgn op_asgn or_asgn].to_set.freeze
 
-      private
+        private_constant(*constants(false))
 
-        # Emit arguments
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_operation
-          parentheses(*BRACKETS_SQUARE) do
-            delimited_plain(indices)
-          end
-
-          if emit_value?
-            write(WS, T_ASN, WS)
-            visit(children.last)
-          end
+        def emit_heredoc_reminders
+          emitter(children.last).emit_heredoc_reminders
         end
 
-        # The indices
-        #
-        # @return [Array<Parser::AST::Node>]
-        #
-        def indices
-          if emit_value?
-            children[VALUE_RANGE]
-          else
-            children.drop(1)
-          end
+        def dispatch
+          emit_receiver
+          emit_operation(children[VALUE_RANGE])
+          write(' = ')
+          visit(children.last)
         end
 
-        # Test if value should be emitted
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def emit_value?
-          !mlhs? && !no_value_parent?
+        def emit_mlhs
+          emit_receiver
+          emit_operation(children.drop(1))
         end
 
-        # Test for no value parent
-        #
-        # @return [Boolean]
-        #
-        # @api private
-        #
-        def no_value_parent?
-          NO_VALUE_PARENT.include?(parent_type)
+      private
+
+        def emit_operation(indices)
+          parentheses('[', ']') do
+            delimited(indices)
+          end
         end
       end # Assign
     end # Index
diff --git a/lib/unparser/emitter/kwargs.rb b/lib/unparser/emitter/kwargs.rb
new file mode 100644
index 0000000..bffffc9
--- /dev/null
+++ b/lib/unparser/emitter/kwargs.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    class Kwargs < self
+      handle :kwargs
+
+      def dispatch
+        delimited(children)
+      end
+    end # Kwargs
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/kwbegin.rb b/lib/unparser/emitter/kwbegin.rb
new file mode 100644
index 0000000..06fd712
--- /dev/null
+++ b/lib/unparser/emitter/kwbegin.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for explicit begins
+    class KWBegin < self
+      handle :kwbegin
+
+    private
+
+      def dispatch
+        write('begin')
+
+        if children.one?
+          emit_body_ensure_rescue(children.first)
+        else
+          indented do
+            emit_multiple_body
+          end
+        end
+
+        k_end
+      end
+
+      def emit_multiple_body
+        emit_join(children, method(:emit_body_member), method(:nl))
+      end
+
+    end # KWBegin
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/lambda.rb b/lib/unparser/emitter/lambda.rb
index 2bcb432..4e3bb73 100644
--- a/lib/unparser/emitter/lambda.rb
+++ b/lib/unparser/emitter/lambda.rb
@@ -4,18 +4,10 @@ module Unparser
   class Emitter
     # Emitter for lambda nodes
     class Lambda < self
-      include Terminated
-
       handle :lambda
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write('->')
       end
diff --git a/lib/unparser/emitter/literal.rb b/lib/unparser/emitter/literal.rb
deleted file mode 100644
index 504a348..0000000
--- a/lib/unparser/emitter/literal.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    # Namespace class for literal emiters
-    class Literal < self
-      include Terminated
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/array.rb b/lib/unparser/emitter/literal/array.rb
deleted file mode 100644
index a55345a..0000000
--- a/lib/unparser/emitter/literal/array.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Array literal emitter
-      class Array < self
-        handle :array
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          parentheses(*BRACKETS_SQUARE) do
-            delimited(children)
-          end
-        end
-
-      end # Array
-
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/dynamic.rb b/lib/unparser/emitter/literal/dynamic.rb
deleted file mode 100644
index 24d89c0..0000000
--- a/lib/unparser/emitter/literal/dynamic.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Base class for dynamic literal emitters
-      class Dynamic < self
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          util = self.class
-          visit_parentheses(dynamic_body, util::OPEN, util::CLOSE)
-        end
-
-        # Return dynamic body
-        #
-        # @return [Parser::AST::Node]
-        #
-        # @api private
-        #
-        def dynamic_body
-          Parser::AST::Node.new(:dyn_str_body, children)
-        end
-
-        # Dynamic string literal emitter
-        class String < self
-
-          OPEN = CLOSE = '"'.freeze
-          handle :dstr
-
-        end # String
-
-        # Dynamic symbol literal emitter
-        class Symbol < self
-
-          OPEN = ':"'.freeze
-          CLOSE = '"'.freeze
-
-          handle :dsym
-
-        end # Symbol
-      end # Dynamic
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/dynamic_body.rb b/lib/unparser/emitter/literal/dynamic_body.rb
deleted file mode 100644
index 8bdf559..0000000
--- a/lib/unparser/emitter/literal/dynamic_body.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Emitter for dynamic bodies
-      class DynamicBody < self
-
-        OPEN = '#{'.freeze
-        CLOSE = '}'.freeze
-
-        # Emitter for interpolated nodes
-        class Interpolation < self
-
-          handle :interpolated
-
-          children :subject
-
-        private
-
-          # Perform dispatch
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def dispatch
-            write(OPEN)
-            visit_plain(subject)
-            write(CLOSE)
-          end
-
-        end
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          children.each(&method(:emit_segment))
-        end
-
-        # Emit segment
-        #
-        # @param [Parser::AST::Node] node
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_segment(node)
-          emit_interpolated_segment(node)
-        end
-
-        pairs = Parser::Lexer::ESCAPES.invert.map do |key, value|
-          [key, "\\#{value}"] unless key.eql?(WS)
-        end.compact
-
-        pairs << ['#{', '\#{']
-
-        ESCAPES = ::Hash[pairs]
-
-        REPLACEMENTS = ::Regexp.union(ESCAPES.keys)
-
-        # Emit str segment
-        #
-        # @param [Parser::AST::Node] node
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_str_segment(node)
-          util = self.class
-          string = node.children.first
-          segment = string
-                    .gsub(REPLACEMENTS, ESCAPES)
-                    .gsub(util::DELIMITER, util::REPLACEMENT)
-          write(segment)
-        end
-
-        # Emit interpolated segment
-        #
-        # @param [Parser::AST::Node] node
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_interpolated_segment(node)
-          visit_parentheses(node, OPEN, CLOSE)
-        end
-
-        # Dynamic string body
-        class String < self
-
-          handle :dyn_str_body
-
-          DELIMITER   = '"'.freeze
-          REPLACEMENT = '\"'.freeze
-
-        end # String
-
-        # Dynamic regexp body
-        class Regexp < self
-
-          handle :dyn_regexp_body
-
-          DELIMITER   = '/'.freeze
-          REPLACEMENT = '\/'.freeze
-
-        end # Regexp
-
-        # Dynamic regexp body
-        class ExecuteString < self
-
-          handle :dyn_xstr_body
-
-          DELIMITER   = '`'.freeze
-          REPLACEMENT = '\`'.freeze
-
-        end # ExecuteString
-
-      end # DynamicBody
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/execute_string.rb b/lib/unparser/emitter/literal/execute_string.rb
deleted file mode 100644
index 64b4176..0000000
--- a/lib/unparser/emitter/literal/execute_string.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-      # Emitter for execute strings (xstr) nodes
-      class ExecuteString < self
-
-        OPEN = CLOSE = '`'.freeze
-
-        handle :xstr
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          visit_parentheses(dynamic_body, OPEN, CLOSE)
-        end
-
-        # Return dynamic body
-        #
-        # @return [Parser::AST::Node]
-        #
-        # @api private
-        #
-        def dynamic_body
-          Parser::AST::Node.new(:dyn_xstr_body, children)
-        end
-
-      end # ExecuteString
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/hash.rb b/lib/unparser/emitter/literal/hash.rb
deleted file mode 100644
index fb958ce..0000000
--- a/lib/unparser/emitter/literal/hash.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Abstract namespace class for hash pair emitters
-      class HashPair < self
-
-        children :key, :value
-
-      private
-
-        # Emit value
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_value
-          value_type = value.type
-          conditional_parentheses(value_type.equal?(:if)) do
-            visit(value)
-          end
-        end
-
-        # Pair emitter that emits hash-rocket separated key values
-        class Rocket < self
-          HASHROCKET = ' => '.freeze
-
-          handle :pair_rocket, :pair
-
-        private
-
-          # Perform dispatch
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def dispatch
-            visit(key)
-            write(HASHROCKET)
-            emit_value
-          end
-
-        end # Rocket
-
-        # Pair emitter that emits colon separated key values
-        class Colon < self
-          COLON = ': '.freeze
-
-          handle :pair_colon
-
-        private
-
-          # Perform dispatch
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def dispatch
-            write(key.children.first.to_s, COLON)
-            emit_value
-          end
-
-        end # Colon
-
-      end # HashPair
-
-      # Emitter for hash bodies
-      class HashBody < self
-
-        BAREWORD = /\A[A-Za-z_][A-Za-z_0-9]*[?!]?\z/.freeze
-
-        handle :hash_body
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          delimited(effective_body)
-        end
-
-        # Return effective body
-        #
-        # @return [Enumerable<Parser::AST::Node>]
-        #
-        # @api private
-        #
-        def effective_body
-          children.map do |pair|
-            if pair.type.equal?(:kwsplat)
-              pair
-            else
-              key, _value = *pair
-              if key.type.equal?(:sym) && key.children.first.to_s =~ BAREWORD
-                n(:pair_colon, pair.children)
-              else
-                n(:pair_rocket, pair.children)
-              end
-            end
-          end
-        end
-
-      end # HashBody
-
-      # Emitter for Hash literals
-      class Hash < self
-
-        DELIMITER = ', '.freeze
-        OPEN      = '{'.freeze
-        CLOSE     = '}'.freeze
-
-        handle :hash
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          if children.empty?
-            write(OPEN, CLOSE)
-          else
-            emit_hash_body
-          end
-        end
-
-        # Emit hash body
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_hash_body
-          parentheses(OPEN, CLOSE) do
-            write(WS)
-            run(HashBody)
-            write(WS)
-          end
-        end
-
-      end # Hash
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/primitive.rb b/lib/unparser/emitter/literal/primitive.rb
deleted file mode 100644
index a9b69ac..0000000
--- a/lib/unparser/emitter/literal/primitive.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Base class for primitive emitters
-      class Primitive < self
-
-        children :value
-
-        # Emitter for primitives based on Object#inspect
-        class Inspect < self
-
-          handle :sym, :str
-
-        private
-
-          # Dispatch value
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def dispatch
-            write(value.inspect)
-          end
-
-        end # Inspect
-
-        # Emitter for complex literals
-        class Complex < self
-
-          handle :complex
-
-          RATIONAL_FORMAT = 'i'.freeze
-
-          MAP =
-            if 0.class.equal?(Integer)
-              IceNine.deep_freeze(
-                Float    => :float,
-                Rational => :rational,
-                Integer  => :int
-              )
-            else
-              IceNine.deep_freeze(
-                Float    => :float,
-                Rational => :rational,
-                Fixnum   => :int,
-                Bignum   => :int
-              )
-            end
-
-        private
-
-          # Dispatch value
-          #
-          # @return [undefined]
-          #
-          def dispatch
-            emit_imaginary
-            write(RATIONAL_FORMAT)
-          end
-
-          # Emit imaginary component
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def emit_imaginary
-            visit(imaginary_node)
-          end
-
-          # Return imaginary node
-          #
-          # @return [Parser::AST::Node]
-          #
-          # @api private
-          #
-          def imaginary_node
-            imaginary = value.imaginary
-            s(MAP.fetch(imaginary.class), imaginary)
-          end
-
-        end # Rational
-
-        # Emitter for rational literals
-        class Rational < self
-
-          handle :rational
-
-          RATIONAL_FORMAT = 'r'.freeze
-
-        private
-
-          # Dispatch value
-          #
-          # @return [undefined]
-          #
-          def dispatch
-            integer = value.to_i
-            float   = value.to_f
-
-            write_rational(integer.to_f.eql?(float) ? integer : float)
-          end
-
-          # Write rational format
-          #
-          # @param [#to_s] value
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def write_rational(value)
-            write(value.to_s, RATIONAL_FORMAT)
-          end
-
-        end # Rational
-
-        # Emiter for numeric literals
-        class Numeric < self
-
-          handle :int, :float
-
-        private
-
-          # Dispatch value
-          #
-          # @return [undefined]
-          #
-          # @api private
-          #
-          def dispatch
-            conditional_parentheses(parent.is_a?(Send) && value.negative?) do
-              write(value.inspect)
-            end
-          end
-
-        end # Numeric
-      end # Primitive
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/range.rb b/lib/unparser/emitter/literal/range.rb
deleted file mode 100644
index a1160ba..0000000
--- a/lib/unparser/emitter/literal/range.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-      # Abstract base class for literal range emitter
-      class Range < self
-        include Unterminated
-
-        TOKENS = IceNine.deep_freeze(
-          irange: '..',
-          erange: '...'
-        )
-
-        handle(*TOKENS.keys)
-
-        children :begin_node, :end_node
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          visit(begin_node)
-          write(TOKENS.fetch(node.type))
-          visit(end_node) if end_node
-        end
-
-      end # Range
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/regexp.rb b/lib/unparser/emitter/literal/regexp.rb
deleted file mode 100644
index 13db5ea..0000000
--- a/lib/unparser/emitter/literal/regexp.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-
-      # Emitter for regexp literals
-      class Regexp < self
-        DELIMITER = '/'.freeze
-        ESCAPED_DELIMITER = '\/'.freeze
-
-        handle :regexp
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          parentheses(DELIMITER, DELIMITER) do
-            body.each(&method(:write_body))
-          end
-          visit(children.last)
-        end
-
-        # Return non regopt children
-        #
-        # @return [Array<Parser::AST::Node>]
-        #
-        # @api private
-        #
-        def body
-          children[0..-2]
-        end
-
-        # Write specific body component
-        #
-        # @param [Parser::AST::Node] node
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def write_body(node)
-          case node.type
-          when :str
-            buffer.append_without_prefix(escape(node).children.first)
-          else
-            visit(s(:interpolated, node))
-          end
-        end
-
-        # Return dynamic body
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dynamic_body
-          Parser::AST::Node.new(:dyn_regexp_body, dynamic_body_children)
-        end
-
-        # Return dynamic body children
-        #
-        # @return [Enumerable<Parser::AST::Node>]
-        #
-        # @api private
-        #
-        def dynamic_body_children
-          children[0..-2].map do |child|
-            escape(child)
-          end
-        end
-
-        # Return escaped child
-        #
-        # @param [Parser::AST::Node] child
-        #
-        # @return [Parser::AST::Node]
-        #
-        # @api private
-        #
-        def escape(child)
-          source = child.children.first
-          s(:str, source.gsub(DELIMITER, ESCAPED_DELIMITER))
-        end
-
-      end # Regexp
-
-      # Emitter for regexp options
-      class Regopt < self
-
-        handle :regopt
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          write(children.map(&:to_s).join)
-        end
-
-      end # Regopt
-
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/literal/singleton.rb b/lib/unparser/emitter/literal/singleton.rb
deleted file mode 100644
index cf664d4..0000000
--- a/lib/unparser/emitter/literal/singleton.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Literal
-      # Emiter for literal singletons
-      class Singleton < self
-
-        handle :self, :true, :false, :nil
-
-      private
-
-        # Perform dispatco
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          buffer.append(node.type.to_s)
-        end
-
-      end # Singleton
-    end # Literal
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/masgn.rb b/lib/unparser/emitter/masgn.rb
new file mode 100644
index 0000000..7eb2d49
--- /dev/null
+++ b/lib/unparser/emitter/masgn.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for multiple assignment nodes
+    class MASGN < self
+      handle :masgn
+
+      children :target, :source
+
+    private
+
+      def dispatch
+        visit(target)
+        write(' = ')
+        visit(source)
+      end
+    end # MLHS
+  end # Emitter
+end # Unaprser
diff --git a/lib/unparser/emitter/match.rb b/lib/unparser/emitter/match.rb
index 1af6418..ec37bf3 100644
--- a/lib/unparser/emitter/match.rb
+++ b/lib/unparser/emitter/match.rb
@@ -5,10 +5,6 @@ module Unparser
 
     # Base class for special match node emitters
     class Match < self
-      include Unterminated
-
-      OPERATOR = '=~'.freeze
-
       # Emitter for match with local variable assignment
       class Lvasgn < self
         handle :match_with_lvasgn
@@ -17,15 +13,9 @@ module Unparser
 
       private
 
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def dispatch
           visit(regexp)
-          write(WS, OPERATOR, WS)
+          write(' =~ ')
           visit(lvasgn)
         end
 
@@ -37,12 +27,8 @@ module Unparser
 
         children :regexp
 
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
+      private
+
         def dispatch
           visit(regexp)
         end
diff --git a/lib/unparser/emitter/match_alt.rb b/lib/unparser/emitter/match_alt.rb
new file mode 100644
index 0000000..ff877ab
--- /dev/null
+++ b/lib/unparser/emitter/match_alt.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class MatchAlt < self
+
+      handle :match_alt
+
+      children :left, :right
+
+    private
+
+      def dispatch
+        visit(left)
+        ws
+        write('|')
+        ws
+        visit(right)
+      end
+    end # MatchAlt
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_as.rb b/lib/unparser/emitter/match_as.rb
new file mode 100644
index 0000000..59eb0d8
--- /dev/null
+++ b/lib/unparser/emitter/match_as.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class MatchAs < self
+
+      handle :match_as
+
+      children :left, :right
+
+    private
+
+      def dispatch
+        visit(left)
+        write(' => ')
+        visit(right)
+      end
+    end # MatchAs
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_pattern.rb b/lib/unparser/emitter/match_pattern.rb
new file mode 100644
index 0000000..c9e45dd
--- /dev/null
+++ b/lib/unparser/emitter/match_pattern.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class MatchPattern < self
+
+      handle :match_pattern
+
+      children :target, :pattern
+
+      # Modern ast format emits `match_pattern`
+      # node on single line pre 3.0, but 3.0+ uses `match_pattern_p`
+      SYMBOL =
+        if RUBY_VERSION < '3.0'
+          ' in '
+        else
+          ' => '
+        end
+
+    private
+
+      def dispatch
+        visit(target)
+        write(SYMBOL)
+        visit(pattern)
+      end
+    end # MatchPattern
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_pattern_p.rb b/lib/unparser/emitter/match_pattern_p.rb
new file mode 100644
index 0000000..90b6251
--- /dev/null
+++ b/lib/unparser/emitter/match_pattern_p.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    class MatchPatternP < self
+
+      handle :match_pattern_p
+
+      children :target, :pattern
+
+    private
+
+      def dispatch
+        visit(target)
+        write(' in ')
+        visit(pattern)
+      end
+    end # MatchPatternP
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_rest.rb b/lib/unparser/emitter/match_rest.rb
new file mode 100644
index 0000000..b9a2f1c
--- /dev/null
+++ b/lib/unparser/emitter/match_rest.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emiter for match rest nodes
+    class MatchRest < self
+      handle :match_rest
+
+      children :match_var
+
+      def dispatch
+        write('*')
+        visit(match_var) if match_var
+      end
+
+      def emit_array_pattern
+        write('*')
+        emit_match_var
+      end
+
+      def emit_hash_pattern
+        write('**')
+        emit_match_var
+      end
+
+    private
+
+      def emit_match_var
+        visit(match_var) if match_var
+      end
+    end # MatchRest
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/match_var.rb b/lib/unparser/emitter/match_var.rb
new file mode 100644
index 0000000..5c89590
--- /dev/null
+++ b/lib/unparser/emitter/match_var.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for in pattern nodes
+    class MatchVar < self
+
+      handle :match_var
+
+      children :name
+
+    private
+
+      def dispatch
+        write(name.to_s)
+      end
+    end # MatchVar
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/meta.rb b/lib/unparser/emitter/meta.rb
deleted file mode 100644
index d84078d..0000000
--- a/lib/unparser/emitter/meta.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    # Namespace class for meta emitters
-    class Meta < self
-      include Terminated
-
-      handle(:__ENCODING__, :__FILE__, :__LINE__)
-
-      def dispatch
-        write(node.type.to_s)
-      end
-    end # Meta
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/mlhs.rb b/lib/unparser/emitter/mlhs.rb
new file mode 100644
index 0000000..c883539
--- /dev/null
+++ b/lib/unparser/emitter/mlhs.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for multiple assignment left hand side
+    class MLHS < self
+      handle :mlhs
+
+      NO_COMMA = %i[arg splat mlhs restarg].freeze
+
+      private_constant(*constants(false))
+
+    private
+
+      def dispatch
+        if children.one?
+          emit_one_child_mlhs
+        else
+          emit_many
+        end
+      end
+
+      def emit_one_child_mlhs
+        child = children.first
+        parentheses do
+          emitter(child).emit_mlhs
+          write(',') unless NO_COMMA.include?(child.type)
+        end
+      end
+
+      def emit_many
+        parentheses do
+          delimited(children) do |node|
+            emitter(node).emit_mlhs
+          end
+        end
+      end
+    end # MLHS
+  end # Emitter
+end # Unaprser
diff --git a/lib/unparser/emitter/module.rb b/lib/unparser/emitter/module.rb
index 364e248..06b3d9b 100644
--- a/lib/unparser/emitter/module.rb
+++ b/lib/unparser/emitter/module.rb
@@ -4,7 +4,7 @@ module Unparser
   class Emitter
     # Emitter for module nodes
     class Module < self
-      include LocalVariableRoot, Terminated
+      include LocalVariableRoot
 
       handle :module
 
@@ -12,16 +12,10 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_MODULE, WS)
+        write('module ')
         visit(name)
-        emit_body
+        emit_optional_body(body)
         k_end
       end
 
diff --git a/lib/unparser/emitter/op_assign.rb b/lib/unparser/emitter/op_assign.rb
index 0a2d91e..52d3d11 100644
--- a/lib/unparser/emitter/op_assign.rb
+++ b/lib/unparser/emitter/op_assign.rb
@@ -5,28 +5,25 @@ module Unparser
 
     # Base class for and and or op-assign
     class BinaryAssign < self
-      include Unterminated
-
       children :target, :expression
 
-      MAP = IceNine.deep_freeze(
+      MAP = {
         and_asgn: '&&=',
         or_asgn:  '||='
-      )
+      }.freeze
 
       handle(*MAP.keys)
 
+      def emit_heredoc_reminders
+        emitter(target).emit_heredoc_reminders
+        emitter(expression).emit_heredoc_reminders
+      end
+
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        visit(target)
-        write(WS, MAP.fetch(node.type), WS)
+        emitter(target).emit_mlhs
+        write(' ', MAP.fetch(node.type), ' ')
         visit(expression)
       end
 
@@ -34,32 +31,20 @@ module Unparser
 
     # Emitter for op assign
     class OpAssign < self
-      include Unterminated
-
       handle :op_asgn
 
+      children :target, :operator, :value
+
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        visit(first_child)
+        emitter(first_child).emit_mlhs
         emit_operator
-        visit(children[2])
+        visit(value)
       end
 
-      # Emit operator
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_operator
-        write(WS, children[1].to_s, T_ASN, WS)
+        write(' ', operator.to_s, '= ')
       end
 
     end # OpAssign
diff --git a/lib/unparser/emitter/pair.rb b/lib/unparser/emitter/pair.rb
new file mode 100644
index 0000000..04c87d8
--- /dev/null
+++ b/lib/unparser/emitter/pair.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for key value pairs in hash literals or kwargs
+    class Pair < self
+      BAREWORD = /\A[A-Za-z_][A-Za-z_0-9]*[?!]?\z/.freeze
+
+      private_constant(*constants(false))
+
+      handle :pair
+
+      children :key, :value
+
+    private
+
+      def dispatch
+        if colon?(key)
+          write(key.children.first.to_s, ': ')
+        else
+          visit(key)
+          write(' => ')
+        end
+
+        visit(value)
+      end
+
+      def colon?(key)
+        n_sym?(key) && BAREWORD.match?(key.children.first)
+      end
+    end
+  end
+end
diff --git a/lib/unparser/emitter/pin.rb b/lib/unparser/emitter/pin.rb
new file mode 100644
index 0000000..444469e
--- /dev/null
+++ b/lib/unparser/emitter/pin.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for pin nodes
+    class Pin < self
+      handle :pin
+
+      children :target
+
+    private
+
+      def dispatch
+        write('^')
+        visit(target)
+      end
+    end # Pin
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/primitive.rb b/lib/unparser/emitter/primitive.rb
new file mode 100644
index 0000000..e173704
--- /dev/null
+++ b/lib/unparser/emitter/primitive.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Base class for primitive emitters
+    class Primitive < self
+
+      children :value
+
+      # Emitter for primitives based on Object#inspect
+      class Inspect < self
+
+        handle :sym, :str
+
+      private
+
+        def dispatch
+          write(value.inspect)
+        end
+
+      end # Inspect
+
+      # Emitter for complex literals
+      class Complex < self
+
+        handle :complex
+
+        RATIONAL_FORMAT = 'i'.freeze
+
+        MAP =
+          {
+            ::Float    => :float,
+            ::Rational => :rational,
+            ::Integer  => :int
+          }.freeze
+
+      private
+
+        def dispatch
+          emit_imaginary
+          write(RATIONAL_FORMAT)
+        end
+
+        def emit_imaginary
+          visit(imaginary_node)
+        end
+
+        def imaginary_node
+          imaginary = value.imaginary
+          s(MAP.fetch(imaginary.class), imaginary)
+        end
+
+      end # Rational
+
+      # Emitter for rational literals
+      class Rational < self
+
+        handle :rational
+
+        RATIONAL_FORMAT = 'r'.freeze
+
+      private
+
+        # rubocop:disable Lint/FloatComparison
+        def dispatch
+          integer = Integer(value)
+          float   = value.to_f
+
+          write_rational(integer.to_f.equal?(float) ? integer : float)
+        end
+        # rubocop:enable Lint/FloatComparison
+
+        def write_rational(value)
+          write(value.to_s, RATIONAL_FORMAT)
+        end
+
+      end # Rational
+
+      # Emiter for numeric literals
+      class Numeric < self
+
+        handle :int
+
+      private
+
+        def dispatch
+          write(value.inspect)
+        end
+
+      end # Numeric
+    end # Primitive
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/range.rb b/lib/unparser/emitter/range.rb
new file mode 100644
index 0000000..8db822e
--- /dev/null
+++ b/lib/unparser/emitter/range.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Range emitters
+    class Range < self
+      TOKENS = {
+        irange: '..',
+        erange: '...'
+      }.freeze
+
+      SYMBOLS = {
+        erange: :tDOT3,
+        irange: :tDOT2
+      }.freeze
+
+      def symbol_name
+        true
+      end
+
+      handle(*TOKENS.keys)
+
+      children :begin_node, :end_node
+
+    private
+
+      def dispatch
+        visit(begin_node) if begin_node
+        write(TOKENS.fetch(node.type))
+        visit(end_node) if end_node
+      end
+
+    end # Range
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/redo.rb b/lib/unparser/emitter/redo.rb
deleted file mode 100644
index 93faa1a..0000000
--- a/lib/unparser/emitter/redo.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    # Emitter for redo nodes
-    class Redo < self
-      include Terminated
-
-      handle :redo
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        write(K_REDO)
-      end
-
-    end # Redo
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/regexp.rb b/lib/unparser/emitter/regexp.rb
new file mode 100644
index 0000000..a9bdc94
--- /dev/null
+++ b/lib/unparser/emitter/regexp.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for regexp literals
+    class Regexp < self
+      handle :regexp
+
+      define_group(:body, 0..-2)
+
+    private
+
+      def dispatch
+        parentheses('/', '/') do
+          body.each(&method(:emit_body))
+        end
+        emit_options
+      end
+
+      def emit_options
+        write(children.last.children.join)
+      end
+
+      def emit_body(node)
+        if n_begin?(node)
+          write('#{')
+          node.children.each(&method(:visit))
+          write('}')
+        else
+          buffer.append_without_prefix(node.children.first.gsub('/', '\/'))
+        end
+      end
+    end # Regexp
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/repetition.rb b/lib/unparser/emitter/repetition.rb
index 870028f..9ecb993 100644
--- a/lib/unparser/emitter/repetition.rb
+++ b/lib/unparser/emitter/repetition.rb
@@ -5,39 +5,29 @@ module Unparser
 
     # Emitter for postconditions
     class Post < self
-      include Unterminated
-
-      handle :while_post, :until_post
-
       children :condition, :body
 
       MAP = {
-        while_post: K_WHILE,
-        until_post: K_UNTIL
+        while_post: 'while',
+        until_post: 'until'
       }.freeze
 
       handle(*MAP.keys)
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
+    private
+
       def dispatch
         visit(body)
-        write(WS, MAP.fetch(node.type), WS)
+        write(' ', MAP.fetch(node.type), ' ')
         visit(condition)
       end
     end
 
-    # Base class for while and until emitters
+    # Emitter for while and until nodes
     class Repetition < self
-      include Terminated
-
       MAP = {
-        while: K_WHILE,
-        until: K_UNTIL
+        while: 'while',
+        until: 'until'
       }.freeze
 
       handle(*MAP.keys)
@@ -46,12 +36,6 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         if postcontrol?
           emit_postcontrol
@@ -60,54 +44,30 @@ module Unparser
         end
       end
 
-      # Test if node must be emitted in postcontrol form
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
       def postcontrol?
-        return nil unless body # greez from falsyness
-
-        local_variable_scope.first_assignment_in_body_and_used_in_condition?(body, condition)
+        body && local_variable_scope.first_assignment_in?(body, condition)
       end
 
-      # Emit keyword
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_keyword
-        write(MAP.fetch(node.type), WS)
+        write(MAP.fetch(node.type), ' ')
       end
 
-      # Emit embedded
-      #
-      # @return [undefned]
-      #
-      # @api private
-      #
       def emit_normal
         emit_keyword
-        conditional_parentheses(condition.type.equal?(:block)) do
-          visit(condition)
+        visit(condition)
+        if body
+          emit_body(body)
+        else
+          nl
         end
-        emit_body
         k_end
       end
 
-      # Emit postcontrol
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_postcontrol
-        visit_plain(body)
+        visit(body)
         ws
         emit_keyword
-        visit_plain(condition)
+        visit(condition)
       end
 
     end # Repetition
diff --git a/lib/unparser/emitter/resbody.rb b/lib/unparser/emitter/resbody.rb
deleted file mode 100644
index 33494eb..0000000
--- a/lib/unparser/emitter/resbody.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    # Emitter for rescue body nodes
-    class Resbody < self
-
-      children :exception, :assignment, :body
-
-      # Emitter for resbody in standalone form
-      class Standalone < self
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          write(K_RESCUE, WS)
-          visit_plain(body)
-        end
-      end
-
-      # Emitter for resbody in keyworkd-embedded form
-      class Embedded < self
-
-        handle :resbody
-
-      private
-
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          write(K_RESCUE)
-          emit_exception
-          emit_assignment
-          emit_body
-        end
-
-        # Emit exception
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_exception
-          return unless exception
-
-          ws
-          delimited(exception.children)
-        end
-
-        # Emit assignment
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_assignment
-          return unless assignment
-
-          write(WS, T_ASR, WS)
-          visit(assignment)
-        end
-
-      end # Resbody
-    end
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/rescue.rb b/lib/unparser/emitter/rescue.rb
index 0846aa3..e4ca0e3 100644
--- a/lib/unparser/emitter/rescue.rb
+++ b/lib/unparser/emitter/rescue.rb
@@ -4,109 +4,13 @@ module Unparser
   class Emitter
     # Emitter for rescue nodes
     class Rescue < self
-      include Unterminated
-
       handle :rescue
 
-      children :body, :rescue_body
-
-      define_group :rescue_bodies, 1..-2
-
-      EMBEDDED_TYPES = %i[block def defs kwbegin ensure].to_set.freeze
-
-      NOINDENT_STANDALONE_RESCUE = %i[root begin pair_rocket pair_colon lvasgn ivasgn].to_set.freeze
-
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        if standalone?
-          if NOINDENT_STANDALONE_RESCUE.include?(parent_type)
-            emit_standalone
-          else
-            indented { emit_standalone }
-          end
-        else
-          emit_embedded
-        end
-      end
-
-      # Test if rescue node ist standalone
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def standalone?
-        if parent_type.equal?(:ensure)
-          !parent.node.children.first.equal?(node)
-        else
-          !EMBEDDED_TYPES.include?(parent_type)
-        end
-      end
-
-      # Emit standalone form
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_standalone
-        visit_plain(body)
-        ws
-        run(Resbody::Standalone, rescue_body)
+        emit_rescue_postcontrol(node)
       end
-
-      # Emit embedded form
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_embedded
-        if body
-          visit_indented(body)
-        else
-          nl
-        end
-        rescue_bodies.each do |child|
-          run(Resbody::Embedded, child)
-        end
-        emit_else
-      end
-
-      # Emit else
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_else
-        return unless else_branch
-
-        write(K_ELSE)
-        visit_indented(else_branch)
-      end
-
-      # Return else body
-      #
-      # @return [Parser::AST::Node]
-      #   if else body is present
-      #
-      # @return [nil]
-      #   otherwise
-      #
-      # @api private
-      #
-      def else_branch
-        children.last
-      end
-
     end # Rescue
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/retry.rb b/lib/unparser/emitter/retry.rb
deleted file mode 100644
index c8ab4bf..0000000
--- a/lib/unparser/emitter/retry.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    # Emitter for retry nodes
-    class Retry < self
-      include Terminated
-
-      handle :retry
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        write(K_RETRY)
-      end
-
-    end # Break
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/root.rb b/lib/unparser/emitter/root.rb
index 5e0bb01..e335f44 100644
--- a/lib/unparser/emitter/root.rb
+++ b/lib/unparser/emitter/root.rb
@@ -4,8 +4,24 @@ module Unparser
   class Emitter
     # Root emitter a special case
     class Root < self
-      include Concord::Public.new(:node, :buffer, :comments)
+      include Concord::Public.new(:buffer, :node, :comments)
       include LocalVariableRoot
+
+      END_NL = %i[class sclass module begin].freeze
+
+      private_constant(*constants(false))
+
+      def dispatch
+        if children.any?
+          emit_body(node, indent: false)
+        else
+          visit_deep(node)
+        end
+
+        emit_eof_comments
+
+        nl if END_NL.include?(node.type) && !buffer.fresh_line?
+      end
     end # Root
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/send.rb b/lib/unparser/emitter/send.rb
index 76518ec..7c598d4 100644
--- a/lib/unparser/emitter/send.rb
+++ b/lib/unparser/emitter/send.rb
@@ -3,236 +3,27 @@
 module Unparser
   class Emitter
     # Emitter for send
-    # ignore :reek:TooManyMethods
     class Send < self
+      handle :csend, :send
 
-      handle :send
-
-      ASSIGN_SUFFIX    = '='.freeze
-      INDEX_ASSIGN     = :'[]='
-      INDEX_REFERENCE  = :'[]'
-      NON_ASSIGN_RANGE = (0..-2).freeze
-
-      children :receiver, :selector
+      def emit_mlhs
+        writer.emit_mlhs
+      end
 
-      def terminated?
-        effective_emitter.terminated?
+      def emit_heredoc_reminders
+        writer.emit_heredoc_reminders
       end
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        effective_emitter.write_to_buffer
-      end
-
-      # Return effective emitter
-      #
-      # @return [Emitter]
-      #
-      # @api private
-      #
-      def effective_emitter
-        effective_emitter_class.new(node, parent)
-      end
-
-      # Return effective emitter
-      #
-      # @return [Class:Emitter]
-      #
-      # @api private
-      #
-      def effective_emitter_class
-        if binary_operator?
-          Binary
-        elsif unary_operator?
-          Unary
-        elsif attribute_assignment?
-          AttributeAssignment
-        else
-          Regular
-        end
-      end
-
-      # Return string selector
-      #
-      # @return [String]
-      #
-      # @api private
-      #
-      def string_selector
-        selector.to_s
-      end
-
-      # Test for unary operator implemented as method
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def unary_operator?
-        UNARY_OPERATORS.include?(selector)
-      end
-
-      # Test for binary operator implemented as method
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def binary_operator?
-        BINARY_OPERATORS.include?(selector) && arguments.one? && !arguments.first.type.equal?(:splat)
-      end
-
-      # Emit selector
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_selector
-        write(mlhs? ? non_assignment_selector : string_selector)
-      end
-
-      # Test for mlhs
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def mlhs?
-        parent_type.equal?(:mlhs)
-      end
-
-      # Test for assignment
-      #
-      # FIXME: This also returns true for <= operator!
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def assignment?
-        string_selector[-1].eql?(ASSIGN_SUFFIX)
-      end
-
-      # Test for attribute assignment
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def attribute_assignment?
-        !BINARY_OPERATORS.include?(selector) && !UNARY_OPERATORS.include?(selector) && assignment? && !mlhs?
-      end
-
-      # Test for empty arguments
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def arguments?
-        arguments.any?
-      end
-
-      # Return argument nodes
-      #
-      # @return [Array<Parser::AST::Node>]
-      #
-      # @api private
-      #
-      def arguments
-        children[2..-1]
-      end
-      memoize :arguments
-
-      # Emit arguments
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def emit_arguments
-        if arguments.empty?
-          write('()') if receiver.nil? && avoid_clash?
-        else
-          normal_arguments
-        end
-      end
-
-      # Emit normal arguments
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def normal_arguments
-        parentheses do
-          delimited_plain(effective_arguments)
-        end
-      end
-
-      # The effective arguments
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
-      def effective_arguments
-        last = arguments.length - 1
-        arguments.each_with_index.map do |argument, index|
-          if last.equal?(index) && argument.type.equal?(:hash) && argument.children.any?
-            argument.updated(:hash_body)
-          else
-            argument
-          end
-        end
-      end
-
-      # Test if clash with local variable or constant needs to be avoided
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def avoid_clash?
-        local_variable_clash? || parses_as_constant?
-      end
-
-      # Test for local variable clash
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def local_variable_clash?
-        local_variable_scope.local_variable_defined_for_node?(node, selector)
-      end
-
-      # The non assignment selector
-      #
-      # @return [String]
-      #
-      # @api private
-      def non_assignment_selector
-        string_selector[NON_ASSIGN_RANGE]
+        writer.dispatch
       end
 
-      # Test if selector parses as constant
-      #
-      # @return [Boolean]
-      #
-      # @api private
-      #
-      def parses_as_constant?
-        Unparser.parse(selector.to_s).type.equal?(:const)
+      def writer
+        writer_with(Writer::Send, node)
       end
+      memoize :writer
     end # Send
   end # Emitter
 end # Unparser
diff --git a/lib/unparser/emitter/send/binary.rb b/lib/unparser/emitter/send/binary.rb
deleted file mode 100644
index 8de416f..0000000
--- a/lib/unparser/emitter/send/binary.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Send
-      # Emitter for binary sends
-      class Binary < self
-        include Unterminated
-
-      private
-
-        # Return undefined
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          visit(receiver)
-          emit_operator
-          emit_right
-        end
-
-        # Emit operator
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_operator
-          write(WS, string_selector, WS)
-        end
-
-        # Return right node
-        #
-        # @return [Parser::AST::Node]
-        #
-        # @api private
-        #
-        def right_node
-          children[2]
-        end
-
-        # Emit right
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_right
-          visit(right_node)
-        end
-
-      end # Binary
-    end # Send
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/send/conditional.rb b/lib/unparser/emitter/send/conditional.rb
deleted file mode 100644
index ac29782..0000000
--- a/lib/unparser/emitter/send/conditional.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Send
-      # Emitter for "conditional" receiver&.selector(arguments...) case
-      class Conditional < self
-        include Terminated
-
-        handle :csend
-
-      private
-
-        # Perform regular dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          emit_receiver
-          emit_selector
-          emit_arguments
-        end
-
-        # Emit receiver
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_receiver
-          visit(receiver)
-          write(T_AMP, T_DOT)
-        end
-
-      end # Regular
-    end # Send
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/send/regular.rb b/lib/unparser/emitter/send/regular.rb
deleted file mode 100644
index 4ed6145..0000000
--- a/lib/unparser/emitter/send/regular.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  class Emitter
-    class Send
-      # Emitter for "regular" receiver.selector(arguments...) case
-      class Regular < self
-        include Terminated
-
-      private
-
-        # Perform regular dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def dispatch
-          emit_receiver
-          emit_selector
-          emit_arguments
-        end
-
-        # Emit receiver
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
-        def emit_receiver
-          return unless first_child
-
-          visit(receiver)
-          write(T_DOT)
-        end
-
-      end # Regular
-    end # Send
-  end # Emitter
-end # Unparser
diff --git a/lib/unparser/emitter/simple.rb b/lib/unparser/emitter/simple.rb
new file mode 100644
index 0000000..b3dfe12
--- /dev/null
+++ b/lib/unparser/emitter/simple.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Emitter for simple nodes that generate a single token
+    class Simple < self
+      MAP = {
+        __ENCODING__:      '__ENCODING__',
+        __FILE__:          '__FILE__',
+        __LINE__:          '__LINE__',
+        false:             'false',
+        forward_arg:       '...',
+        forwarded_args:    '...',
+        kwnilarg:          '**nil',
+        match_nil_pattern: '**nil',
+        nil:               'nil',
+        redo:              'redo',
+        retry:             'retry',
+        self:              'self',
+        true:              'true',
+        zsuper:            'super'
+      }.freeze
+
+      handle(*MAP.keys)
+
+    private
+
+      def dispatch
+        write(MAP.fetch(node_type))
+      end
+    end # Simple
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/splat.rb b/lib/unparser/emitter/splat.rb
index c0944bc..5129fa5 100644
--- a/lib/unparser/emitter/splat.rb
+++ b/lib/unparser/emitter/splat.rb
@@ -4,46 +4,40 @@ module Unparser
   class Emitter
     # Emitter for splats
     class KwSplat < self
-      include Terminated
-
       handle :kwsplat
 
       children :subject
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(T_SPLAT, T_SPLAT)
+        write('**')
         visit(subject)
       end
     end
 
     # Emitter for splats
     class Splat < self
-      include Terminated
-
       handle :splat
 
       children :subject
 
+      def emit_mlhs
+        write('*')
+        subject_emitter.emit_mlhs if subject
+      end
+
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(T_SPLAT)
-        visit(subject) if subject
+        write('*')
+        subject_emitter.write_to_buffer
+      end
+
+      def subject_emitter
+        emitter(subject)
       end
+      memoize :subject_emitter
     end
   end
 end # Unparser
diff --git a/lib/unparser/emitter/super.rb b/lib/unparser/emitter/super.rb
index 669d376..f78729f 100644
--- a/lib/unparser/emitter/super.rb
+++ b/lib/unparser/emitter/super.rb
@@ -3,42 +3,14 @@
 module Unparser
   class Emitter
 
-    # Emitter for zsuper nodes
-    class ZSuper < self
-      include Terminated
-
-      handle :zsuper
-
-    private
-
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
-      def dispatch
-        write(K_SUPER)
-      end
-
-    end # ZSuper
-
     # Emitter for super nodes
     class Super < self
-      include Terminated
-
       handle :super
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_SUPER)
+        write('super')
         parentheses do
           delimited(children)
         end
diff --git a/lib/unparser/emitter/undef.rb b/lib/unparser/emitter/undef.rb
index fef3c38..cda2297 100644
--- a/lib/unparser/emitter/undef.rb
+++ b/lib/unparser/emitter/undef.rb
@@ -4,20 +4,12 @@ module Unparser
   class Emitter
     # Emitter for undef nodes
     class Undef < self
-      include Unterminated
-
       handle :undef
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_UNDEF, WS)
+        write('undef ')
         delimited(children)
       end
 
diff --git a/lib/unparser/emitter/variable.rb b/lib/unparser/emitter/variable.rb
index 905e767..e4915ba 100644
--- a/lib/unparser/emitter/variable.rb
+++ b/lib/unparser/emitter/variable.rb
@@ -5,20 +5,12 @@ module Unparser
 
     # Emitter for various variable accesses
     class Variable < self
-      include Terminated
-
       handle :ivar, :lvar, :cvar, :gvar, :back_ref
 
       children :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write(name.to_s)
       end
@@ -27,43 +19,27 @@ module Unparser
 
     # Emitter for constant access
     class Const < self
-      include Terminated
-
       handle :const
 
       children :scope, :name
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         emit_scope
         write(name.to_s)
       end
 
-      # Emit parent
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def emit_scope
         return unless scope
 
         visit(scope)
-        write(T_DCL) unless scope.type.equal?(:cbase)
+        write('::') unless n_cbase?(scope)
       end
     end
 
     # Emitter for nth_ref nodes (regexp captures)
     class NthRef < self
-      include Terminated
-
       PREFIX = '$'.freeze
       handle :nth_ref
 
@@ -71,12 +47,6 @@ module Unparser
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
         write(PREFIX)
         write(name.to_s)
diff --git a/lib/unparser/emitter/xstr.rb b/lib/unparser/emitter/xstr.rb
new file mode 100644
index 0000000..3655924
--- /dev/null
+++ b/lib/unparser/emitter/xstr.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Unparser
+  class Emitter
+    # Dynamic execute string literal emitter
+    class XStr < self
+
+      handle :xstr
+
+    private
+
+      def dispatch
+        if heredoc?
+          emit_heredoc
+        else
+          emit_xstr
+        end
+      end
+
+      def heredoc?
+        children.any? { |node| node.eql?(s(:str, '')) }
+      end
+
+      def emit_heredoc
+        write(%(<<~`HEREDOC`))
+        buffer.indent
+        nl
+        children.each do |child|
+          if n_str?(child)
+            write(child.children.first)
+          else
+            emit_begin(child)
+          end
+        end
+        buffer.unindent
+        write("HEREDOC\n")
+      end
+
+      def emit_xstr
+        write('`')
+        children.each do |child|
+          if n_begin?(child)
+            emit_begin(child)
+          else
+            emit_string(child)
+          end
+        end
+        write('`')
+      end
+
+      def emit_string(value)
+        write(escape_xstr(value.children.first))
+      end
+
+      def escape_xstr(input)
+        input.chars.map do |char|
+          if char.eql?('`')
+            '\\`'
+          else
+            char
+          end
+        end.join
+      end
+
+      def emit_begin(component)
+        write('#{')
+        visit(unwrap_single_begin(component))
+        write('}')
+      end
+    end # XStr
+  end # Emitter
+end # Unparser
diff --git a/lib/unparser/emitter/yield.rb b/lib/unparser/emitter/yield.rb
index a457b12..4b6f8df 100644
--- a/lib/unparser/emitter/yield.rb
+++ b/lib/unparser/emitter/yield.rb
@@ -5,20 +5,12 @@ module Unparser
 
     # Emitter for yield node
     class Yield < self
-      include Terminated
-
       handle :yield
 
     private
 
-      # Perform dispatch
-      #
-      # @return [undefined]
-      #
-      # @api private
-      #
       def dispatch
-        write(K_YIELD)
+        write('yield')
         return if children.empty?
 
         parentheses do
diff --git a/lib/unparser/equalizer.rb b/lib/unparser/equalizer.rb
new file mode 100644
index 0000000..ab7e1c7
--- /dev/null
+++ b/lib/unparser/equalizer.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Define equality, equivalence and inspection methods
+  #
+  # Original code before vendoring and reduction from: https://github.com/dkubb/equalizer.
+  class Equalizer < Module
+    # Initialize an Equalizer with the given keys
+    #
+    # Will use the keys with which it is initialized to define #cmp?,
+    # #hash, and #inspect
+    #
+    # @param [Array<Symbol>] keys
+    #
+    # @return [undefined]
+    #
+    # @api private
+    #
+    # rubocop:disable Lint/MissingSuper
+    def initialize(*keys)
+      @keys = keys
+      define_methods
+      freeze
+    end
+    # rubocop:enable Lint/MissingSuper
+
+    private
+
+    def included(descendant)
+      descendant.include(Methods)
+    end
+
+    def define_methods
+      define_cmp_method
+      define_hash_method
+      define_inspect_method
+    end
+
+    def define_cmp_method
+      keys = @keys
+      define_method(:cmp?) do |comparator, other|
+        keys.all? do |key|
+          __send__(key).public_send(comparator, other.__send__(key))
+        end
+      end
+      private :cmp?
+    end
+
+    def define_hash_method
+      keys = @keys
+      define_method(:hash) do
+        keys.map(&public_method(:__send__)).push(self.class).hash
+      end
+    end
+
+    def define_inspect_method
+      keys = @keys
+      define_method(:inspect) do
+        klass = self.class
+        name  = klass.name || klass.inspect
+        "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
+      end
+    end
+
+    # The comparison methods
+    module Methods
+      # Compare the object with other object for equality
+      #
+      # @example
+      #   object.eql?(other)  # => true or false
+      #
+      # @param [Object] other
+      #   the other object to compare with
+      #
+      # @return [Boolean]
+      #
+      # @api public
+      def eql?(other)
+        instance_of?(other.class) && cmp?(__method__, other)
+      end
+
+      # Compare the object with other object for equivalency
+      #
+      # @example
+      #   object == other  # => true or false
+      #
+      # @param [Object] other
+      #   the other object to compare with
+      #
+      # @return [Boolean]
+      #
+      # @api public
+      def ==(other)
+        instance_of?(other.class) && cmp?(__method__, other)
+      end
+    end # module Methods
+  end # class Equalizer
+end # Unparser
diff --git a/lib/unparser/generation.rb b/lib/unparser/generation.rb
new file mode 100644
index 0000000..e553c58
--- /dev/null
+++ b/lib/unparser/generation.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+module Unparser
+  # rubocop:disable Metrics/ModuleLength
+  module Generation
+    EXTRA_NL = %i[kwbegin def defs module class sclass].freeze
+
+    private_constant(*constants(false))
+
+    def emit_heredoc_reminders; end
+
+    def symbol_name; end
+
+    def write_to_buffer
+      with_comments { dispatch }
+      self
+    end
+
+  private
+
+    def delimited(nodes, delimiter = ', ', &block)
+      return if nodes.empty?
+
+      emit_join(nodes, block || method(:visit), -> { write(delimiter) })
+    end
+
+    def emit_join(nodes, emit_node, emit_delimiter)
+      return if nodes.empty?
+
+      head, *tail = nodes
+      emit_node.call(head)
+
+      tail.each do |node|
+        emit_delimiter.call
+        emit_node.call(node)
+      end
+    end
+
+    def nl
+      emit_eol_comments
+      buffer.nl
+    end
+
+    def with_comments
+      emit_comments_before if buffer.fresh_line?
+      yield
+      comments.consume(node)
+    end
+
+    def ws
+      write(' ')
+    end
+
+    def emit_eol_comments
+      comments.take_eol_comments.each do |comment|
+        write(' ', comment.text)
+      end
+    end
+
+    def emit_eof_comments
+      emit_eol_comments
+      comments_left = comments.take_all
+      return if comments_left.empty?
+
+      buffer.nl
+      emit_comments(comments_left)
+    end
+
+    def emit_comments_before(source_part = :expression)
+      comments_before = comments.take_before(node, source_part)
+      return if comments_before.empty?
+
+      emit_comments(comments_before)
+      buffer.nl
+    end
+
+    def emit_comments(comments)
+      max = comments.size - 1
+      comments.each_with_index do |comment, index|
+        if comment.type.equal?(:document)
+          buffer.append_without_prefix(comment.text.chomp)
+        else
+          write(comment.text)
+        end
+        buffer.nl if index < max
+      end
+    end
+
+    def write(*strings)
+      strings.each(&buffer.method(:append))
+    end
+
+    def k_end
+      buffer.indent
+      emit_comments_before(:end)
+      buffer.unindent
+      write('end')
+    end
+
+    def parentheses(open = '(', close = ')')
+      write(open)
+      yield
+      write(close)
+    end
+
+    def indented
+      buffer = buffer()
+      buffer.indent
+      nl
+      yield
+      nl
+      buffer.unindent
+    end
+
+    def emit_optional_body(node, indent: true)
+      if node
+        emit_body(node, indent: indent)
+      else
+        nl
+      end
+    end
+
+    def emit_body(node, indent: true)
+      if indent
+        buffer.indent
+        nl
+      end
+
+      if n_begin?(node)
+        if node.children.one?
+          visit_deep(node)
+        else
+          emit_body_inner(node)
+        end
+      else
+        visit_deep(node)
+      end
+
+      if indent
+        buffer.unindent
+        nl
+      end
+    end
+
+    def emit_body_inner(node)
+      head, *tail = node.children
+      emit_body_member(head)
+
+      tail.each do |child|
+        nl
+
+        nl if EXTRA_NL.include?(child.type)
+
+        emit_body_member(child)
+      end
+    end
+
+    def emit_body_member(node)
+      if n_rescue?(node)
+        emit_rescue_postcontrol(node)
+      else
+        visit_deep(node)
+      end
+    end
+
+    def emit_ensure(node)
+      body, ensure_body = node.children
+
+      if body
+        emit_body_rescue(body)
+      else
+        nl
+      end
+
+      write('ensure')
+
+      emit_optional_body(ensure_body)
+    end
+
+    def emit_body_rescue(node)
+      if n_rescue?(node)
+        emit_rescue_regular(node)
+      else
+        emit_body(node)
+      end
+    end
+
+    def emit_optional_body_ensure_rescue(node)
+      if node
+        emit_body_ensure_rescue(node)
+      else
+        nl
+      end
+    end
+
+    def emit_body_ensure_rescue(node)
+      if n_ensure?(node)
+        emit_ensure(node)
+      elsif n_rescue?(node)
+        emit_rescue_regular(node)
+      else
+        emit_body(node)
+      end
+    end
+
+    def emit_rescue_postcontrol(node)
+      writer = writer_with(Writer::Rescue, node)
+      writer.emit_postcontrol
+      writer.emit_heredoc_reminders
+    end
+
+    def emit_rescue_regular(node)
+      writer_with(Writer::Rescue, node).emit_regular
+    end
+
+    def writer_with(klass, node)
+      klass.new(to_h.merge(node: node))
+    end
+
+    def emitter(node)
+      Emitter.emitter(**to_h.merge(node: node))
+    end
+
+    def visit(node)
+      emitter(node).write_to_buffer
+    end
+
+    def visit_deep(node)
+      emitter(node).tap do |emitter|
+        emitter.write_to_buffer
+        emitter.emit_heredoc_reminders
+      end
+    end
+
+    def first_child
+      children.first
+    end
+
+    def conditional_parentheses(flag, &block)
+      if flag
+        parentheses(&block)
+      else
+        block.call
+      end
+    end
+
+    def children
+      node.children
+    end
+  end # Generation
+  # rubocop:enable Metrics/ModuleLength
+end # Unparser
diff --git a/lib/unparser/node_details.rb b/lib/unparser/node_details.rb
new file mode 100644
index 0000000..5193304
--- /dev/null
+++ b/lib/unparser/node_details.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unparser
+  module NodeDetails
+    include Constants, NodeHelpers
+
+    def self.included(descendant)
+      descendant.class_eval do
+        include Adamantium, Concord.new(:node)
+
+        extend DSL
+      end
+    end
+
+    private
+
+    def children
+      node.children
+    end
+  end # NodeDetails
+end # Unparser
diff --git a/lib/unparser/node_details/send.rb b/lib/unparser/node_details/send.rb
new file mode 100644
index 0000000..082d942
--- /dev/null
+++ b/lib/unparser/node_details/send.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Unparser
+  module NodeDetails
+    class Send
+      include NodeDetails
+
+      ASSIGN_SUFFIX    = '='.freeze
+      NON_ASSIGN_RANGE = (0..-2).freeze
+
+      private_constant(*constants(false))
+
+      children :receiver, :selector
+
+      public :receiver, :selector
+
+      def selector_binary_operator?
+        BINARY_OPERATORS.include?(selector)
+      end
+
+      def binary_syntax_allowed?
+        selector_binary_operator? \
+          && n_send?(node) \
+          && arguments.one? \
+          && !n_splat?(arguments.first) \
+          && !n_kwargs?(arguments.first)
+      end
+
+      def selector_unary_operator?
+        UNARY_OPERATORS.include?(selector)
+      end
+
+      def assignment_operator?
+        assignment? && !selector_binary_operator? && !selector_unary_operator?
+      end
+
+      def arguments?
+        arguments.any?
+      end
+
+      def non_assignment_selector
+        if assignment?
+          string_selector[NON_ASSIGN_RANGE]
+        else
+          string_selector
+        end
+      end
+
+      def assignment?
+        string_selector[-1].eql?(ASSIGN_SUFFIX)
+      end
+      memoize :assignment?
+
+      def arguments
+        children[2..]
+      end
+      memoize :arguments
+
+      def string_selector
+        selector.to_s
+      end
+      memoize :string_selector
+
+    end # Send
+  end # NodeDetails
+end # Unparser
diff --git a/lib/unparser/node_helpers.rb b/lib/unparser/node_helpers.rb
index 926feae..bfb8bb7 100644
--- a/lib/unparser/node_helpers.rb
+++ b/lib/unparser/node_helpers.rb
@@ -11,9 +11,6 @@ module Unparser
     # @return [Parser::AST::Node]
     #
     # @api private
-    #
-    # ignore :reek:UncommunicativeMethodName
-    # ignore :reek:UtilityFunction
     def s(type, *children)
       Parser::AST::Node.new(type, children)
     end
@@ -26,12 +23,57 @@ module Unparser
     # @param [Array] children
     #
     # @api private
-    #
-    # ignore :reek:UncommunicativeMethodName
-    # ignore :reek:UtilityFunction
     def n(type, children = [])
       Parser::AST::Node.new(type, children)
     end
 
+    def n?(type, node)
+      node.type.equal?(type)
+    end
+
+    %i[
+      arg
+      args
+      array
+      array_pattern
+      begin
+      block
+      cbase
+      const
+      dstr
+      empty_else
+      ensure
+      hash
+      hash_pattern
+      if
+      in_pattern
+      int
+      kwarg
+      kwargs
+      kwsplat
+      lambda
+      match_rest
+      pair
+      rescue
+      send
+      shadowarg
+      splat
+      str
+      sym
+    ].each do |type|
+      name = "n_#{type}?"
+      define_method(name) do |node|
+        n?(type, node)
+      end
+      private(name)
+    end
+
+    def unwrap_single_begin(node)
+      if n_begin?(node) && node.children.one?
+        node.children.first
+      else
+        node
+      end
+    end
   end # NodeHelpers
 end # Unparser
diff --git a/lib/unparser/preprocessor.rb b/lib/unparser/preprocessor.rb
deleted file mode 100644
index 7400661..0000000
--- a/lib/unparser/preprocessor.rb
+++ /dev/null
@@ -1,159 +0,0 @@
-# frozen_string_literal: true
-
-module Unparser
-  # Preprocessor to normalize AST generated by parser
-  class Preprocessor
-    include Adamantium::Flat, NodeHelpers, AbstractType, Concord.new(:node, :parent_type), Procto.call(:result)
-
-    # Return preprocessor result
-    #
-    # @return [Parser::AST::Node]
-    #
-    # @api private
-    #
-    abstract_method :result
-
-    EMPTY = Parser::AST::Node.new(:empty)
-
-    # Run preprocessor for node
-    #
-    # @param [Parser::AST::Node, nil] node
-    #
-    # @return [Parser::AST::Node, nil]
-    #
-    # @api private
-    #
-    def self.run(node, parent_type = nil)
-      return EMPTY if node.nil?
-
-      REGISTRY.fetch(node.type, [Noop]).reduce(node) do |current, processor|
-        processor.call(current, parent_type)
-      end
-    end
-
-    REGISTRY = Hash.new { |hash, key| hash[key] = [] }
-
-    # Register preprocessor
-    #
-    # @param [Symbol] type
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def self.register(type)
-      REGISTRY[type] << self
-    end
-    private_class_method :register
-
-  private
-
-    # Visit node
-    #
-    # @param [Parser::AST::Node] child
-    #
-    # @return [undefined]
-    #
-    # @api private
-    #
-    def visit(child)
-      self.class.run(child, node.type)
-    end
-
-    # Return children
-    #
-    # @return [Array<Parser::AST::Node>]
-    #
-    # @api private
-    #
-    def children
-      node.children
-    end
-
-    # Return visited children
-    #
-    # @return [Array<Parser::Ast::Node>]
-    #
-    # @api private
-    #
-    def visited_children
-      children.map do |node|
-        if node.is_a?(Parser::AST::Node)
-          visit(node)
-        else
-          node
-        end
-      end
-    end
-
-    # Noop preprocessor that just passes node through.
-    class Noop < self
-
-      register :int
-      register :str
-
-      # Return preprocessor result
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
-      def result
-        node.updated(nil, visited_children)
-      end
-
-    end # Noop
-
-    # Preprocessor transforming numeric nodes with infinity as value to round trippable equivalent.
-    class Infinity < self
-
-      register :float
-      register :int
-
-      NEG_INFINITY = -(Float::INFINITY - 1)
-
-      # Return preprocessor result
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
-      def result
-        value = node.children.first
-        case value
-        when Float::INFINITY
-          s(:const, s(:const, nil, :Float), :INFINITY)
-        when NEG_INFINITY
-          s(:send, s(:const, s(:const, nil, :Float), :INFINITY), :-@)
-        else
-          node
-        end
-      end
-    end
-
-    # Preprocessor for begin nodes. Removes begin nodes with one child.
-    #
-    # This reduces the amount of complex logic needed inside unparser to emit "nice" syntax with minimal
-    # tokens.
-    #
-    class Begin < self
-
-      register :begin
-
-      # Return preprocessor result
-      #
-      # @return [Parser::AST::Node]
-      #
-      # @api private
-      #
-      def result
-        if children.one? && !parent_type.equal?(:regexp)
-          visit(children.first)
-        else
-          Noop.call(node, parent_type)
-        end
-      end
-
-    end # Begin
-  end # Preprocessor
-end # Unparser
diff --git a/lib/unparser/validation.rb b/lib/unparser/validation.rb
new file mode 100644
index 0000000..b660079
--- /dev/null
+++ b/lib/unparser/validation.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+module Unparser
+  # Validation of unparser results
+  class Validation
+    include Adamantium, Anima.new(
+      :generated_node,
+      :generated_source,
+      :identification,
+      :original_node,
+      :original_source
+    )
+
+    # Test if source could be unparsed successfully
+    #
+    # @return [Boolean]
+    #
+    # @api private
+    #
+    # rubocop:disable Style/OperatorMethodCall
+    def success?
+      [
+        original_source,
+        original_node,
+        generated_source,
+        generated_node
+      ].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
+    end
+    # rubocop:enable Style/OperatorMethodCall
+
+    # Return error report
+    #
+    # @return [String]
+    #
+    # @api private
+    #
+    def report
+      message = [identification]
+
+      message.concat(make_report('Original-Source',  :original_source))
+      message.concat(make_report('Generated-Source', :generated_source))
+      message.concat(make_report('Original-Node',    :original_node))
+      message.concat(make_report('Generated-Node',   :generated_node))
+      message.concat(node_diff_report)
+
+      message.join("\n")
+    end
+    memoize :report
+
+    # Create validator from string
+    #
+    # @param [String] original_source
+    #
+    # @return [Validator]
+    def self.from_string(original_source)
+      original_node = Unparser
+        .parse_either(original_source)
+
+      generated_source = original_node
+        .lmap(&method(:const_unit))
+        .bind(&Unparser.method(:unparse_either))
+
+      generated_node = generated_source
+        .lmap(&method(:const_unit))
+        .bind(&Unparser.method(:parse_either))
+
+      new(
+        identification:   '(string)',
+        original_source:  Either::Right.new(original_source),
+        original_node:    original_node,
+        generated_source: generated_source,
+        generated_node:   generated_node
+      )
+    end
+
+    # Create validator from node
+    #
+    # @param [Parser::AST::Node] original_node
+    #
+    # @return [Validator]
+    def self.from_node(original_node)
+      generated_source = Unparser.unparse_either(original_node)
+
+      generated_node = generated_source
+        .lmap(&method(:const_unit))
+        .bind(&Unparser.public_method(:parse_either))
+
+      new(
+        identification:   '(string)',
+        original_source:  generated_source,
+        original_node:    Either::Right.new(original_node),
+        generated_source: generated_source,
+        generated_node:   generated_node
+      )
+    end
+
+    # Create validator from file
+    #
+    # @param [Pathname] path
+    #
+    # @return [Validator]
+    def self.from_path(path)
+      from_string(path.read).with(identification: path.to_s)
+    end
+
+  private
+
+    def make_report(label, attribute_name)
+      ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
+    end
+
+    def report_exception(exception)
+      if exception
+        [exception.inspect].concat(exception.backtrace.take(20))
+      else
+        ['undefined']
+      end
+    end
+
+    def node_diff_report
+      diff = nil
+
+      original_node.fmap do |original|
+        generated_node.fmap do |generated|
+          diff = Diff.new(
+            original.to_s.lines.map(&:chomp),
+            generated.to_s.lines.map(&:chomp)
+          ).colorized_diff
+        end
+      end
+
+      diff ? ['Node-Diff:', diff] : []
+    end
+
+    def self.const_unit(_value); end
+    private_class_method :const_unit
+
+    class Literal < self
+      def success?
+        original_source.eql?(generated_source)
+      end
+
+      def report
+        message = [identification]
+
+        message.concat(make_report('Original-Source',  :original_source))
+        message.concat(make_report('Generated-Source', :generated_source))
+        message.concat(make_report('Original-Node',    :original_node))
+        message.concat(make_report('Generated-Node',   :generated_node))
+        message.concat(node_diff_report)
+        message.concat(source_diff_report)
+
+        message.join("\n")
+      end
+
+    private
+
+      def source_diff_report
+        diff = nil
+
+        original_source.fmap do |original|
+          generated_source.fmap do |generated|
+            diff = Diff.new(
+              original.split("\n", -1),
+              generated.split("\n", -1)
+            ).colorized_diff
+          end
+        end
+
+        diff ? ['Source-Diff:', diff] : []
+      end
+    end # Literal
+  end # Validation
+end # Unparser
diff --git a/lib/unparser/writer.rb b/lib/unparser/writer.rb
new file mode 100644
index 0000000..94b95bf
--- /dev/null
+++ b/lib/unparser/writer.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    include Generation, NodeHelpers
+
+    def self.included(descendant)
+      descendant.class_eval do
+        include Anima.new(:buffer, :comments, :node, :local_variable_scope)
+
+        extend DSL
+      end
+    end
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/binary.rb b/lib/unparser/writer/binary.rb
new file mode 100644
index 0000000..db2025a
--- /dev/null
+++ b/lib/unparser/writer/binary.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class Binary
+      include Writer, Adamantium
+
+      children :left, :right
+
+      OPERATOR_TOKENS =
+        {
+          and: '&&',
+          or:  '||'
+        }.freeze
+
+      KEYWORD_TOKENS =
+        {
+          and: 'and',
+          or:  'or'
+        }.freeze
+
+      KEYWORD_SYMBOLS =
+        {
+          and: :kAND,
+          or:  :kOR
+        }.freeze
+
+      OPERATOR_SYMBOLS =
+        {
+          and: :tANDOP,
+          or:  :tOROP
+        }.freeze
+
+      MAP =
+        {
+          kAND:   'and',
+          kOR:    'or',
+          tOROP:  '||',
+          tANDOP: '&&'
+        }.freeze
+
+      NEED_KEYWORD = %i[return break next].freeze
+
+      private_constant(*constants(false))
+
+      def emit_operator
+        emit_with(OPERATOR_TOKENS)
+      end
+
+      def symbol_name
+        true
+      end
+
+      def dispatch
+        left_emitter.write_to_buffer
+        write(' ', MAP.fetch(effective_symbol), ' ')
+        visit(right)
+      end
+
+    private
+
+      def effective_symbol
+        if NEED_KEYWORD.include?(right.type) || NEED_KEYWORD.include?(left.type)
+          return keyword_symbol
+        end
+
+        unless left_emitter.symbol_name
+          return operator_symbol
+        end
+
+        keyword_symbol
+      end
+
+      def emit_with(map)
+        visit(left)
+        write(' ', map.fetch(node.type), ' ')
+        visit(right)
+      end
+
+      def keyword_symbol
+        KEYWORD_SYMBOLS.fetch(node.type)
+      end
+
+      def operator_symbol
+        OPERATOR_SYMBOLS.fetch(node.type)
+      end
+
+      def left_emitter
+        emitter(left)
+      end
+      memoize :left_emitter
+
+      def right_emitter
+        emitter(right)
+      end
+      memoize :right_emitter
+    end # Binary
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/dynamic_string.rb b/lib/unparser/writer/dynamic_string.rb
new file mode 100644
index 0000000..fec4627
--- /dev/null
+++ b/lib/unparser/writer/dynamic_string.rb
@@ -0,0 +1,211 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class DynamicString
+      include Writer, Adamantium
+
+      PATTERNS_2 =
+        [
+          %i[str_empty begin].freeze,
+          %i[begin str_nl].freeze
+        ].freeze
+
+      PATTERNS_3 =
+        [
+          %i[begin str_nl_eol str_nl_eol].freeze,
+          %i[str_nl_eol begin str_nl_eol].freeze,
+          %i[str_ws begin str_nl_eol].freeze
+        ].freeze
+
+      FLAT_INTERPOLATION = %i[ivar cvar gvar nth_ref].to_set.freeze
+
+      private_constant(*constants(false))
+
+      def emit_heredoc_reminder
+        return unless heredoc?
+
+        emit_heredoc_body
+        emit_heredoc_footer
+      end
+
+      def dispatch
+        if heredoc?
+          emit_heredoc_header
+        else
+          emit_dstr
+        end
+      end
+
+    private
+
+      def heredoc_header
+        '<<-HEREDOC'
+      end
+
+      def heredoc?
+        !children.empty? && (nl_last_child? && heredoc_pattern?)
+      end
+
+      def emit_heredoc_header
+        write(heredoc_header)
+      end
+
+      def emit_heredoc_body
+        nl
+        emit_normal_heredoc_body
+      end
+
+      def emit_heredoc_footer
+        write('HEREDOC')
+      end
+
+      def classify(node)
+        if n_str?(node)
+          classify_str(node)
+        else
+          node.type
+        end
+      end
+
+      def classify_str(node)
+        if str_nl?(node)
+          :str_nl
+        elsif node.children.first.end_with?("\n")
+          :str_nl_eol
+        elsif str_ws?(node)
+          :str_ws
+        elsif str_empty?(node)
+          :str_empty
+        end
+      end
+
+      def str_nl?(node)
+        node.eql?(s(:str, "\n"))
+      end
+
+      def str_empty?(node)
+        node.eql?(s(:str, ''))
+      end
+
+      def str_ws?(node)
+        /\A( |\t)+\z/.match?(node.children.first)
+      end
+
+      def heredoc_pattern?
+        heredoc_pattern_2? || heredoc_pattern_3?
+      end
+
+      def heredoc_pattern_3?
+        children.each_cons(3).any? do |group|
+          PATTERNS_3.include?(group.map(&method(:classify)))
+        end
+      end
+
+      def heredoc_pattern_2?
+        children.each_cons(2).any? do |group|
+          PATTERNS_2.include?(group.map(&method(:classify)))
+        end
+      end
+
+      def nl_last_child?
+        last = children.last
+        n_str?(last) && last.children.first[-1].eql?("\n")
+      end
+
+      def emit_normal_heredoc_body
+        buffer.root_indent do
+          children.each do |child|
+            if n_str?(child)
+              write(escape_dynamic(child.children.first))
+            else
+              emit_dynamic(child)
+            end
+          end
+        end
+      end
+
+      def escape_dynamic(string)
+        string.gsub('#', '\#')
+      end
+
+      def emit_dynamic(child)
+        if FLAT_INTERPOLATION.include?(child.type)
+          write('#')
+          visit(child)
+        elsif n_dstr?(child)
+          emit_body(child.children)
+        else
+          write('#{')
+          emit_dynamic_component(child.children.first)
+          write('}')
+        end
+      end
+
+      def emit_dynamic_component(node)
+        visit(node) if node
+      end
+
+      def emit_dstr
+        if children.empty?
+          write('%()')
+        else
+          segments.each_with_index do |children, index|
+            emit_segment(children, index)
+          end
+        end
+      end
+
+      def breakpoint?(child, current)
+        last_type = current.last&.type
+
+        [
+          n_str?(child) && last_type.equal?(:str) && current.none?(&method(:n_begin?)),
+          last_type.equal?(:dstr),
+          n_dstr?(child) && last_type
+        ].any?
+      end
+
+      def segments
+        segments = []
+
+        segments << current = []
+
+        children.each do |child|
+          if breakpoint?(child, current)
+            segments << current = []
+          end
+
+          current << child
+        end
+
+        segments
+      end
+
+      def emit_segment(children, index)
+        write(' ') unless index.zero?
+
+        write('"')
+        emit_body(children)
+        write('"')
+      end
+
+      def emit_body(children)
+        buffer.root_indent do
+          children.each_with_index do |child, index|
+            if n_str?(child)
+              string = child.children.first
+              if string.eql?("\n") && children.fetch(index.pred).type.equal?(:begin)
+                write("\n")
+              else
+                write(string.inspect[1..-2])
+              end
+            else
+              emit_dynamic(child)
+            end
+          end
+        end
+      end
+    end # DynamicString
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/resbody.rb b/lib/unparser/writer/resbody.rb
new file mode 100644
index 0000000..bc38616
--- /dev/null
+++ b/lib/unparser/writer/resbody.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    # Writer for rescue bodies
+    class Resbody
+      include Writer
+
+      children :exception, :assignment, :body
+
+      def emit_postcontrol
+        write(' rescue ')
+        visit(body)
+      end
+
+      def emit_regular
+        write('rescue')
+        emit_exception
+        emit_assignment
+        emit_optional_body(body)
+      end
+
+    private
+
+      def emit_exception
+        return unless exception
+
+        ws
+        delimited(exception.children)
+      end
+
+      def emit_assignment
+        return unless assignment
+
+        write(' => ')
+        visit(assignment)
+      end
+    end # Resbody
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/rescue.rb b/lib/unparser/writer/rescue.rb
new file mode 100644
index 0000000..892b2fb
--- /dev/null
+++ b/lib/unparser/writer/rescue.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class Rescue
+      include Writer, Adamantium
+
+      children :body, :rescue_body
+
+      define_group :rescue_bodies, 1..-2
+
+      def emit_regular
+        emit_optional_body(body)
+
+        rescue_bodies.each(&method(:emit_rescue_body))
+
+        if else_node
+          write('else')
+          emit_body(else_node)
+        end
+      end
+
+      def emit_heredoc_reminders
+        emitter(body).emit_heredoc_reminders
+      end
+
+      def emit_postcontrol
+        visit(body)
+        writer_with(Resbody, rescue_body).emit_postcontrol
+      end
+
+    private
+
+      def else_node
+        children.last
+      end
+
+      def emit_rescue_body(node)
+        writer_with(Resbody, node).emit_regular
+      end
+    end # Rescue
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send.rb b/lib/unparser/writer/send.rb
new file mode 100644
index 0000000..f75d30e
--- /dev/null
+++ b/lib/unparser/writer/send.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    # Writer for send
+    class Send
+      include Writer, Adamantium, Constants, Generation
+
+      INDEX_ASSIGN    = :[]=
+      INDEX_REFERENCE = :[]
+
+      OPERATORS = {
+        csend: '&.',
+        send:  '.'
+      }.freeze
+
+      private_constant(*constants(false))
+
+      children :receiver, :selector
+
+      def dispatch
+        effective_writer.dispatch
+      end
+
+      def emit_mlhs
+        effective_writer.emit_send_mlhs
+      end
+
+      def emit_selector
+        write(details.string_selector)
+      end
+
+      def emit_heredoc_reminders
+        emitter(receiver).emit_heredoc_reminders if receiver
+        arguments.each(&method(:emit_heredoc_reminder))
+      end
+
+    private
+
+      def effective_writer
+        writer_with(effective_writer_class, node)
+      end
+      memoize :effective_writer
+
+      def effective_writer_class
+        if details.binary_syntax_allowed?
+          Binary
+        elsif details.selector_unary_operator? && n_send?(node) && arguments.empty?
+          Unary
+        elsif write_as_attribute_assignment?
+          AttributeAssignment
+        else
+          Regular
+        end
+      end
+
+      def write_as_attribute_assignment?
+        details.assignment_operator?
+      end
+
+      def emit_operator
+        write(OPERATORS.fetch(node.type))
+      end
+
+      def emit_arguments
+        if arguments.empty?
+          write('()') if receiver.nil? && avoid_clash?
+        else
+          emit_normal_arguments
+        end
+      end
+
+      def arguments
+        details.arguments
+      end
+
+      def emit_normal_arguments
+        parentheses { delimited(arguments) }
+      end
+
+      def emit_heredoc_reminder(argument)
+        emitter(argument).emit_heredoc_reminders
+      end
+
+      def avoid_clash?
+        local_variable_clash? || parses_as_constant?
+      end
+
+      def local_variable_clash?
+        local_variable_scope.local_variable_defined_for_node?(node, selector)
+      end
+
+      def parses_as_constant?
+        test = Unparser.parse_either(selector.to_s).from_right do
+          fail InvalidNodeError.new("Invalid selector for send node: #{selector.inspect}", node)
+        end
+
+        n_const?(test)
+      end
+
+      def details
+        NodeDetails::Send.new(node)
+      end
+      memoize :details
+
+      def emit_send_regular(node)
+        if n_send?(node)
+          writer_with(Regular, node).dispatch
+        else
+          visit(node)
+        end
+      end
+    end # Send
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/emitter/send/attribute_assignment.rb b/lib/unparser/writer/send/attribute_assignment.rb
similarity index 51%
rename from lib/unparser/emitter/send/attribute_assignment.rb
rename to lib/unparser/writer/send/attribute_assignment.rb
index 569d577..fe404c5 100644
--- a/lib/unparser/emitter/send/attribute_assignment.rb
+++ b/lib/unparser/writer/send/attribute_assignment.rb
@@ -1,24 +1,16 @@
 # frozen_string_literal: true
 
 module Unparser
-  class Emitter
+  module Writer
     class Send
-      # Emitter for send as attribute assignment
+      # Writer for send as attribute assignment
       class AttributeAssignment < self
-        include Unterminated
-
         children :receiver, :selector, :first_argument
 
-        # Perform regular dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def dispatch
           emit_receiver
           emit_attribute
-          write(T_ASN)
+          write('=')
 
           if arguments.one?
             visit(first_argument)
@@ -27,29 +19,22 @@ module Unparser
           end
         end
 
+        def emit_send_mlhs
+          emit_receiver
+          write(details.non_assignment_selector)
+        end
+
       private
 
-        # Emit receiver
-        #
-        # @return [Parser::AST::Node]
-        #
-        # @api private
-        #
         def emit_receiver
           visit(receiver)
-          write(T_DOT)
+          emit_operator
         end
 
-        # Emit attribute
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def emit_attribute
-          write(non_assignment_selector)
+          write(details.non_assignment_selector)
         end
       end # AttributeAssignment
     end # Send
-  end # Emitter
+  end # Writer
 end # Unparser
diff --git a/lib/unparser/writer/send/binary.rb b/lib/unparser/writer/send/binary.rb
new file mode 100644
index 0000000..c3cc46b
--- /dev/null
+++ b/lib/unparser/writer/send/binary.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class Send
+      # Writer for binary sends
+      class Binary < self
+        def dispatch
+          visit(receiver)
+          emit_operator
+          emit_right
+        end
+
+      private
+
+        def emit_operator
+          write(' ', details.string_selector, ' ')
+        end
+
+        def emit_right
+          emit_send_regular(children.fetch(2))
+        end
+
+      end # Binary
+    end # Send
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send/conditional.rb b/lib/unparser/writer/send/conditional.rb
new file mode 100644
index 0000000..c3ddcf6
--- /dev/null
+++ b/lib/unparser/writer/send/conditional.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class Send
+      # Writer for "conditional" receiver&.selector(arguments...) case
+      class Conditional < self
+
+      private
+
+        def dispatch
+          emit_receiver
+          emit_selector
+          emit_arguments
+        end
+
+        def emit_receiver
+          visit(receiver)
+          write('&.')
+        end
+
+      end # Regular
+    end # Send
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/writer/send/regular.rb b/lib/unparser/writer/send/regular.rb
new file mode 100644
index 0000000..f5642b7
--- /dev/null
+++ b/lib/unparser/writer/send/regular.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Unparser
+  module Writer
+    class Send
+      # Writer for "regular" receiver.selector(arguments...) case
+      class Regular < self
+        def dispatch
+          emit_receiver
+          emit_selector
+          emit_arguments
+        end
+
+        def emit_send_mlhs
+          dispatch
+        end
+
+        def emit_arguments_without_heredoc_body
+          emit_normal_arguments if arguments.any?
+        end
+
+        def emit_receiver
+          return unless receiver
+
+          emit_send_regular(receiver)
+
+          emit_operator
+        end
+
+      end # Regular
+    end # Send
+  end # Writer
+end # Unparser
diff --git a/lib/unparser/emitter/send/unary.rb b/lib/unparser/writer/send/unary.rb
similarity index 51%
rename from lib/unparser/emitter/send/unary.rb
rename to lib/unparser/writer/send/unary.rb
index 906b9e3..ed40961 100644
--- a/lib/unparser/emitter/send/unary.rb
+++ b/lib/unparser/writer/send/unary.rb
@@ -1,36 +1,29 @@
 # frozen_string_literal: true
 
 module Unparser
-  class Emitter
+  module Writer
     class Send
-      # Emitter for unary sends
+      # Writer for unary sends
       class Unary < self
-        include Unterminated
-
-      private
-
-        MAP = IceNine.deep_freeze(
+        MAP = {
           '-@': '-',
           '+@': '+'
-        )
+        }.freeze
+
+        private_constant(*constants(false))
 
-        # Perform dispatch
-        #
-        # @return [undefined]
-        #
-        # @api private
-        #
         def dispatch
           name = selector
+
           write(MAP.fetch(name, name).to_s)
-          if receiver.type.equal?(:int) && selector.equal?(:'+@')
+
+          if n_int?(receiver) && selector.equal?(:+@)
             write('+')
           end
 
           visit(receiver)
         end
-
       end # Unary
     end # Send
-  end # Emitter
+  end # Writer
 end # Unparser
diff --git a/spec/integration/unparser/corpus_spec.rb b/spec/integration/unparser/corpus_spec.rb
deleted file mode 100644
index 58cc86f..0000000
--- a/spec/integration/unparser/corpus_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-describe 'Unparser on ruby corpus', mutant: false do
-  ROOT = Pathname.new(__FILE__).parent.parent.parent.parent
-
-  TMP = ROOT.join('tmp')
-
-  class Project
-    include Anima.new(:name, :repo_uri, :repo_ref, :exclude)
-
-    # Perform verification via unparser cli
-    #
-    # @return [self]
-    #   if successful
-    #
-    # @raise [Exception]
-    #   otherwise
-    #
-    def verify
-      checkout
-      command = %W(unparser #{repo_path})
-      exclude.each do |name|
-        command.concat(%W(--ignore #{repo_path.join(name)}))
-      end
-      system(command) do
-        raise "Verifing #{name} failed!"
-      end
-      self
-    end
-
-    # Checkout repository
-    #
-    # @return [self]
-    #
-    # @api private
-    #
-    def checkout
-      TMP.mkdir unless TMP.directory?
-      if repo_path.exist?
-        Dir.chdir(repo_path) do
-          system(%w(git pull origin master))
-          system(%w(git clean -f -d -x))
-        end
-      else
-        system(%W(git clone #{repo_uri} #{repo_path}))
-      end
-
-      Dir.chdir(repo_path) do
-        system(%W(git checkout #{repo_ref}))
-        system(%w(git reset --hard))
-        system(%w(git clean -f -d -x))
-      end
-
-      self
-    end
-
-  private
-
-    # Return repository path
-    #
-    # @return [Pathname]
-    #
-    # @api private
-    #
-    def repo_path
-      TMP.join(name)
-    end
-
-    # Helper method to execute system commands
-    #
-    # @param [Array<String>] arguments
-    #
-    # @api private
-    #
-    def system(arguments)
-      return if Kernel.system(*arguments)
-
-      if block_given?
-        yield
-      else
-        raise "System command #{arguments.inspect} failed!"
-      end
-    end
-
-    LOADER = Morpher.build do
-      s(:block,
-        s(:guard, s(:primitive, Array)),
-        s(:map,
-          s(:block,
-            s(:guard, s(:primitive, Hash)),
-            s(:hash_transform,
-              s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
-              s(:key_symbolize, :repo_ref, s(:guard, s(:primitive, String))),
-              s(:key_symbolize, :name,     s(:guard, s(:primitive, String))),
-              s(:key_symbolize, :exclude,  s(:map, s(:guard, s(:primitive, String))))),
-            s(:load_attribute_hash,
-              # NOTE: The domain param has no DSL currently!
-              Morpher::Evaluator::Transformer::Domain::Param.new(
-                Project,
-                [:repo_uri, :repo_ref, :name, :exclude]
-              )))))
-    end
-
-    ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
-  end
-
-  Project::ALL.each do |project|
-    specify "unparsing #{project.name}" do
-      project.verify
-    end
-  end
-end
diff --git a/spec/integrations.yml b/spec/integrations.yml
deleted file mode 100644
index e87ff54..0000000
--- a/spec/integrations.yml
+++ /dev/null
@@ -1,92 +0,0 @@
----
-- name: anima
-  repo_uri: 'https://github.com/mbj/anima.git'
-  repo_ref: 'origin/master'
-  exclude: []
-- name: mutant
-  repo_uri: 'https://github.com/mbj/mutant.git'
-  repo_ref: 'origin/master'
-  exclude: []
-- name: yaks
-  repo_uri: 'https://github.com/plexus/yaks.git'
-  repo_ref: 'origin/master'
-  exclude: []
-- name: chassis
-  repo_uri: 'https://github.com/ahawkins/chassis.git'
-  repo_ref: 'origin/master'
-  exclude: []
-- name: rubyspec
-  repo_uri: 'https://github.com/ruby/spec.git'
-  # Revision of rubyspec on the last CI build of unparser that passed
-  repo_ref: 'origin/master'
-  exclude:
-    - command_line/fixtures/bad_syntax.rb
-    - core/array/pack/shared/float.rb
-    - core/array/pack/shared/integer.rb
-    - core/array/pack/shared/string.rb
-    - core/array/pack/{b,c,h,m}_spec.rb
-    - core/array/pack/{u,w}_spec.rb
-    - core/encoding/compatible_spec.rb
-    - core/encoding/converter/convert_spec.rb
-    - core/encoding/converter/last_error_spec.rb
-    - core/encoding/converter/primitive_convert_spec.rb
-    - core/encoding/converter/primitive_errinfo_spec.rb
-    - core/encoding/converter/putback_spec.rb
-    - core/encoding/fixtures/classes.rb
-    - core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
-    - core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb
-    - core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb
-    - core/encoding/replicate_spec.rb
-    - core/env/element_reference_spec.rb
-    - core/io/readpartial_spec.rb
-    - core/io/shared/gets_ascii.rb
-    - core/kernel/shared/sprintf_encoding.rb
-    - core/marshal/dump_spec.rb
-    - core/marshal/fixtures/marshal_data.rb
-    - core/marshal/shared/load.rb
-    - core/random/bytes_spec.rb
-    - core/regexp/shared/new.rb
-    - core/regexp/shared/new_ascii.rb
-    - core/regexp/shared/new_ascii_8bit.rb
-    - core/regexp/shared/quote.rb
-    - core/string/byteslice_spec.rb
-    - core/string/casecmp_spec.rb
-    - core/string/codepoints_spec.rb
-    - core/string/count_spec.rb
-    - core/string/encode_spec.rb
-    - core/string/inspect_spec.rb
-    - core/string/shared/codepoints.rb
-    - core/string/shared/each_codepoint_without_block.rb
-    - core/string/shared/eql.rb
-    - core/string/shared/succ.rb
-    - core/string/shared/to_sym.rb
-    - core/string/squeeze_spec.rb
-    - core/string/unpack/shared/float.rb
-    - core/string/unpack/shared/integer.rb
-    - core/string/unpack/{b,c,h,m}_spec.rb
-    - core/string/unpack/{u,w}_spec.rb
-    - core/symbol/casecmp_spec.rb
-    - core/time/_dump_spec.rb
-    - core/time/_load_spec.rb
-    - language/fixtures/binary_symbol.rb
-    - language/fixtures/squiggly_heredoc.rb
-    - language/for_spec.rb
-    - language/regexp/encoding_spec.rb
-    - language/regexp/escapes_spec.rb
-    - language/source_encoding_spec.rb
-    - language/string_spec.rb
-    - library/base64/decode64_spec.rb
-    - library/digest/md5/shared/constants.rb
-    - library/digest/md5/shared/sample.rb
-    - library/digest/sha1/shared/constants.rb
-    - library/digest/sha256/shared/constants.rb
-    - library/digest/sha384/shared/constants.rb
-    - library/digest/sha512/shared/constants.rb
-    - library/openssl/shared/constants.rb
-    - library/socket/basicsocket/recv_spec.rb
-    - library/socket/socket/gethostbyname_spec.rb
-    - library/stringscanner/getch_spec.rb
-    - library/stringscanner/shared/get_byte.rb
-    - library/zlib/inflate/set_dictionary_spec.rb
-    - optional/capi/integer_spec.rb
-    - security/cve_2010_1330_spec.rb
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
deleted file mode 100644
index 83cb9b4..0000000
--- a/spec/spec_helper.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'yaml'
-require 'pathname'
-require 'unparser'
-require 'anima'
-require 'morpher'
-require 'devtools/spec_helper'
-
-require 'parser/current'
-
-module SpecHelper
-  def s(type, *children)
-    Parser::AST::Node.new(type, children)
-  end
-end
-
-RSpec.configure do |config|
-  config.include(SpecHelper)
-  config.extend(SpecHelper)
-  config.raise_errors_for_deprecations!
-end
diff --git a/spec/unit/unparser/buffer/append_spec.rb b/spec/unit/unparser/buffer/append_spec.rb
deleted file mode 100644
index 1c315b6..0000000
--- a/spec/unit/unparser/buffer/append_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#append' do
-  subject { object.append(string) }
-
-  let(:object) { described_class.new }
-  let(:string) { 'foo' }
-
-  specify do
-    expect { subject }.to change { object.content }.from('').to('foo')
-  end
-
-  # Yeah duplicate, mutant will be improved ;)
-  it 'should prefix with indentation if line is empty' do
-    object.append('foo')
-    object.nl
-    object.indent
-    object.append('bar')
-    object.append('baz')
-    expect(object.content).to eql("foo\n  barbaz")
-  end
-
-  it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/append_without_prefix_spec.rb b/spec/unit/unparser/buffer/append_without_prefix_spec.rb
deleted file mode 100644
index 695c1bb..0000000
--- a/spec/unit/unparser/buffer/append_without_prefix_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#append_without_prefix' do
-  subject { object.append_without_prefix(string) }
-
-  let(:object) { described_class.new }
-  let(:string) { 'foo' }
-
-  specify do
-    expect { subject }.to change { object.content }.from('').to('foo')
-  end
-
-  it 'should not prefix with indentation' do
-    object.append_without_prefix('foo')
-    object.nl
-    object.indent
-    object.append_without_prefix('bar')
-    object.append_without_prefix('baz')
-    expect(object.content).to eql("foo\nbarbaz")
-  end
-
-  it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/capture_content_spec.rb b/spec/unit/unparser/buffer/capture_content_spec.rb
deleted file mode 100644
index 8937e6d..0000000
--- a/spec/unit/unparser/buffer/capture_content_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#capture_content' do
-
-  let(:object) { described_class.new }
-
-  it 'should capture only the content appended within the block' do
-    object.append('foo')
-    object.nl
-    object.indent
-    captured = object.capture_content do
-      object.append('bar')
-      object.nl
-    end
-    expect(captured).to eql("  bar\n")
-  end
-end
diff --git a/spec/unit/unparser/buffer/content_spec.rb b/spec/unit/unparser/buffer/content_spec.rb
deleted file mode 100644
index 067d926..0000000
--- a/spec/unit/unparser/buffer/content_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#content' do
-  subject { object.content }
-
-  let(:object) { described_class.new }
-
-  shared_examples_for 'buffer content' do
-    it 'contains expected content' do
-      should eql(expected_content)
-    end
-
-    it { should be_frozen }
-
-    it 'returns fresh string copies' do
-      first  = object.content
-      second = object.content
-      expect(first).to eql(second)
-      expect(first).not_to be(second)
-    end
-  end
-
-  context 'with empty buffer' do
-    let(:expected_content) { '' }
-
-    it_should_behave_like 'buffer content'
-  end
-
-  context 'with filled buffer' do
-    before do
-      object.append('foo')
-    end
-
-    let(:expected_content) { 'foo' }
-
-    it_behaves_like 'buffer content'
-  end
-end
diff --git a/spec/unit/unparser/buffer/fresh_line_spec.rb b/spec/unit/unparser/buffer/fresh_line_spec.rb
deleted file mode 100644
index dc01499..0000000
--- a/spec/unit/unparser/buffer/fresh_line_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#fresh_line?' do
-  let(:object) { described_class.new }
-
-  it 'should return true while buffer is empty' do
-    expect(object.fresh_line?).to eql(true)
-  end
-
-  it 'should return false after content has been appended' do
-    object.append('foo')
-    expect(object.fresh_line?).to eql(false)
-  end
-
-  it 'should return true after a nl has been appended' do
-    object.append('foo')
-    object.nl
-    expect(object.fresh_line?).to eql(true)
-  end
-end
diff --git a/spec/unit/unparser/buffer/indent_spec.rb b/spec/unit/unparser/buffer/indent_spec.rb
deleted file mode 100644
index e38084b..0000000
--- a/spec/unit/unparser/buffer/indent_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#indent' do
-  let(:object) { described_class.new }
-
-  subject { object.indent }
-
-  it 'should indent with two spaces' do
-    object.append('foo')
-    object.nl
-    object.indent
-    object.append('bar')
-    object.nl
-    object.indent
-    object.append('baz')
-    expect(object.content).to eql("foo\n  bar\n    baz")
-  end
-
-  it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/nl_spec.rb b/spec/unit/unparser/buffer/nl_spec.rb
deleted file mode 100644
index 1917c04..0000000
--- a/spec/unit/unparser/buffer/nl_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#nl' do
-  let(:object) { described_class.new }
-
-  subject { object.nl }
-
-  it 'writes a newline' do
-    object.append('foo')
-    subject
-    object.append('bar')
-    expect(object.content).to eql("foo\nbar")
-  end
-
-  it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/buffer/unindent_spec.rb b/spec/unit/unparser/buffer/unindent_spec.rb
deleted file mode 100644
index cc8202e..0000000
--- a/spec/unit/unparser/buffer/unindent_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Buffer, '#unindent' do
-  let(:object) { described_class.new }
-
-  subject { object.unindent }
-
-  it 'unindents two chars' do
-    object.append('foo')
-    object.nl
-    object.indent
-    object.append('bar')
-    object.nl
-    object.unindent
-    object.append('baz')
-    expect(object.content).to eql("foo\n  bar\nbaz")
-  end
-
-  it_should_behave_like 'a command method'
-end
diff --git a/spec/unit/unparser/comments/consume_spec.rb b/spec/unit/unparser/comments/consume_spec.rb
deleted file mode 100644
index 57b1702..0000000
--- a/spec/unit/unparser/comments/consume_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#consume' do
-
-  let(:ast_and_comments) do
-    Unparser.parse_with_comments(<<~'RUBY')
-      def hi # EOL 1
-      end # EOL 2
-    RUBY
-  end
-  let(:ast)      { ast_and_comments[0] }
-  let(:comments) { ast_and_comments[1] }
-  let(:object)   { described_class.new(comments) }
-
-  it 'should cause further EOL comments to be returned' do
-    expect(object.take_eol_comments).to eql([])
-    object.consume(ast, :name)
-    expect(object.take_eol_comments).to eql([comments[0]])
-    object.consume(ast, :end)
-    expect(object.take_eol_comments).to eql([comments[1]])
-  end
-end
diff --git a/spec/unit/unparser/comments/take_all_spec.rb b/spec/unit/unparser/comments/take_all_spec.rb
deleted file mode 100644
index 594f672..0000000
--- a/spec/unit/unparser/comments/take_all_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_all' do
-
-  let(:ast_and_comments) do
-    Unparser.parse_with_comments(<<~'RUBY')
-      def hi # EOL 1
-      end # EOL 2
-    RUBY
-  end
-  let(:ast)      { ast_and_comments[0] }
-  let(:comments) { ast_and_comments[1] }
-  let(:object)   { described_class.new(comments) }
-
-  it 'should take all comments' do
-    expect(object.take_all).to eql(comments)
-    expect(object.take_all).to eql([])
-  end
-end
diff --git a/spec/unit/unparser/comments/take_before_spec.rb b/spec/unit/unparser/comments/take_before_spec.rb
deleted file mode 100644
index 288cccd..0000000
--- a/spec/unit/unparser/comments/take_before_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_before' do
-
-  let(:ast)      { ast_and_comments[0] }
-  let(:comments) { ast_and_comments[1] }
-  let(:object)   { described_class.new(comments) }
-
-  context 'usual case' do
-
-    let(:ast_and_comments) do
-      Unparser.parse_with_comments(<<~'RUBY')
-        def hi # EOL 1
-          # comment
-        end # EOL 2
-      RUBY
-    end
-
-    it 'should return no comments if none are before the node' do
-      expect(object.take_before(ast, :expression)).to eql([])
-    end
-
-    it 'should return only the comments that are before the specified part of the node' do
-      expect(object.take_before(ast, :end)).to eql(comments.first(2))
-      expect(object.take_all).to eql([comments[2]])
-    end
-  end
-
-  context 'when node does not respond to source part' do
-
-    let(:ast_and_comments) do
-      Unparser.parse_with_comments(<<~'RUBY')
-        expression ? :foo : :bar # EOL 1
-        # EOL 2
-      RUBY
-    end
-
-    it 'should return no comments if none are before the node' do
-      expect(object.take_before(ast, :expression)).to eql([])
-    end
-
-    it 'should return only the comments that are before the specified part of the node' do
-      expect(object.take_before(ast, :end)).to eql([])
-    end
-  end
-end
diff --git a/spec/unit/unparser/comments/take_eol_comments_spec.rb b/spec/unit/unparser/comments/take_eol_comments_spec.rb
deleted file mode 100644
index d2ff392..0000000
--- a/spec/unit/unparser/comments/take_eol_comments_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Comments, '#take_eol_comments' do
-
-  let(:ast_and_comments) do
-    Unparser.parse_with_comments(<<~'RUBY')
-      def hi # EOL 1
-      =begin
-      doc comment
-      =end
-      end # EOL 2
-    RUBY
-  end
-  let(:ast)              { ast_and_comments[0] }
-  let(:comments)         { ast_and_comments[1] }
-  let(:object)           { described_class.new(comments) }
-
-  it 'should return no comments if nothing has been consumed' do
-    expect(object.take_eol_comments).to eql([])
-  end
-
-  it 'should return comments once their line has been consumed' do
-    object.consume(ast, :name)
-    expect(object.take_eol_comments).to eql([comments[0]])
-  end
-
-  it 'should leave doc comments to be taken later' do
-    object.consume(ast)
-    expect(object.take_eol_comments).to eql([comments[0], comments[2]])
-    expect(object.take_all).to eql([comments[1]])
-  end
-end
diff --git a/spec/unit/unparser/emitter/class_methods/handle_spec.rb b/spec/unit/unparser/emitter/class_methods/handle_spec.rb
deleted file mode 100644
index 026697f..0000000
--- a/spec/unit/unparser/emitter/class_methods/handle_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Unparser::Emitter, '.handle', mutant_expression: 'Unparser::Emitter*' do
-  subject { class_under_test.class_eval { handle :foo } }
-
-  let(:class_under_test) do
-    Class.new(described_class)
-  end
-
-  before do
-    stub_const('Unparser::Emitter::REGISTRY', {})
-  end
-
-  it 'should register emitter' do
-    expect { subject }.to change { Unparser::Emitter::REGISTRY }.from({}).to(foo: class_under_test)
-  end
-end
diff --git a/spec/unit/unparser_spec.rb b/spec/unit/unparser_spec.rb
deleted file mode 100644
index d92a185..0000000
--- a/spec/unit/unparser_spec.rb
+++ /dev/null
@@ -1,1849 +0,0 @@
-require 'spec_helper'
-
-describe Unparser, mutant_expression: 'Unparser::Emitter*' do
-  describe '.buffer' do
-    let(:source) { 'a + b' }
-
-    def apply
-      described_class.buffer(source)
-    end
-
-    it 'returns parser buffer with expected name' do
-      expect(apply.name).to eql('(string)')
-    end
-
-    it 'returns parser buffer with pre-filled source' do
-      expect(apply.source).to eql(source)
-    end
-  end
-
-  describe '.parser' do
-    let(:invalid_source_buffer) { Unparser.buffer('a +') }
-
-    def apply
-      described_class.parser
-    end
-
-    context 'failure' do
-      def apply
-        super.tap do |parser|
-          parser.diagnostics.consumer = ->(_) {}
-        end
-      end
-
-      it 'returns a parser that fails with syntax error' do
-        expect { apply.parse(invalid_source_buffer) }
-          .to raise_error(Parser::SyntaxError)
-      end
-    end
-
-    context 'warnings' do
-      before do
-        allow(Kernel).to receive(:warn)
-      end
-
-      it 'returns a parser that warns on diagnostics' do
-        expect { apply.parse(invalid_source_buffer) }
-          .to raise_error(Parser::SyntaxError)
-
-        expect(Kernel).to have_received(:warn)
-          .with([
-            "(string):1:4: error: unexpected token $end",
-            "(string):1: a +", "(string):1:    "
-          ])
-      end
-    end
-  end
-
-  describe '.parse' do
-    def apply
-      described_class.parse('self[1]=2')
-    end
-
-    it 'returns expected AST' do
-      expect(apply).to eql(s(:indexasgn, s(:self), s(:int, 1), s(:int, 2)))
-    end
-  end
-
-  describe '.unparse' do
-    let(:builder_options) { {} }
-
-    def parser
-      Unparser.parser.tap do |parser|
-        builder_options.each do |name, value|
-          parser.builder.public_send(:"#{name}=", value)
-        end
-      end
-    end
-
-    def buffer(input)
-      Unparser.buffer(input)
-    end
-
-    def parse_with_comments(string)
-      parser.parse_with_comments(buffer(string))
-    end
-
-    def self.with_builder_options(options, &block)
-      context "with #{options}" do
-        let(:builder_options) { options }
-
-        class_eval(&block)
-      end
-    end
-
-    def assert_round_trip(string, parser)
-      ast, comments = parse_with_comments(string)
-      generated = Unparser.unparse(ast, comments)
-      expect(generated).to eql(string.chomp)
-      generated_ast, _comments = parse_with_comments(generated)
-      expect(ast == generated_ast).to be(true)
-    end
-
-    def assert_generates_from_string(parser, string, expected)
-      ast_with_comments = parse_with_comments(string)
-      assert_generates_from_ast(parser, ast_with_comments, expected.chomp)
-    end
-
-    def assert_generates_from_ast(parser, ast_with_comments, expected)
-      generated = Unparser.unparse(*ast_with_comments)
-      expect(generated).to eql(expected)
-      ast, comments = parse_with_comments(generated)
-      expect(Unparser.unparse(ast, comments)).to eql(expected)
-    end
-
-    def self.assert_unterminated(expression)
-      assert_source(expression)
-      assert_source("(#{expression}).foo")
-    end
-
-    def self.assert_terminated(expression)
-      assert_source(expression)
-      assert_source("foo(#{expression})")
-      assert_source("#{expression}.foo")
-    end
-
-    def self.assert_generates(input, expected)
-      it "should generate #{input} as #{expected}" do
-        if input.is_a?(String)
-          assert_generates_from_string(parser, input, expected)
-        else
-          assert_generates_from_ast(parser, [input, []], expected)
-        end
-      end
-    end
-
-    def self.assert_round_trip(input)
-      it "should round trip #{input}" do
-        assert_round_trip(input, parser)
-      end
-    end
-
-    def self.assert_source(input)
-      assert_round_trip(input)
-    end
-
-    context 'kwargs' do
-      assert_source <<~RUBY
-        def foo(bar:, baz:)
-        end
-      RUBY
-
-      assert_source <<~RUBY
-        foo(**bar)
-      RUBY
-
-      assert_source <<~RUBY
-        def foo(bar:, baz: "value")
-        end
-      RUBY
-    end
-
-    context 'literal' do
-      context 'int' do
-        assert_generates '-0', '0'
-        assert_source '++1'
-        assert_terminated '1'
-        assert_unterminated '-1'
-        assert_generates '0x1', '1'
-        assert_generates '1_000', '1000'
-        assert_generates '1e10',  '10000000000.0'
-        assert_generates '10e10000000000', 'Float::INFINITY'
-        assert_generates '-10e10000000000', '-Float::INFINITY'
-      end
-
-      context 'rational' do
-        assert_terminated '1r'
-        assert_generates '1.0r', '1r'
-        assert_generates '-0r', '0r'
-
-        assert_terminated '1.5r'
-        assert_terminated '1.3r'
-      end
-
-      context 'complex' do
-        %w(
-          5i
-          -5i
-          0.6i
-          -0.6i
-          1000000000000000000000000000000i
-          1ri
-        ).each do |expression|
-          assert_terminated(expression)
-        end
-      end
-
-      context 'string' do
-        assert_generates '?c',                 '"c"'
-        assert_generates '"foo" "bar"',        '"#{"foo"}#{"bar"}"'
-        assert_generates '"foo" "bar #{baz}"', '"#{"foo"}#{"#{"bar "}#{baz}"}"'
-        assert_generates '%Q(foo"#{@bar})',    '"#{"foo\\""}#{@bar}"'
-        assert_generates '"foo#{1}bar"',       '"#{"foo"}#{1}#{"bar"}"'
-        assert_generates '"\\\\#{}"',          '"#{"\\\\"}#{}"'
-        assert_generates '"#{}\#{}"',          '"#{}#{"\#{}"}"'
-        assert_generates '"\#{}#{}"',          '"#{"\#{}"}#{}"'
-        assert_terminated '"\""'
-        assert_terminated '"foo bar"'
-        assert_terminated '"foo\nbar"'
-        # Within indentation
-        assert_generates <<~'RUBY', <<~'RUBY'
-          if foo
-            "
-            #{foo}
-            "
-          end
-        RUBY
-          if foo
-            "#{"\n"}#{"  "}#{foo}#{"\n"}#{"  "}"
-          end
-        RUBY
-      end
-
-      context 'execute string' do
-        assert_generates '`foo`',        '`#{"foo"}`'
-        assert_generates '`foo#{@bar}`', '`#{"foo"}#{@bar}`'
-        assert_generates '%x(\))',       '`#{")"}`'
-        assert_generates '%x(`)',        '`#{"`"}`'
-        assert_generates '`"`',          '`#{"\\""}`'
-      end
-
-      context 'symbol' do
-        assert_generates s(:sym, :foo), ':foo'
-        assert_generates s(:sym, :"A B"), ':"A B"'
-        assert_terminated ':foo'
-        assert_terminated ':"A B"'
-        assert_terminated ':"A\"B"'
-        assert_terminated ':""'
-      end
-
-      context 'regexp' do
-        assert_terminated '/foo/'
-        assert_terminated %q(/[^-+',.\/:@[:alnum:]\[\]]+/)
-        assert_terminated '/foo#{@bar}/'
-        assert_terminated '/foo#{@bar}/imx'
-        assert_terminated '/#{"\u0000"}/'
-        assert_terminated "/\n/"
-        assert_terminated '/\n/'
-        assert_terminated "/\n/x"
-        # Within indentation
-        assert_source <<~RUBY
-          if foo
-            /
-            /
-          end
-        RUBY
-        assert_generates '%r(/)', '/\//'
-        assert_generates '%r(\))', '/\)/'
-        assert_generates '%r(#{@bar}baz)', '/#{@bar}baz/'
-        assert_terminated '/\/\//x'
-      end
-
-      context 'dynamic symbol' do
-        assert_generates ':"foo#{bar}baz"', ':"#{"foo"}#{bar}#{"baz"}"'
-        assert_source ':"#{"foo"}"'
-      end
-
-      context 'irange' do
-        assert_unterminated '1..'
-        assert_unterminated '1..2'
-        assert_unterminated '(0.0 / 0.0)..1'
-        assert_unterminated '1..(0.0 / 0.0)'
-        assert_unterminated '(0.0 / 0.0)..100'
-      end
-
-      context 'erange' do
-        assert_unterminated '1...'
-        assert_unterminated '1...2'
-      end
-
-      context 'float' do
-        assert_source '-0.1'
-        assert_terminated '0.1'
-        assert_terminated '0.1'
-        assert_generates '10.2e10000000000', 'Float::INFINITY'
-        assert_generates '-10.2e10000000000', '-Float::INFINITY'
-        assert_generates s(:float, -0.1), '-0.1'
-        assert_generates s(:float, 0.1), '0.1'
-      end
-
-      context 'array' do
-        assert_terminated '[1, 2]'
-        assert_terminated '[1, (), n2]'
-        assert_terminated '[1]'
-        assert_terminated '[]'
-        assert_terminated '[1, *@foo]'
-        assert_terminated '[*@foo, 1]'
-        assert_terminated '[*@foo, *@baz]'
-        assert_generates '%w(foo bar)', '["foo", "bar"]'
-      end
-
-      context 'hash' do
-        assert_terminated '{}'
-        assert_source '{ () => () }'
-        assert_source '{ 1 => 2 }'
-        assert_source '{ 1 => 2, 3 => 4 }'
-
-        # special case for 2.1.3
-        assert_source "{ foo: (if true\nend) }"
-
-        context 'with symbol keys' do
-          assert_source '{ a: (1 rescue foo), b: 2 }'
-          assert_source '{ a: 1, b: 2 }'
-          assert_source '{ a: :a }'
-          assert_source '{ :"a b" => 1 }'
-          assert_source '{ :-@ => 1 }'
-        end
-      end
-    end
-
-    context 'access' do
-      %w(@a @@a $a $1 $` CONST SCOPED::CONST ::TOPLEVEL ::TOPLEVEL::CONST).each do |expression|
-        assert_terminated(expression)
-      end
-    end
-
-    context 'control keywords' do
-      %w(retry redo).each do |expression|
-        assert_terminated(expression)
-      end
-    end
-
-    context 'singletons' do
-      %w(self true false nil).each do |expression|
-        assert_terminated(expression)
-      end
-    end
-
-    context 'magic keywords' do
-      assert_source '__ENCODING__'
-
-      # These two assertions don't actually need to be wrapped in this block since `true` is the default,
-      # but it is helpful to contrast with the assertions farther down.
-      with_builder_options(emit_file_line_as_literals: true) do
-        assert_generates '__FILE__', '"(string)"'
-        assert_generates '__LINE__', '1'
-      end
-
-      with_builder_options(emit_file_line_as_literals: false) do
-        assert_source '__FILE__'
-        assert_source '__LINE__'
-      end
-    end
-
-    context 'assignment' do
-      context 'single' do
-        assert_unterminated 'a = 1'
-        assert_unterminated '@a = 1'
-        assert_unterminated '@@a = 1'
-        assert_unterminated '$a = 1'
-        assert_unterminated 'CONST = 1'
-        assert_unterminated 'Name::Spaced::CONST = 1'
-        assert_unterminated '::Foo = ::Bar'
-      end
-
-      context 'lvar assigned from method with same name' do
-        assert_unterminated 'foo = foo()'
-      end
-
-      context 'lvar introduction from condition' do
-        assert_source 'foo = bar while foo'
-        assert_source 'foo = bar until foo'
-        assert_source <<~'RUBY'
-          foo = exp
-          while foo
-            foo = bar
-          end
-        RUBY
-
-        # Ugly I know. But its correct :D
-        #
-        # if foo { |pair| }
-        #   pair = :foo
-        #   foo
-        # end
-        assert_source <<~'RUBY'
-          if foo do |pair|
-            pair
-          end
-            pair = :foo
-            foo
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          while foo
-            foo = bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          each do |bar|
-            while foo
-              foo = bar
-            end
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            foo = bar while foo != baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          each do |baz|
-            while foo
-              foo = bar
-            end
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          each do |foo|
-            while foo
-              foo = bar
-            end
-          end
-        RUBY
-      end
-
-      context 'multiple' do
-        assert_source 'a, * = [1, 2]'
-        assert_source 'a, *foo = [1, 2]'
-        assert_source '*a = []'
-        assert_source '*foo = [1, 2]'
-        assert_source 'a, = foo'
-        assert_unterminated 'a, b = [1, 2]'
-        assert_unterminated '@a, @b = [1, 2]'
-        assert_unterminated 'a.foo, a.bar = [1, 2]'
-        assert_unterminated 'a[0], a[1] = [1, 2]'
-        assert_unterminated 'a[*foo], a[1] = [1, 2]'
-        assert_unterminated '@@a, @@b = [1, 2]'
-        assert_unterminated '$a, $b = [1, 2]'
-        assert_unterminated 'a, b = foo'
-        assert_unterminated 'a, (b, c) = [1, [2, 3]]'
-        assert_unterminated 'a = (b, c = 1)'
-        assert_unterminated '(a,), b = 1'
-      end
-    end
-
-    %w(next return break).each do |keyword|
-
-      context keyword do
-        assert_terminated keyword.to_s
-        assert_unterminated "#{keyword} 1"
-        assert_unterminated "#{keyword} 2, 3"
-        assert_unterminated "#{keyword} *nil"
-        assert_unterminated "#{keyword} *foo, bar"
-
-        assert_generates <<~RUBY, <<~RUBY
-          foo do |bar|
-            bar =~ // || #{keyword}
-            baz
-          end
-        RUBY
-          foo do |bar|
-            (bar =~ //) || #{keyword}
-            baz
-          end
-        RUBY
-
-        assert_generates <<~RUBY, <<~RUBY
-          #{keyword}(a ? b : c)
-        RUBY
-          #{keyword} (if a
-            b
-          else
-            c
-          end)
-        RUBY
-      end
-    end
-
-    context 'conditional send (csend)' do
-      assert_terminated 'a&.b'
-      assert_terminated 'a&.b(c)'
-    end
-
-    context 'send' do
-      assert_terminated 'foo'
-      assert_terminated 'self.foo'
-      assert_terminated 'a.foo'
-      assert_terminated 'A.foo'
-      assert_terminated 'foo[]'
-      assert_terminated 'foo[1]'
-      assert_terminated 'foo[*baz]'
-      assert_terminated 'foo(1)'
-      assert_terminated 'foo(bar)'
-      assert_terminated 'foo(&block)'
-      assert_terminated 'foo(&(foo || bar))'
-      assert_terminated 'foo(*arguments)'
-      assert_terminated 'foo(*arguments)'
-
-      assert_source <<~'RUBY'
-        foo do
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do |a|
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do |a, |
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do |a, b|
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo(1) do
-          nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do |a, *b|
-          nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do |a, *|
-          nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo do
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar(*args)
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |(a, b), c|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |*a; b|
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |a; b|
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |; a, b|
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |*|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |(*)|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |((*))|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |(a, (*))|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do |(a, b)|
-          d
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo.bar do
-        end.baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        FOO()
-      RUBY
-
-      assert_terminated '(1..2).max'
-      assert_terminated '1..2.max'
-      assert_unterminated 'a || return'
-      assert_unterminated 'foo << (bar * baz)'
-
-      assert_source <<~'RUBY'
-        foo ||= (a, _ = b)
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-        rescue
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        case (def foo
-        end
-        :bar)
-        when bar
-        end.baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when bar
-        end.baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        class << self
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        def self.foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        def foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        until foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        while foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        loop do
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        class Foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        module Foo
-        end.bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        if foo
-        end.baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        local = 1
-        local.bar
-      RUBY
-
-      assert_terminated 'foo.bar(*args)'
-      assert_terminated 'foo.bar(*arga, foo, *argb)'
-      assert_terminated 'foo.bar(*args, foo)'
-      assert_terminated 'foo.bar(foo, *args)'
-      assert_terminated 'foo.bar(foo, *args, &block)'
-      assert_source <<~'RUBY'
-        foo(bar, *args)
-      RUBY
-
-      assert_terminated 'foo(*args, &block)'
-      assert_terminated 'foo.bar(&baz)'
-      assert_terminated 'foo.bar(:baz, &baz)'
-      assert_terminated 'foo.bar=:baz'
-
-      assert_unterminated 'self.foo=:bar'
-
-      assert_terminated 'foo.bar(baz: boz)'
-      assert_terminated 'foo.bar(foo, "baz" => boz)'
-      assert_terminated 'foo.bar({ foo: boz }, boz)'
-      assert_terminated 'foo.bar(foo, {})'
-    end
-
-    context 'begin; end' do
-      assert_generates s(:begin), ''
-
-      assert_source <<~'RUBY'
-        begin
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo
-        bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-          bar
-        end.blah
-      RUBY
-    end
-
-    context 'begin / rescue / ensure' do
-      assert_source <<~'RUBY'
-        begin
-          foo
-        ensure
-          bar
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        rescue
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          begin
-            foo
-            bar
-          rescue
-          end
-        rescue
-          baz
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          raise(Exception) rescue foo = bar
-        rescue Exception
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-          bar
-        rescue
-          baz
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        rescue Exception
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        rescue => bar
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        rescue Exception, Other => bar
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        class << self
-          undef :bar rescue nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        module Foo
-          undef :bar rescue nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        class Foo
-          undef :bar rescue nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-        rescue Exception => e
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-        ensure
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-        rescue
-        ensure
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        rescue Exception => bar
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue SomeError, *bar
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue SomeError, *bar => exception
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue *bar
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue LoadError
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue
-        else
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          bar
-        rescue *bar => exception
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-        rescue Exception => e
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-        ensure
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-        rescue
-        ensure
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          foo
-        rescue Exception => bar
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue SomeError, *bar
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue SomeError, *bar => exception
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue *bar
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue LoadError
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue
-        else
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        m do
-          bar
-        rescue *bar => exception
-          baz
-        end
-      RUBY
-
-      assert_source 'foo rescue bar'
-      assert_source 'foo rescue return bar'
-      assert_source 'x = (foo rescue return bar)'
-
-      %w(while until if).each do |keyword|
-        assert_source <<~RUBY
-          #{keyword} (
-            foo rescue false
-          )
-          end
-        RUBY
-
-        assert_generates <<~RUBY, <<~GENERATED
-          foo rescue false #{keyword} true
-        RUBY
-          #{keyword} true
-            foo rescue false
-          end
-        GENERATED
-      end
-
-      assert_generates <<~'RUBY', <<~GENERATED
-        case (foo rescue false)
-        when true
-        end
-      RUBY
-        case (
-          foo rescue false
-        )
-        when true
-        end
-      GENERATED
-    end
-
-    context 'super' do
-      assert_source 'super'
-
-      assert_source 'super()'
-      assert_source 'super(a)'
-      assert_source 'super(a, b)'
-      assert_source 'super(&block)'
-      assert_source 'super(a, &block)'
-
-      assert_source <<~'RUBY'
-        super(a do
-          foo
-        end)
-      RUBY
-
-      assert_source <<~'RUBY'
-        super do
-          foo
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        super(a) do
-          foo
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        super() do
-          foo
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        super(a, b) do
-          foo
-        end
-      RUBY
-
-    end
-
-    context 'undef' do
-      assert_source 'undef :foo'
-      assert_source 'undef :foo, :bar'
-    end
-
-    context 'BEGIN' do
-      assert_source <<~'RUBY'
-        BEGIN {
-          foo
-        }
-      RUBY
-    end
-
-    context 'END' do
-      assert_source <<~'RUBY'
-        END {
-          foo
-        }
-      RUBY
-    end
-
-    context 'alias' do
-      assert_source <<~'RUBY'
-        alias $foo $bar
-      RUBY
-
-      assert_source <<~'RUBY'
-        alias :foo :bar
-      RUBY
-    end
-
-    context 'yield' do
-      context 'without arguments' do
-        assert_source 'yield'
-      end
-
-      context 'with argument' do
-        assert_source 'yield(a)'
-      end
-
-      context 'with arguments' do
-        assert_source 'yield(a, b)'
-      end
-    end
-
-    context 'if statement' do
-      assert_source <<~'RUBY'
-        if /foo/
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        if 3
-          9
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        if 4
-          5
-        else
-          6
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        unless 3
-          nil
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        unless 3
-          9
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        if foo
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo = bar if foo
-      RUBY
-
-      assert_source <<~'RUBY'
-        foo = bar unless foo
-      RUBY
-
-      assert_source <<~'RUBY'
-        def foo(*foo)
-          unless foo
-            foo = bar
-          end
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        each do |foo|
-          unless foo
-            foo = bar
-          end
-        end
-      RUBY
-    end
-
-    context 'def' do
-      context 'on instance' do
-
-        assert_source <<~'RUBY'
-          def foo
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            foo
-          rescue
-            bar
-          ensure
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          begin
-            foo
-          ensure
-            bar rescue nil
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            bar
-          ensure
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def self.foo
-            bar
-          rescue
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            bar
-          rescue
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar, baz)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar = ())
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar = (baz
-          nil))
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar = true)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar, baz = true)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar: 1)
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar: bar)
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar: bar())
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(*)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(*bar)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar, *baz)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(baz = true, *bor)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(baz = true, *bor, &block)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar, baz = true, *bor)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(&block)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo(bar, &block)
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo
-            bar
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def (foo do |bar|
-          end).bar
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def (foo(1)).bar
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def (Foo::Bar.baz).bar
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def (Foo::Bar).bar
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def Foo.bar
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def foo.bar
-            baz
-          end
-        RUBY
-      end
-
-      context 'on singleton' do
-        assert_source <<~'RUBY'
-          def self.foo
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def self.foo
-            bar
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def self.foo
-            bar
-            baz
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          def Foo.bar
-            bar
-          end
-        RUBY
-
-      end
-
-      context 'class' do
-        assert_source <<~'RUBY'
-          class TestClass
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class << some_object
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class << some_object
-            the_body
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class SomeNameSpace::TestClass
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class Some::Name::Space::TestClass
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class TestClass < Object
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class TestClass < SomeNameSpace::Object
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class TestClass
-            def foo
-              :bar
-            end
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          class ::TestClass
-          end
-        RUBY
-      end
-
-      context 'module' do
-
-        assert_source <<~'RUBY'
-          module TestModule
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          module SomeNameSpace::TestModule
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          module Some::Name::Space::TestModule
-          end
-        RUBY
-
-        assert_source <<~'RUBY'
-          module TestModule
-            def foo
-              :bar
-            end
-          end
-        RUBY
-
-      end
-
-      context 'op assign' do
-        %w(|= ||= &= &&= += -= *= /= **= %=).each do |op|
-          assert_source "self.foo #{op} bar"
-          assert_source "foo[key] #{op} bar"
-          assert_source "a #{op} (true\nfalse)"
-        end
-      end
-
-      context 'element assignment' do
-        assert_source 'foo[index] = value'
-        assert_source 'foo[*index] = value'
-        assert_source 'foo[a, b] = value'
-        assert_source 'foo[1..2] = value'
-        assert_source 'foo.[]=()'
-        assert_source 'foo.[]=true'
-        assert_source 'foo.[]=(1, 2)'
-        assert_source 'foo[] = 1'
-        assert_unterminated 'foo[] = 1'
-
-        %w(+ - * / % & | || &&).each do |operator|
-          context "with #{operator}" do
-            assert_source "foo[index] #{operator}= 2"
-            assert_source "foo[] #{operator}= 2"
-          end
-        end
-      end
-
-      context 'defined?' do
-        assert_source <<~'RUBY'
-          defined?(@foo)
-        RUBY
-
-        assert_source <<~'RUBY'
-          defined?(Foo)
-        RUBY
-
-        assert_source <<~'RUBY'
-          defined?((a, b = [1, 2]))
-        RUBY
-      end
-    end
-
-    context 'lambda' do
-      assert_source <<~'RUBY'
-        lambda do
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        lambda do |a, b|
-          a
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        ->() do
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        ->(a) do
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        ->(a, b) do
-        end
-      RUBY
-    end
-
-    context 'match operators' do
-      assert_source '/bar/ =~ foo'
-      assert_source '/bar/ =~ :foo'
-      assert_source '(/bar/ =~ :foo).foo'
-      assert_source 'foo =~ /bar/'
-      assert_source 'foo(foo =~ /bar/)'
-      assert_source '(foo =~ /bar/).foo'
-    end
-
-    context 'binary operator methods' do
-      %w(+ - * / & | << >> == === != <= < <=> > >= =~ !~ ^ **).each do |operator|
-        assert_source "(-1) #{operator} 2"
-        assert_source "(-1.2) #{operator} 2"
-        assert_source "left.#{operator}(*foo)"
-        assert_source "left.#{operator}(a, b)"
-        assert_source "self #{operator} b"
-        assert_source "a #{operator} b"
-        assert_source "(a #{operator} b).foo"
-      end
-
-      assert_source 'left / right'
-    end
-
-    context 'nested binary operators' do
-      assert_source '(a + b) / (c - d)'
-      assert_source '(a + b) / c.-(e, f)'
-      assert_source '(a + b) / c.-(*f)'
-    end
-
-    context 'binary operator' do
-      assert_source 'a || (return foo)'
-      assert_source '(return foo) || a'
-      assert_source 'a || (break foo)'
-      assert_source '(break foo) || a'
-      assert_source '(a || b).foo'
-      assert_source 'a || (b || c)'
-    end
-
-    { or: :'||', and: :'&&' }.each do |word, symbol|
-      assert_generates "a #{word} return foo", "a #{symbol} (return foo)"
-      assert_generates "a #{word} break foo", "a #{symbol} (break foo)"
-      assert_generates "a #{word} next foo", "a #{symbol} (next foo)"
-    end
-
-    context 'expansion of shortcuts' do
-      assert_source 'a += 2'
-      assert_source 'a -= 2'
-      assert_source 'a **= 2'
-      assert_source 'a *= 2'
-      assert_source 'a /= 2'
-    end
-
-    context 'shortcuts' do
-      assert_source 'a &&= b'
-      assert_source 'a ||= 2'
-      assert_source '(a ||= 2).bar'
-      assert_source '(h ||= {})[k] = v'
-    end
-
-    context 'flip flops' do
-      context 'inclusive' do
-        assert_source <<~'RUBY'
-          if ((i == 4)..(i == 4))
-            foo
-          end
-        RUBY
-      end
-
-      context 'exclusive' do
-        assert_source <<~'RUBY'
-          if ((i == 4)...(i == 4))
-            foo
-          end
-        RUBY
-      end
-    end
-
-    context 'case statement' do
-      assert_source <<~'RUBY'
-        case
-        when bar
-          baz
-        when baz
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when bar
-        when baz
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when bar
-          baz
-        when baz
-          bar
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when bar, baz
-          :other
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when *bar
-          :value
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        case foo
-        when bar
-          baz
-        else
-          :foo
-        end
-      RUBY
-    end
-
-    context 'for' do
-      assert_source <<~'RUBY'
-        bar(for a in bar do
-          baz
-        end)
-      RUBY
-      assert_source <<~'RUBY'
-        for a in bar do
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        for a, *b in bar do
-          baz
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        for a, b in bar do
-          baz
-        end
-      RUBY
-    end
-
-    context 'unary operators' do
-      assert_source '!1'
-      assert_source '!(!1)'
-      assert_source '!(!(foo || bar))'
-      assert_source '!(!1).baz'
-      assert_source '~a'
-      assert_source '-a'
-      assert_source '+a'
-      assert_source '-(-a).foo'
-    end
-
-    context 'loop' do
-      assert_source <<~'RUBY'
-        loop do
-          foo
-        end
-      RUBY
-    end
-
-    context 'post conditions' do
-      assert_source <<~'RUBY'
-        x = (begin
-          foo
-        end while baz)
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-        end while baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-          bar
-        end until baz
-      RUBY
-
-      assert_source <<~'RUBY'
-        begin
-          foo
-          bar
-        end while baz
-      RUBY
-    end
-
-    context 'while' do
-      assert_source <<~'RUBY'
-        while false
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        while false
-          3
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        while (foo do
-        end)
-          :body
-        end
-      RUBY
-    end
-
-    context 'until' do
-      assert_source <<~'RUBY'
-        until false
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        until false
-          3
-        end
-      RUBY
-
-      assert_source <<~'RUBY'
-        until (foo do
-        end)
-          :body
-        end
-      RUBY
-    end
-
-    assert_source <<~'RUBY'
-      # comment before
-      a_line_of_code
-    RUBY
-
-    assert_source <<~'RUBY'
-      a_line_of_code # comment after
-    RUBY
-
-    assert_source <<~'RUBY'
-      nested do # first
-        # second
-        something # comment
-        # another
-      end
-      # last
-    RUBY
-
-    assert_generates <<~'RUBY', <<~'RUBY'
-      foo if bar
-      # comment
-    RUBY
-      if bar
-        foo
-      end
-      # comment
-    RUBY
-
-    assert_source <<~'RUBY'
-      def noop
-        # do nothing
-      end
-    RUBY
-
-    assert_source <<~'RUBY'
-      =begin
-        block comment
-      =end
-      nested do
-      =begin
-      another block comment
-      =end
-        something
-      =begin
-      last block comment
-      =end
-      end
-    RUBY
-
-    assert_generates(<<~'RUBY', <<~'RUBY')
-      1 + # first
-        2 # second
-    RUBY
-      1 + 2 # first # second
-    RUBY
-
-    assert_generates(<<~'RUBY', <<~'RUBY')
-      1 +
-        # first
-        2 # second
-    RUBY
-      1 + 2 # first # second
-    RUBY
-
-    assert_generates(<<~'RUBY', <<~'RUBY')
-      1 +
-      =begin
-        block comment
-      =end
-        2
-    RUBY
-      1 + 2
-      =begin
-        block comment
-      =end
-    RUBY
-  end
-end
diff --git a/unparser.gemspec b/unparser.gemspec
index 3df65e1..e3b3902 100644
--- a/unparser.gemspec
+++ b/unparser.gemspec
@@ -1,32 +1,52 @@
-Gem::Specification.new do |gem|
-  gem.name        = 'unparser'
-  gem.version     = '0.4.7'
+#########################################################
+# This file has been automatically generated by gem2tgz #
+#########################################################
+# -*- encoding: utf-8 -*-
+# stub: unparser 0.6.8 ruby lib
 
-  gem.authors     = ['Markus Schirp']
-  gem.email       = 'mbj@schirp-dso.com'
-  gem.summary     = 'Generate equivalent source for parser gem AST nodes'
+Gem::Specification.new do |s|
+  s.name = "unparser".freeze
+  s.version = "0.6.8"
 
-  gem.description = gem.summary
-  gem.homepage    = 'http://github.com/mbj/unparser'
-  gem.license     = 'MIT'
+  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
+  s.metadata = { "bug_tracker_uri" => "https://github.com/mbj/unparser/issues", "changelog_uri" => "https://github.com/mbj/unparser/blob/master/Changelog.md", "funding_uri" => "https://github.com/sponsors/mbj", "rubygems_mfa_required" => "true" } if s.respond_to? :metadata=
+  s.require_paths = ["lib".freeze]
+  s.authors = ["Markus Schirp".freeze]
+  s.date = "2023-06-15"
+  s.description = "Generate equivalent source for parser gem AST nodes".freeze
+  s.email = "mbj@schirp-dso.com".freeze
+  s.executables = ["unparser".freeze]
+  s.extra_rdoc_files = ["README.md".freeze]
+  s.files = ["README.md".freeze, "bin/unparser".freeze, "lib/unparser.rb".freeze, "lib/unparser/abstract_type.rb".freeze, "lib/unparser/adamantium.rb".freeze, "lib/unparser/adamantium/method_builder.rb".freeze, "lib/unparser/anima.rb".freeze, "lib/unparser/anima/attribute.rb".freeze, "lib/unparser/anima/error.rb".freeze, "lib/unparser/ast.rb".freeze, "lib/unparser/ast/local_variable_scope.rb".freeze, "lib/unparser/buffer.rb".freeze, "lib/unparser/cli.rb".freeze, "lib/unparser/color.rb".freeze, "lib/unparser/comments.rb".freeze, "lib/unparser/concord.rb".freeze, "lib/unparser/constants.rb".freeze, "lib/unparser/diff.rb".freeze, "lib/unparser/dsl.rb".freeze, "lib/unparser/either.rb".freeze, "lib/unparser/emitter.rb".freeze, "lib/unparser/emitter/alias.rb".freeze, "lib/unparser/emitter/args.rb".freeze, "lib/unparser/emitter/argument.rb".freeze, "lib/unparser/emitter/array.rb".freeze, "lib/unparser/emitter/array_pattern.rb".freeze, "lib/unparser/emitter/assignment.rb".freeze, "lib/unparser/emitter/begin.rb".freeze, "lib/unparser/emitter/binary.rb".freeze, "lib/unparser/emitter/block.rb".freeze, "lib/unparser/emitter/case.rb".freeze, "lib/unparser/emitter/case_guard.rb".freeze, "lib/unparser/emitter/case_match.rb".freeze, "lib/unparser/emitter/cbase.rb".freeze, "lib/unparser/emitter/class.rb".freeze, "lib/unparser/emitter/const_pattern.rb".freeze, "lib/unparser/emitter/def.rb".freeze, "lib/unparser/emitter/defined.rb".freeze, "lib/unparser/emitter/dstr.rb".freeze, "lib/unparser/emitter/dsym.rb".freeze, "lib/unparser/emitter/find_pattern.rb".freeze, "lib/unparser/emitter/flipflop.rb".freeze, "lib/unparser/emitter/float.rb".freeze, "lib/unparser/emitter/flow_modifier.rb".freeze, "lib/unparser/emitter/for.rb".freeze, "lib/unparser/emitter/hash.rb".freeze, "lib/unparser/emitter/hash_pattern.rb".freeze, "lib/unparser/emitter/hookexe.rb".freeze, "lib/unparser/emitter/if.rb".freeze, "lib/unparser/emitter/in_match.rb".freeze, "lib/unparser/emitter/in_pattern.rb".freeze, "lib/unparser/emitter/index.rb".freeze, "lib/unparser/emitter/kwargs.rb".freeze, "lib/unparser/emitter/kwbegin.rb".freeze, "lib/unparser/emitter/lambda.rb".freeze, "lib/unparser/emitter/masgn.rb".freeze, "lib/unparser/emitter/match.rb".freeze, "lib/unparser/emitter/match_alt.rb".freeze, "lib/unparser/emitter/match_as.rb".freeze, "lib/unparser/emitter/match_pattern.rb".freeze, "lib/unparser/emitter/match_pattern_p.rb".freeze, "lib/unparser/emitter/match_rest.rb".freeze, "lib/unparser/emitter/match_var.rb".freeze, "lib/unparser/emitter/mlhs.rb".freeze, "lib/unparser/emitter/module.rb".freeze, "lib/unparser/emitter/op_assign.rb".freeze, "lib/unparser/emitter/pair.rb".freeze, "lib/unparser/emitter/pin.rb".freeze, "lib/unparser/emitter/primitive.rb".freeze, "lib/unparser/emitter/range.rb".freeze, "lib/unparser/emitter/regexp.rb".freeze, "lib/unparser/emitter/repetition.rb".freeze, "lib/unparser/emitter/rescue.rb".freeze, "lib/unparser/emitter/root.rb".freeze, "lib/unparser/emitter/send.rb".freeze, "lib/unparser/emitter/simple.rb".freeze, "lib/unparser/emitter/splat.rb".freeze, "lib/unparser/emitter/super.rb".freeze, "lib/unparser/emitter/undef.rb".freeze, "lib/unparser/emitter/variable.rb".freeze, "lib/unparser/emitter/xstr.rb".freeze, "lib/unparser/emitter/yield.rb".freeze, "lib/unparser/equalizer.rb".freeze, "lib/unparser/finalize.rb".freeze, "lib/unparser/generation.rb".freeze, "lib/unparser/node_details.rb".freeze, "lib/unparser/node_details/send.rb".freeze, "lib/unparser/node_helpers.rb".freeze, "lib/unparser/validation.rb".freeze, "lib/unparser/writer.rb".freeze, "lib/unparser/writer/binary.rb".freeze, "lib/unparser/writer/dynamic_string.rb".freeze, "lib/unparser/writer/resbody.rb".freeze, "lib/unparser/writer/rescue.rb".freeze, "lib/unparser/writer/send.rb".freeze, "lib/unparser/writer/send/attribute_assignment.rb".freeze, "lib/unparser/writer/send/binary.rb".freeze, "lib/unparser/writer/send/conditional.rb".freeze, "lib/unparser/writer/send/regular.rb".freeze, "lib/unparser/writer/send/unary.rb".freeze]
+  s.homepage = "http://github.com/mbj/unparser".freeze
+  s.licenses = ["MIT".freeze]
+  s.required_ruby_version = Gem::Requirement.new(">= 2.7".freeze)
+  s.rubygems_version = "3.2.5".freeze
+  s.summary = "Generate equivalent source for parser gem AST nodes".freeze
 
-  gem.files            = `git ls-files`.split("\n")
-  gem.test_files       = `git ls-files -- {spec,features}/*`.split("\n")
-  gem.require_paths    = %w[lib]
-  gem.extra_rdoc_files = %w[README.md]
-  gem.executables      = %w[unparser]
+  if s.respond_to? :specification_version then
+    s.specification_version = 4
+  end
 
-  gem.add_dependency('abstract_type', '~> 0.0.7')
-  gem.add_dependency('adamantium',    '~> 0.2.0')
-  gem.add_dependency('equalizer',     '~> 0.0.9')
-  gem.add_dependency('diff-lcs',      '~> 1.3')
-  gem.add_dependency('concord',       '~> 0.1.5')
-  gem.add_dependency('parser',        '>= 2.6.5')
-  gem.add_dependency('procto',        '~> 0.0.2')
-
-  gem.add_development_dependency('anima',        '~> 0.3.1')
-  gem.add_development_dependency('devtools',     '~> 0.1.23')
-  gem.add_development_dependency('morpher',      '~> 0.2.6')
-  gem.add_development_dependency('mutant',       '~> 0.9.4')
-  gem.add_development_dependency('mutant-rspec', '~> 0.9.4')
+  if s.respond_to? :add_runtime_dependency then
+    s.add_runtime_dependency(%q<diff-lcs>.freeze, ["~> 1.3"])
+    s.add_development_dependency(%q<mutant>.freeze, ["~> 0.11.18"])
+    s.add_development_dependency(%q<mutant-rspec>.freeze, ["~> 0.11.18"])
+    s.add_runtime_dependency(%q<parser>.freeze, [">= 3.2.0"])
+    s.add_development_dependency(%q<rspec>.freeze, ["~> 3.9"])
+    s.add_development_dependency(%q<rspec-core>.freeze, ["~> 3.9"])
+    s.add_development_dependency(%q<rspec-its>.freeze, ["~> 1.3.0"])
+    s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.7"])
+    s.add_development_dependency(%q<rubocop-packaging>.freeze, ["~> 0.5"])
+  else
+    s.add_dependency(%q<diff-lcs>.freeze, ["~> 1.3"])
+    s.add_dependency(%q<mutant>.freeze, ["~> 0.11.18"])
+    s.add_dependency(%q<mutant-rspec>.freeze, ["~> 0.11.18"])
+    s.add_dependency(%q<parser>.freeze, [">= 3.2.0"])
+    s.add_dependency(%q<rspec>.freeze, ["~> 3.9"])
+    s.add_dependency(%q<rspec-core>.freeze, ["~> 3.9"])
+    s.add_dependency(%q<rspec-its>.freeze, ["~> 1.3.0"])
+    s.add_dependency(%q<rubocop>.freeze, ["~> 1.7"])
+    s.add_dependency(%q<rubocop-packaging>.freeze, ["~> 0.5"])
+  end
 end

More details

Full run details

Historical runs