campa 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +23 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/campa.gemspec +34 -0
- data/campa/core.cmp +59 -0
- data/campa/example.cmp +2 -0
- data/campa/test.cmp +2 -0
- data/exe/campa +7 -0
- data/lib/campa.rb +18 -0
- data/lib/campa/cli.rb +66 -0
- data/lib/campa/context.rb +42 -0
- data/lib/campa/core/load.rb +25 -0
- data/lib/campa/core/print.rb +20 -0
- data/lib/campa/core/print_ln.rb +17 -0
- data/lib/campa/core/test.rb +52 -0
- data/lib/campa/core/test_report.rb +59 -0
- data/lib/campa/error/arity.rb +11 -0
- data/lib/campa/error/illegal_argument.rb +9 -0
- data/lib/campa/error/invalid_number.rb +9 -0
- data/lib/campa/error/missing_delimiter.rb +9 -0
- data/lib/campa/error/not_a_function.rb +9 -0
- data/lib/campa/error/not_found.rb +9 -0
- data/lib/campa/error/parameters.rb +11 -0
- data/lib/campa/error/reserved.rb +11 -0
- data/lib/campa/error/resolution.rb +9 -0
- data/lib/campa/evaler.rb +106 -0
- data/lib/campa/execution_error.rb +3 -0
- data/lib/campa/lambda.rb +45 -0
- data/lib/campa/language.rb +33 -0
- data/lib/campa/lisp/atom.rb +14 -0
- data/lib/campa/lisp/cadr.rb +41 -0
- data/lib/campa/lisp/car.rb +22 -0
- data/lib/campa/lisp/cdr.rb +22 -0
- data/lib/campa/lisp/cond.rb +50 -0
- data/lib/campa/lisp/cons.rb +23 -0
- data/lib/campa/lisp/core.rb +35 -0
- data/lib/campa/lisp/defun.rb +36 -0
- data/lib/campa/lisp/eq.rb +9 -0
- data/lib/campa/lisp/label.rb +29 -0
- data/lib/campa/lisp/lambda_fn.rb +33 -0
- data/lib/campa/lisp/list_fn.rb +9 -0
- data/lib/campa/lisp/quote.rb +13 -0
- data/lib/campa/list.rb +83 -0
- data/lib/campa/node.rb +17 -0
- data/lib/campa/printer.rb +70 -0
- data/lib/campa/reader.rb +198 -0
- data/lib/campa/repl.rb +75 -0
- data/lib/campa/symbol.rb +23 -0
- data/lib/campa/version.rb +5 -0
- metadata +119 -0
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              class Language < Lisp::Core
         | 
| 3 | 
            +
                def initialize
         | 
| 4 | 
            +
                  super
         | 
| 5 | 
            +
                  load_core_funs
         | 
| 6 | 
            +
                  load_core_files
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                private
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                FUNS = {
         | 
| 12 | 
            +
                  "tests-run": Core::Test.new,
         | 
| 13 | 
            +
                  "tests-report": Core::TestReport.new,
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  "print": Core::Print.new,
         | 
| 16 | 
            +
                  "println": Core::PrintLn.new,
         | 
| 17 | 
            +
                }.freeze
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                FILES = [
         | 
| 20 | 
            +
                  "../campa/core.cmp",
         | 
| 21 | 
            +
                  "../campa/test.cmp",
         | 
| 22 | 
            +
                ].freeze
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def load_core_funs
         | 
| 25 | 
            +
                  FUNS.each { |label, fun| self[Symbol.new(label.to_s)] = fun }
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def load_core_files
         | 
| 29 | 
            +
                  loader = Campa::Core::Load.new
         | 
| 30 | 
            +
                  FILES.each { |f| loader.call(Campa.root.join(f), env: self) }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Cadr
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                    @car = Car.new
         | 
| 7 | 
            +
                    @cdr = Cdr.new
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def macro?
         | 
| 11 | 
            +
                    true
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def call(operation, list)
         | 
| 15 | 
            +
                    return nil if list.nil? || list == List::EMPTY
         | 
| 16 | 
            +
                    raise illegal_argument(list) if !list.is_a?(List)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    cut_list(
         | 
| 19 | 
            +
                      list,
         | 
| 20 | 
            +
                      operation
         | 
| 21 | 
            +
                        .label[1..-2]
         | 
| 22 | 
            +
                        .reverse
         | 
| 23 | 
            +
                        .split("")
         | 
| 24 | 
            +
                    )
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def illegal_argument(list)
         | 
