loxxy 0.0.27 → 0.1.03
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 +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +56 -10
- data/bin/loxxy +11 -0
- data/lib/loxxy.rb +0 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +2 -0
- data/lib/loxxy/ast/ast_builder.rb +56 -83
- data/lib/loxxy/ast/ast_visitor.rb +28 -10
- data/lib/loxxy/ast/lox_block_stmt.rb +5 -1
- data/lib/loxxy/ast/lox_call_expr.rb +25 -0
- data/lib/loxxy/ast/lox_for_stmt.rb +0 -1
- data/lib/loxxy/ast/lox_fun_stmt.rb +34 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +3 -1
- data/lib/loxxy/back_end/binary_operator.rb +57 -0
- data/lib/loxxy/back_end/engine.rb +126 -5
- data/lib/loxxy/back_end/function.rb +44 -0
- data/lib/loxxy/back_end/unary_operator.rb +41 -0
- data/lib/loxxy/error.rb +9 -0
- data/lib/loxxy/front_end/grammar.rb +28 -28
- data/lib/loxxy/front_end/literal.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +2 -2
- data/spec/datatype/lx_string_spec.rb +2 -0
- data/spec/datatype/number_spec.rb +3 -1
- data/spec/front_end/scanner_spec.rb +2 -0
- data/spec/interpreter_spec.rb +45 -7
- metadata +12 -4
| @@ -6,11 +6,15 @@ module Loxxy | |
| 6 6 | 
             
              module Ast
         | 
| 7 7 | 
             
                class LoxBlockStmt < LoxCompoundExpr
         | 
| 8 8 | 
             
                  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
         | 
| 9 | 
            -
                  # @param  | 
| 9 | 
            +
                  # @param decls [Loxxy::Ast::LoxSeqDecl]
         | 
| 10 10 | 
             
                  def initialize(aPosition, decls)
         | 
| 11 11 | 
             
                    super(aPosition, [decls])
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 | 
            +
                  def empty?
         | 
| 15 | 
            +
                    subnodes.size == 1 && subnodes[0].nil?
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 14 18 | 
             
                  # Part of the 'visitee' role in Visitor design pattern.
         | 
| 15 19 | 
             
                  # @param visitor [Ast::ASTVisitor] the visitor
         | 
| 16 20 | 
             
                  def accept(visitor)
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'lox_compound_expr'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Loxxy
         | 
| 6 | 
            +
              module Ast
         | 
| 7 | 
            +
                class LoxCallExpr < LoxCompoundExpr
         | 
| 8 | 
            +
                  attr_accessor :callee
         | 
| 9 | 
            +
                  attr_reader :arguments
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
         | 
| 12 | 
            +
                  # @param argList [Array<Loxxy::Ast::LoxNode>]
         | 
| 13 | 
            +
                  def initialize(aPosition, argList)
         | 
| 14 | 
            +
                    super(aPosition, [])
         | 
| 15 | 
            +
                    @arguments = argList
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # Part of the 'visitee' role in Visitor design pattern.
         | 
| 19 | 
            +
                  # @param visitor [Ast::ASTVisitor] the visitor
         | 
| 20 | 
            +
                  def accept(visitor)
         | 
| 21 | 
            +
                    visitor.visit_call_expr(self)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end # class
         | 
| 24 | 
            +
              end # module
         | 
| 25 | 
            +
            end # module
         | 
| @@ -18,7 +18,6 @@ module Loxxy | |
| 18 18 | 
             
                  # @param initialization [Loxxy::Ast::LoxNode]
         | 
| 19 19 | 
             
                  # @param testExpr [Loxxy::Ast::LoxNode]
         | 
| 20 20 | 
             
                  # @param updateExpr [Loxxy::Ast::LoxNode]
         | 
| 21 | 
            -
                  # @param body [Loxxy::Ast::LoxNode]
         | 
| 22 21 | 
             
                  def initialize(aPosition, initialization, testExpr, updateExpr)
         | 
| 23 22 | 
             
                    child = initialization ? [initialization] : []
         | 
| 24 23 | 
             
                    super(aPosition, child)
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'lox_compound_expr'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Loxxy
         | 
| 6 | 
            +
              module Ast
         | 
| 7 | 
            +
                # rubocop: disable Style/AccessorGrouping
         | 
| 8 | 
            +
                class LoxFunStmt < LoxCompoundExpr
         | 
| 9 | 
            +
                  attr_reader :name
         | 
| 10 | 
            +
                  attr_reader :params
         | 
| 11 | 
            +
                  attr_reader :body
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
         | 
| 14 | 
            +
                  # @param aName [String]
         | 