| 30 | 
            +
                    Error::IllegalArgument.new(@printer.call(list), "list")
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def cut_list(list, invocation_sequence)
         | 
| 34 | 
            +
                    invocation_sequence.reduce(list) do |l, oper|
         | 
| 35 | 
            +
                      to_call = oper == "a" ? @car : @cdr
         | 
| 36 | 
            +
                      to_call.call(l)
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Car
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def call(list)
         | 
| 9 | 
            +
                    return nil if list.nil? || list == List::EMPTY
         | 
| 10 | 
            +
                    raise illegal_argument(list) if !list.is_a?(List)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    list.head
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def illegal_argument(list)
         | 
| 18 | 
            +
                    Error::IllegalArgument.new(@printer.call(list), "list")
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Cdr
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def call(list)
         | 
| 9 | 
            +
                    return nil if list.nil? || list == List::EMPTY
         | 
| 10 | 
            +
                    raise illegal_argument(list) if !list.is_a?(List)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    list.tail
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def illegal_argument(list)
         | 
| 18 | 
            +
                    Error::IllegalArgument.new(@printer.call(list), "list")
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Cond
         | 
| 4 | 
            +
                  FALSEY = [false, nil].freeze
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize
         | 
| 7 | 
            +
                    @printer = Printer.new
         | 
| 8 | 
            +
                    @evaler = Evaler.new
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def macro?
         | 
| 12 | 
            +
                    true
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def call(*conditions, env:)
         | 
| 16 | 
            +
                    found = conditions.find do |cond|
         | 
| 17 | 
            +
                      raise illegal_argument(cond) if !cond.is_a?(List)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      !FALSEY.include? evaler.call(cond.head, env)
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    eval_result(found, env)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  attr_reader :evaler, :printer
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def illegal_argument(thing)
         | 
| 30 | 
            +
                    Error::IllegalArgument.new(printer.call(thing), "list")
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def eval_result(found, env)
         | 
| 34 | 
            +
                    return if found.nil?
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    # For when condition is a "truethy" value
         | 
| 37 | 
            +
                    #
         | 
| 38 | 
            +
                    # (cond
         | 
| 39 | 
            +
                    #   ((eq 1 2) 'no)
         | 
| 40 | 
            +
                    #   (true 'yes)
         | 
| 41 | 
            +
                    # )
         | 
| 42 | 
            +
                    # => 'yes
         | 
| 43 | 
            +
                    to_eval = found.tail == List::EMPTY ? [found.head] : found.tail.to_a
         | 
| 44 | 
            +
                    to_eval.reduce(nil) do |_, expr|
         | 
| 45 | 
            +
                      evaler.call(expr, env)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Cons
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def call(new_head, list)
         | 
| 9 | 
            +
                    raise illegal_argument(list) if !list.is_a?(List)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    list.push(new_head)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  attr_reader :printer
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def illegal_argument(list)
         | 
| 19 | 
            +
                    Error::IllegalArgument.new(printer.call(list), "list")
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Core < Context
         | 
| 4 | 
            +
                  CORE_FUNCS_MAP = {
         | 
| 5 | 
            +
                    "quote" => Quote,
         | 
| 6 | 
            +
                    "atom" => Atom,
         | 
| 7 | 
            +
                    "eq" => Eq,
         | 
| 8 | 
            +
                    "car" => Car,
         | 
| 9 | 
            +
                    "cdr" => Cdr,
         | 
| 10 | 
            +
                    "cons" => Cons,
         | 
| 11 | 
            +
                    "cond" => Cond,
         | 
| 12 | 
            +
                    "lambda" => LambdaFn,
         | 
| 13 | 
            +
                    "label" => Label,
         | 
| 14 | 
            +
                    "defun" => Defun,
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    "_cadr" => Cadr,
         | 
| 17 | 
            +
                    "list" => ListFn,
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    "load" => Campa::Core::Load,
         | 
| 20 | 
            +
                  }.freeze
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def initialize
         | 
| 23 | 
            +
                    super Hash[
         | 
| 24 | 
            +
                      CORE_FUNCS_MAP.map { |label, handler| [sym(label), handler.new] }
         | 
| 25 | 
            +
                    ]
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def sym(label)
         | 
| 31 | 
            +
                    Campa::Symbol.new(label)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Defun
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                    @label_fn = Label.new
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def macro?
         | 
| 10 | 
            +
                    true
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def call(label, params, *body, env:)
         | 