| 15 | 
            +
                  # @param arguments [Array<String>]
         | 
| 16 | 
            +
                  # @param body [Ast::LoxBlockStmt]
         | 
| 17 | 
            +
                  def initialize(aPosition, aName, paramList, aBody)
         | 
| 18 | 
            +
                    super(aPosition, [])
         | 
| 19 | 
            +
                    @name = aName
         | 
| 20 | 
            +
                    @params = paramList
         | 
| 21 | 
            +
                    @body = aBody
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # Part of the 'visitee' role in Visitor design pattern.
         | 
| 25 | 
            +
                  # @param visitor [Ast::ASTVisitor] the visitor
         | 
| 26 | 
            +
                  def accept(visitor)
         | 
| 27 | 
            +
                    visitor.visit_fun_stmt(self)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  alias operands subnodes
         | 
| 31 | 
            +
                end # class
         | 
| 32 | 
            +
                # rubocop: enable Style/AccessorGrouping
         | 
| 33 | 
            +
              end # module
         | 
| 34 | 
            +
            end # module
         | 
| @@ -12,7 +12,9 @@ module Loxxy | |
| 12 12 | 
             
                  attr_reader :else_stmt
         | 
| 13 13 |  | 
| 14 14 | 
             
                  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
         | 
| 15 | 
            -
                  # @param  | 
| 15 | 
            +
                  # @param condExpr [Loxxy::Ast::LoxNode]
         | 
| 16 | 
            +
                  # @param thenStmt [Loxxy::Ast::LoxNode]
         | 
| 17 | 
            +
                  # @param elseStmt [Loxxy::Ast::LoxNode]
         | 
| 16 18 | 
             
                  def initialize(aPosition, condExpr, thenStmt, elseStmt)
         | 
| 17 19 | 
             
                    super(aPosition, [condExpr])
         | 
| 18 20 | 
             
                    @then_stmt = thenStmt
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../error'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Loxxy
         | 
| 6 | 
            +
              module BackEnd
         | 
| 7 | 
            +
                Signature = Struct.new(:parameter_types)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # A Lox binary operator
         | 
| 10 | 
            +
                class BinaryOperator
         | 
| 11 | 
            +
                  # @return [String] text representation of the operator
         | 
| 12 | 
            +
                  attr_reader :name
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # @return [Array<Class>]
         | 
| 15 | 
            +
                  attr_reader :signatures
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # @param aName [String] "name" of operator
         | 
| 18 | 
            +
                  # @param theSignatures [Array<Signature>] allowed signatures
         | 
| 19 | 
            +
                  def initialize(aName, theSignatures)
         | 
| 20 | 
            +
                    @name = aName
         | 
| 21 | 
            +
                    @signatures = theSignatures
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # rubocop: disable Style/ClassEqualityComparison
         | 
| 25 | 
            +
                  def validate_operands(operand1, operand2)
         | 
| 26 | 
            +
                    compliant = signatures.find do |(type1, type2)|
         | 
| 27 | 
            +
                      next unless operand1.kind_of?(type1)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      if type2 == :idem
         | 
| 30 | 
            +
                        (operand2.class == operand1.class)
         | 
| 31 | 
            +
                      else
         | 
| 32 | 
            +
                        operand2.kind_of?(type2)
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                    # rubocop: enable Style/ClassEqualityComparison
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    unless compliant
         | 
| 38 | 
            +
                      err = Loxxy::RuntimeError
         | 
| 39 | 
            +
                      if signatures.size == 1 && signatures[0].last == :idem
         | 
| 40 | 
            +
                        raise err, "Operands must be #{datatype_name(signatures[0].first)}s."
         | 
| 41 | 
            +
                      elsif signatures.size == 2 && signatures.all? { |(_, second)| second == :idem }
         | 
| 42 | 
            +
                        type1 = datatype_name(signatures[0].first)
         | 
| 43 | 
            +
                        type2 = datatype_name(signatures[1].first)
         | 
| 44 | 
            +
                        raise err, "Operands must be two #{type1}s or two #{type2}s."
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  private
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def datatype_name(aClass)
         | 
| 52 | 
            +
                    # (?:(?:[^:](?!:|(?<=LX))))+$
         | 
| 53 | 
            +
                    aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end # class
         | 
| 56 | 
            +
              end # module
         | 
| 57 | 
            +
            end # module
         | 
| @@ -2,7 +2,10 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            # Load all the classes implementing AST nodes
         | 
| 4 4 | 
             
            require_relative '../ast/all_lox_nodes'
         | 
| 5 | 
            +
            require_relative 'binary_operator'
         | 
| 6 | 
            +
            require_relative 'function'
         | 
| 5 7 | 
             
            require_relative 'symbol_table'
         | 
| 8 | 
            +
            require_relative 'unary_operator'
         | 
| 6 9 |  | 
| 7 10 | 
             
            module Loxxy
         | 
| 8 11 | 
             
              module BackEnd
         | 
| @@ -19,12 +22,24 @@ module Loxxy | |
| 19 22 | 
             
                  # @return [Array<Datatype::BuiltinDatatyp>] Stack for the values of expr
         | 
| 20 23 | 
             
                  attr_reader :stack
         | 
| 21 24 |  | 
| 25 | 
            +
                 # @return [Hash { Symbol => UnaryOperator}]
         | 
| 26 | 
            +
                  attr_reader :unary_operators
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # @return [Hash { Symbol => BinaryOperator}]
         | 
| 29 | 
            +
                  attr_reader :binary_operators
         | 
| 30 | 
            +
             | 
| 22 31 | 
             
                  # @param theOptions [Hash]
         | 
| 23 32 | 
             
                  def initialize(theOptions)
         | 
| 24 33 | 
             
                    @config = theOptions
         | 
| 25 34 | 
             
                    @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
         | 
| 26 35 | 
             
                    @symbol_table = SymbolTable.new
         | 
| 27 36 | 
             
                    @stack = []
         | 
| 37 | 
            +
                    @unary_operators = {}
         | 
| 38 | 
            +
                    @binary_operators = {}
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    init_unary_operators
         | 
| 41 | 
            +
                    init_binary_operators
         | 
| 42 | 
            +
                    init_globals
         | 
| 28 43 | 
             
                  end
         | 
| 29 44 |  | 
| 30 45 | 
             
                  # Given an abstract syntax parse tree visitor, launch the visit
         | 
| @@ -80,7 +95,7 @@ module Loxxy | |
| 80 95 |  | 
| 81 96 | 
             
                  def after_print_stmt(_printStmt)
         | 
| 82 97 | 
             
                    tos = stack.pop
         | 
| 83 | 
            -
                    @ostream.print tos.to_str
         | 
| 98 | 
            +
                    @ostream.print tos ? tos.to_str : 'nil'
         | 
| 84 99 | 
             
                  end
         | 
| 85 100 |  | 
| 86 101 | 
             
                  def after_while_stmt(aWhileStmt, aVisitor)
         | 
| @@ -145,9 +160,11 @@ module Loxxy | |
| 145 160 | 
             
                  end
         | 
| 146 161 |  | 
| 147 162 | 
             
                  def after_binary_expr(aBinaryExpr)
         | 
| 148 | 
            -
                    op = aBinaryExpr.operator
         | 
| 149 163 | 
             
                    operand2 = stack.pop
         | 
| 150 164 | 
             
                    operand1 = stack.pop
         | 
| 165 | 
            +
                    op = aBinaryExpr.operator
         | 
| 166 | 
            +
                    operator = binary_operators[op]
         | 
| 167 | 
            +
                    operator.validate_operands(operand1, operand2)
         | 
| 151 168 | 
             
                    if operand1.respond_to?(op)
         | 
| 152 169 | 
             
                      stack.push operand1.send(op, operand2)
         | 
| 153 170 | 
             
                    else
         | 
| @@ -157,16 +174,39 @@ module Loxxy | |
| 157 174 | 
             
                  end
         | 
| 158 175 |  | 
| 159 176 | 
             
                  def after_unary_expr(anUnaryExpr)
         | 
| 160 | 
            -
                    op = anUnaryExpr.operator
         | 
| 161 177 | 
             
                    operand = stack.pop
         | 
| 178 | 
            +
                    op = anUnaryExpr.operator
         | 
| 179 | 
            +
                    operator = unary_operators[op]
         | 
| 180 | 
            +
                    operator.validate_operand(operand)
         | 
| 162 181 | 
             
                    if operand.respond_to?(op)
         | 
| 163 182 | 
             
                      stack.push operand.send(op)
         | 
| 164 183 | 
             
                    else
         | 
| 165 | 
            -
                      msg1 = "`#{op}': Unimplemented operator for a #{ | 
| 184 | 
            +
                      msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
         | 
| 166 185 | 
             
                      raise StandardError, msg1
         | 
| 167 186 | 
             
                    end
         | 
| 168 187 | 
             
                  end
         | 
| 169 188 |  | 
| 189 | 
            +
                  def after_call_expr(aCallExpr, aVisitor)
         | 
| 190 | 
            +
                    # Evaluate callee part
         | 