| 14 | 
            +
                    raise label_error(label) if !label.is_a?(Symbol)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    label_fn.call(
         | 
| 17 | 
            +
                      label,
         | 
| 18 | 
            +
                      invoke_lambda(params, body),
         | 
| 19 | 
            +
                      env: env
         | 
| 20 | 
            +
                    )
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  attr_reader :printer, :label_fn
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def label_error(given)
         | 
| 28 | 
            +
                    Error::IllegalArgument.new(printer.call(given), "symbol")
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def invoke_lambda(params, body)
         | 
| 32 | 
            +
                    List.new(SYMBOL_LAMBDA, params, *body)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class Label
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @evaler = Evaler.new
         | 
| 6 | 
            +
                    @printer = Printer.new
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def macro?
         | 
| 10 | 
            +
                    true
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def call(label, expression, env:)
         | 
| 14 | 
            +
                    result = evaler.call(expression, env)
         | 
| 15 | 
            +
                    raise Error::Reserved, printer.call(label) if reserved?(label, result)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    env[label] = result
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  private
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  attr_reader :evaler, :printer
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def reserved?(symbol, result)
         | 
| 25 | 
            +
                    symbol.label.match?(CR_REGEX) && result.is_a?(Lambda)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              module Lisp
         | 
| 3 | 
            +
                class LambdaFn
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @printer = Printer.new
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def macro?
         | 
| 9 | 
            +
                    true
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def call(params, *body, env:)
         | 
| 13 | 
            +
                    raise parameters_error(printer.call(params)) if !params.respond_to?(:find)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    validate_params(params)
         | 
| 16 | 
            +
                    Lambda.new(params, body, env)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  attr_reader :printer
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def parameters_error(param, expected = "list of symbols")
         | 
| 24 | 
            +
                    Error::Parameters.new(param, expected)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def validate_params(params)
         | 
| 28 | 
            +
                    invalid_param = params.find { |el| !el.is_a?(Symbol) }
         | 
| 29 | 
            +
                    raise parameters_error(invalid_param, "symbol") if !invalid_param.nil?
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
    
        data/lib/campa/list.rb
    ADDED
    
    | @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            module Campa
         | 
| 2 | 
            +
              class List
         | 
| 3 | 
            +
                include Enumerable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                EMPTY = new
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(*elements)
         | 
| 8 | 
            +
                  @first = nil
         | 
| 9 | 
            +
                  @head = nil
         | 
| 10 | 
            +
                  @tail = EMPTY
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  with elements
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def push(element)
         | 
| 16 | 
            +
                  self.class.new.tap do |l|
         | 
| 17 | 
            +
                    l.first = Node.new(value: element, next_node: first)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def head
         | 
| 22 | 
            +
                  return nil if self == EMPTY
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  first.value
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def tail
         | 
| 28 | 
            +
                  return EMPTY if first.nil? || first.next_node.nil?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  self.class.new.tap { |l| l.first = @first.next_node }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def each(&block)
         | 
| 34 | 
            +
                  return if self == EMPTY
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  block.call(head)
         | 
| 37 | 
            +
                  tail.each(&block) if tail != EMPTY
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def ==(other)
         | 
| 41 | 
            +
                  return false if !other.is_a?(List)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  node = first
         | 
| 44 | 
            +
                  other_node = other.first
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  loop do
         | 
| 47 | 
            +
                    # If both node and other_node are nil
         | 
| 48 | 
            +
                    # we managed to walk the whole list.
         | 
| 49 | 
            +
                    # Since there was no early return (with false)
         | 
| 50 | 
            +
                    # this means all nodes are equal.
         | 
| 51 | 
            +
                    return node.nil? if other_node.nil?
         | 
| 52 | 
            +
                    return false if node != other_node
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    node = node.next_node
         | 
| 55 | 
            +
                    other_node = other_node.next_node
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def inspect
         | 
| 60 | 
            +
                  @printer ||= Printer.new
         | 
| 61 | 
            +
                  @printer.call(self)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                protected
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                attr_accessor :first
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                private
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def with(elements)
         | 
| 71 | 
            +
                  return if elements.empty?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  elements.reduce(nil) do |previous, current|
         | 
| 74 | 
            +
                    new_node = Node.new(value: current)
         | 
| 75 | 
            +
                    if previous.nil?
         | 
| 76 | 
            +
                      self.first = new_node
         | 
| 77 | 
            +
                    else
         | 
| 78 | 
            +
                      previous.next_node = new_node
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         |