| 191 | 
            +
                    aCallExpr.callee.accept(aVisitor)
         | 
| 192 | 
            +
                    callee = stack.pop
         | 
| 193 | 
            +
                    aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                    if callee.kind_of?(NativeFunction)
         | 
| 196 | 
            +
                      stack.push callee.call # Pass arguments
         | 
| 197 | 
            +
                    else
         | 
| 198 | 
            +
                      new_env = Environment.new(symbol_table.current_env)
         | 
| 199 | 
            +
                      symbol_table.enter_environment(new_env)
         | 
| 200 | 
            +
                      callee.parameters&.each do |param_name|
         | 
| 201 | 
            +
                        local = Variable.new(param_name, stack.pop)
         | 
| 202 | 
            +
                        symbol_table.insert(local)
         | 
| 203 | 
            +
                      end
         | 
| 204 | 
            +
                      callee.call(aVisitor)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                      symbol_table.leave_environment
         | 
| 207 | 
            +
                    end
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 170 210 | 
             
                  def after_grouping_expr(_groupingExpr)
         | 
| 171 211 | 
             
                    # Do nothing: work was already done by visiting /evaluating the subexpression
         | 
| 172 212 | 
             
                  end
         | 
| @@ -184,10 +224,91 @@ module Loxxy | |
| 184 224 | 
             
                    stack.push(literalExpr.literal)
         | 
| 185 225 | 
             
                  end
         | 
| 186 226 |  | 
| 187 | 
            -
                  # @param  | 
| 227 | 
            +
                  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
         | 
| 188 228 | 
             
                  def before_visit_builtin(aValue)
         | 
| 189 229 | 
             
                    stack.push(aValue)
         | 
| 190 230 | 
             
                  end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                  def after_fun_stmt(aFunStmt, _visitor)
         | 
| 233 | 
            +
                    function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
         | 
| 234 | 
            +
                    new_var = Variable.new(aFunStmt.name, function)
         | 
| 235 | 
            +
                    symbol_table.insert(new_var)
         | 
| 236 | 
            +
                  end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                  private
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                  NativeFunction = Struct.new(:callable, :interp) do
         | 
| 241 | 
            +
                    def accept(_visitor)
         | 
| 242 | 
            +
                      interp.stack.push self
         | 
| 243 | 
            +
                    end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                    def call
         | 
| 246 | 
            +
                      callable.call
         | 
| 247 | 
            +
                    end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                    def to_str
         | 
| 250 | 
            +
                      '<native fn>'
         | 
| 251 | 
            +
                    end
         | 
| 252 | 
            +
                  end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                  def init_unary_operators
         | 
| 255 | 
            +
                    negate_op = UnaryOperator.new('-', [Datatype::Number])
         | 
| 256 | 
            +
                    unary_operators[:-@] = negate_op
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                    negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype])
         | 
| 259 | 
            +
                    unary_operators[:!] = negation_op
         | 
| 260 | 
            +
                  end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                  def init_binary_operators
         | 
| 263 | 
            +
                    plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
         | 
| 264 | 
            +
                      [Datatype::LXString, :idem]])
         | 
| 265 | 
            +
                    binary_operators[:+] = plus_op
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                    minus_op = BinaryOperator.new('-', [[Datatype::Number, :idem]])
         | 
| 268 | 
            +
                    binary_operators[:-] = minus_op
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                    star_op = BinaryOperator.new('*', [[Datatype::Number, :idem]])
         | 
| 271 | 
            +
                    binary_operators[:*] = star_op
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                    slash_op = BinaryOperator.new('/', [[Datatype::Number, :idem]])
         | 
| 274 | 
            +
                    binary_operators[:/] = slash_op
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                    equal_equal_op = BinaryOperator.new('==', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
         | 
| 277 | 
            +
                    binary_operators[:==] = equal_equal_op
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                    not_equal_op = BinaryOperator.new('!=', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
         | 
| 280 | 
            +
                    binary_operators[:!=] = not_equal_op
         | 
| 281 | 
            +
             | 
| 282 | 
            +
                    less_op = BinaryOperator.new('<', [[Datatype::Number, :idem]])
         | 
| 283 | 
            +
                    binary_operators[:<] = less_op
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                    less_equal_op = BinaryOperator.new('<=', [[Datatype::Number, :idem]])
         | 
| 286 | 
            +
                    binary_operators[:<=] = less_equal_op
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                    greater_op = BinaryOperator.new('>', [[Datatype::Number, :idem]])
         | 
| 289 | 
            +
                    binary_operators[:>] = greater_op
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    greater_equal_op = BinaryOperator.new('>=', [[Datatype::Number, :idem]])
         | 
| 292 | 
            +
                    binary_operators[:>=] = greater_equal_op
         | 
| 293 | 
            +
                  end
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                  def init_globals
         | 
| 296 | 
            +
                    add_native_fun('clock', native_clock)
         | 
| 297 | 
            +
                  end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  def add_native_fun(aName, aProc)
         | 
| 300 | 
            +
                    native_fun = Variable.new(aName, NativeFunction.new(aProc, self))
         | 
| 301 | 
            +
                    symbol_table.insert(native_fun)
         | 
| 302 | 
            +
                  end
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                  # Ruby-native function that returns (as float) the number of seconds since
         | 
| 305 | 
            +
                  # a given time reference.
         | 
| 306 | 
            +
                  def native_clock
         | 
| 307 | 
            +
                    proc do
         | 
| 308 | 
            +
                      now = Time.now.to_f
         | 
| 309 | 
            +
                      Datatype::Number.new(now)
         | 
| 310 | 
            +
                    end
         | 
| 311 | 
            +
                  end
         | 
| 191 312 | 
             
                end # class
         | 
| 192 313 | 
             
              end # module
         | 
| 193 314 | 
             
            end # module
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../datatype/all_datatypes'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Loxxy
         | 
| 6 | 
            +
              module BackEnd
         | 
| 7 | 
            +
                # rubocop: disable Style/AccessorGrouping
         | 
| 8 | 
            +
                # Representation of a Lox function.
         | 
| 9 | 
            +
                # It is a named slot that can be associated with a value at the time.
         | 
| 10 | 
            +
                class Function
         | 
| 11 | 
            +
                  # @return [String]
         | 
| 12 | 
            +
                  attr_reader :name
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # @return [Array<>] the parameters
         | 
| 15 | 
            +
                  attr_reader :parameters
         | 
| 16 | 
            +
                  attr_reader :body
         | 
| 17 | 
            +
                  attr_reader :stack
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # Create a variable with given name and initial value
         | 
| 20 | 
            +
                  # @param aName [String] The name of the variable
         | 
| 21 | 
            +
                  # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
         | 
| 22 | 
            +
                  def initialize(aName, parameterList, aBody, aStack)
         | 
| 23 | 
            +
                    @name = aName.dup
         | 
| 24 | 
            +
                    @parameters = parameterList
         | 
| 25 | 
            +
                    @body = aBody
         | 
| 26 | 
            +
                    @stack = aStack
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def accept(_visitor)
         | 
| 30 | 
            +
                    stack.push self
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def call(aVisitor)
         | 
| 34 | 
            +
                    body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  # Text representation of a Lox function
         | 
| 38 | 
            +
                  def to_str
         | 
| 39 | 
            +
                    "<fn #{name}>"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end # class
         | 
| 42 | 
            +
                # rubocop: enable Style/AccessorGrouping
         | 
| 43 | 
            +
              end # module
         | 
| 44 | 
            +
            end # module
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../error'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Loxxy
         | 
| 6 | 
            +
              module BackEnd
         | 
| 7 | 
            +
                # A Lox unary operator
         | 
| 8 | 
            +
                class UnaryOperator
         | 
| 9 | 
            +
                  # @return [String] text representation of the operator
         | 
| 10 | 
            +
                  attr_reader :name
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # @return [Array<Class>]
         | 
| 13 | 
            +
                  attr_reader :signatures
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # @param aName [String] "name" of operator
         | 
| 16 | 
            +
                  # @param theSignatures [Array<Class>] allowed signatures
         | 
| 17 | 
            +
                  def initialize(aName, theSignatures)
         | 
| 18 | 
            +
                    @name = aName
         | 
| 19 | 
            +
                    @signatures = theSignatures
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def validate_operand(operand1)
         | 
| 23 | 
            +
                    compliant = signatures.find { |some_type| operand1.kind_of?(some_type) }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    unless compliant
         | 
| 26 | 
            +
                      err = Loxxy::RuntimeError
         | 
| 27 | 
            +
                      # if signatures.size == 1
         | 
| 28 | 
            +
                      raise err, "Operand must be a #{datatype_name(signatures[0])}."
         | 
| 29 | 
            +
                      # end
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  private
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def datatype_name(aClass)
         | 
| 36 | 
            +
                    # (?:(?:[^:](?!:|(?<=LX))))+$
         | 
| 37 | 
            +
                    aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end # class
         | 
| 40 | 
            +
              end # module
         | 
| 41 | 
            +
            end # module
         | 
    
        data/lib/loxxy/error.rb
    ADDED