loxxy 0.0.11 → 0.0.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 583f5c2f620ea8f45b339607103955eda2f32c02f0358ccb88cf4d20b4398d21
4
- data.tar.gz: f349b949c1315473027eaaab589ff1cf2da89deca3b243cd1508c088bf9a9cc5
3
+ metadata.gz: 7274fd2d139354b984962f9317cae890344e528540f2f157772e7395cdfd7d33
4
+ data.tar.gz: c1b38a4dfcb441c00604926cbaf397ab555624270ca647448d9cc4d4f463f500
5
5
  SHA512:
6
- metadata.gz: 9c93648c634783348a22b3b65cf710a59c0ca5e3cefc5a20d7c60b9b429b4334e1e5603cc2bf74f302aed881daa71634cb7253445b3f376838197637f79f8cc7
7
- data.tar.gz: 67d60e89985322911a7842a2b5fc2e22b4ccc3136ffa216027089ea05ea56a6932d1de9b8917173ad072695aaa6abb2147d456f54d09f2878d7831937c42af74
6
+ metadata.gz: 27d5de6532213d1a995f86da84ccbd41a11b5f85c305e57a62f64637ad17852e622685e4261b1c62deb31c924bb8f8c29c4edd97e9776fabf7e0d06e5c302534
7
+ data.tar.gz: dab4a9e391c120c159a63e316f90065faf1415b54c9aa4c6f349f6be8e95de1289a2478f395e6b9e19b2e6f09482ae4db49a70e0d5d857eb5475cecad0590c69
@@ -1,3 +1,16 @@
1
+ ## [0.0.12] - 2021-01-09
2
+ - Initial interpreter capable of evaluating a tiny subset of Lox language.
3
+
4
+ ## Added
5
+ - Class `AST::LoxNoopExpr`
6
+ - Class `AST::LoxPrintStmt`
7
+ - Class `BackEnd::Engine` implementation of the print statement logic
8
+ - Class `Interpreter`
9
+
10
+ ## Changed
11
+ - Class `Ast::ASTVisitor` Added visit method
12
+ - File `README.md` added Hello world example.
13
+
1
14
  ## [0.0.11] - 2021-01-08
2
15
  - AST node generation for logical expression (and, or).
3
16
 
data/README.md CHANGED
@@ -13,58 +13,26 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
13
13
  a Lox interpreter written in Lox.
14
14
 
15
15
  ## Current status
16
- The __loxxy__ gem hosts two distinct parsers classes (`RawParser` and `Parser`).
17
- - A `RawParser` instance is able to recognize valid Lox input and to generate
18
- a concrete parse tree from it.
19
- - A `Parser` instance can also parse Lox source code but will generate an AST
20
- (Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
21
- Currently it generates AST for arithmetic expressions with literal numbers only.
22
-
23
- ## Roadmap
24
- ### Done
25
- - Scanner (tokenizer)
26
- - Lox grammar (in format required by Rley gem)
27
- - Raw parser. It parses Lox programs and generates a raw parse tree.
28
- - Tailored parser for generating AST (Abstract Syntax Tree)
29
-
30
- ### Started
31
- - Custom AST builder class
32
- - Classes for representing Lox expressions in AST
33
-
34
- ### TODO
35
- AST Node generation
36
- Goal: parser should generate AST for any input Lox program
37
- - [X] Equality operator
38
- - [X] Logical operator (and, or)
39
- - [] Unary expressions (negate, not)
40
- - [] Grouping expressions
41
- - [] Print statement
42
- - [] Simple assignment expressions
43
- - [] Variable declaration
44
- - [] Dot notation
45
- - [] Block statement
46
- - [] If statement
47
- - [] For statement
48
- - [] While statement
49
- - [] Function declaration
50
- - [] Call expression
51
- - [] Return statement
52
- - [] Class declaration
53
- - [] AST generation covers complete grammar
54
-
55
- Tree-walking:
56
- - [] Tree visitor recognizes all AST node types
57
-
58
- Interpreter:
59
- - Keywords: symbol table, scope, activation record, class & object model, Lox test suite
60
- - [] Milestone: Interpreter handles expressions but function calls
61
- - [] Milestone: Interpreter handles `for`, `if`, `print`, `while` and block statements
62
- - [] Milestone: Interpreter handles variable declarations (global, block)
63
- - [] Milestone: Interpreter handles function declarations and calls
64
- - [] Milestone: Interpreter supports class and object definition
65
- - [] Milestone: Lox interpreter complete
16
+ The project is still in inception and the interpreter is being implemented...
17
+ Currently it can execute a very limited subset of __Lox__ language.
18
+
19
+ The __loxxy__ gem also a parser class `RawPaser` that can, in principle, any valid Lox input.
20
+
21
+ ## Hello world example
22
+ ```ruby
23
+ require 'loxxy'
24
+
25
+ lox_program = <<LOX_END
26
+ // Your first Lox program!
27
+ print "Hello, world!";
28
+ LOX_END
29
+
30
+ lox = Loxxy::Interpreter.new
31
+ lox.evaluate(lox_program) # => Hello, world!
32
+ ```
66
33
 
67
34
  ## Example using RawParser class
35
+
68
36
  ```ruby
69
37
  require 'loxxy'
70
38
 
@@ -109,6 +77,16 @@ program
109
77
  +-- EOF: ''
110
78
  ```
111
79
 
80
+ ## Suppported Lox language features
81
+ Although the interpreter should parse almost any valid Lox program,
82
+ it currently can evaluate a tiny set of AST node (AST = Abstract Syntax Tree).
83
+
84
+ Here are the language features supported by the interpreter:
85
+ - Line comments,
86
+ - All the Lox literals (booleans, numbers, strings and nil),
87
+ - `print` statement.
88
+
89
+
112
90
  ## Installation
113
91
 
114
92
  Add this line to your application's Gemfile:
@@ -1,10 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'loxxy/version'
4
+ require_relative 'loxxy/interpreter'
4
5
  require_relative 'loxxy/front_end/raw_parser'
5
- require_relative 'loxxy/front_end/parser'
6
6
 
7
+ # Namespace for all classes and constants of __loxxy__ gem.
7
8
  module Loxxy
8
9
  class Error < StandardError; end
9
- # Your code goes here...
10
+
11
+ # Shorthand method. Returns the sole object that represents
12
+ # a Lox false literal.
13
+ # @return [Loxxy::Datatype::False]
14
+ def self.lox_false
15
+ Datatype::False.instance
16
+ end
17
+
18
+ # Shorthand method. Returns the sole object that represents
19
+ # a Lox nil literal.
20
+ # @return [Loxxy::Datatype::Nil]
21
+ def self.lox_nil
22
+ Datatype::Nil.instance
23
+ end
24
+
25
+ # Shorthand method. Returns the sole object that represents
26
+ # a Lox true literal.
27
+ # @return [Loxxy::Datatype::True]
28
+ def self.lox_true
29
+ Datatype::False.instance
30
+ end
10
31
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_print_stmt'
4
+ require_relative 'lox_literal_expr'
5
+ require_relative 'lox_noop_expr'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../datatype/all_datatypes'
4
- require_relative 'lox_literal_expr'
4
+ require_relative 'all_lox_nodes'
5
5
  require_relative 'lox_binary_expr'
6
6
 
7
7
  module Loxxy
@@ -10,31 +10,33 @@ module Loxxy
10
10
  # (Abstract Syntax Tree) from a sequence of input tokens and
11
11
  # visit events produced by walking over a GFGParsing object.
12
12
  class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
13
- # Mapping Token name => operator | separator | delimiter characters
14
- # @return [Hash{String => String}]
15
- Name2special = {
16
- 'AND' => 'and',
17
- 'BANG' => '!',
18
- 'BANG_EQUAL' => '!=',
19
- 'COMMA' => ',',
20
- 'DOT' => '.',
21
- 'EQUAL' => '=',
22
- 'EQUAL_EQUAL' => '==',
23
- 'GREATER' => '>',
24
- 'GREATER_EQUAL' => '>=',
25
- 'LEFT_BRACE' => '{',
26
- 'LEFT_PAREN' => '(',
27
- 'LESS' => '<',
28
- 'LESS_EQUAL' => '<=',
29
- 'MINUS' => '-',
30
- 'OR' => 'or',
31
- 'PLUS' => '+',
32
- 'RIGHT_BRACE' => '}',
33
- 'RIGHT_PAREN' => ')',
34
- 'SEMICOLON' => ';',
35
- 'SLASH' => '/',
36
- 'STAR' => '*'
37
- }.freeze
13
+ unless defined?(Name2special)
14
+ # Mapping Token name => operator | separator | delimiter characters
15
+ # @return [Hash{String => String}]
16
+ Name2special = {
17
+ 'AND' => 'and',
18
+ 'BANG' => '!',
19
+ 'BANG_EQUAL' => '!=',
20
+ 'COMMA' => ',',
21
+ 'DOT' => '.',
22
+ 'EQUAL' => '=',
23
+ 'EQUAL_EQUAL' => '==',
24
+ 'GREATER' => '>',
25
+ 'GREATER_EQUAL' => '>=',
26
+ 'LEFT_BRACE' => '{',
27
+ 'LEFT_PAREN' => '(',
28
+ 'LESS' => '<',
29
+ 'LESS_EQUAL' => '<=',
30
+ 'MINUS' => '-',
31
+ 'OR' => 'or',
32
+ 'PLUS' => '+',
33
+ 'RIGHT_BRACE' => '}',
34
+ 'RIGHT_PAREN' => ')',
35
+ 'SEMICOLON' => ';',
36
+ 'SLASH' => '/',
37
+ 'STAR' => '*'
38
+ }.freeze
39
+ end # defined
38
40
 
39
41
  attr_reader :strict
40
42
 
@@ -117,6 +119,25 @@ module Loxxy
117
119
  [[operator, operand2]]
118
120
  end
119
121
 
122
+ #####################################
123
+ # SEMANTIC ACTIONS
124
+ #####################################
125
+
126
+ # rule('program' => 'EOF').as 'null_program'
127
+ def reduce_null_program(_production, range, _tokens, _theChildren)
128
+ Ast::LoxNoopExpr.new(tokens[0].position)
129
+ end
130
+
131
+ # rule('program' => 'declaration_plus EOF').as ''
132
+ def reduce_lox_program(_production, range, tokens, theChildren)
133
+ return_first_child(range, tokens, theChildren)
134
+ end
135
+
136
+ # rule('printStmt' => 'PRINT expression SEMICOLON')
137
+ def reduce_print_stmt(_production, _range, tokens, theChildren)
138
+ Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
139
+ end
140
+
120
141
  # rule('logic_or' => 'logic_and disjunct_plus')
121
142
  def reduce_logic_or_plus(production, range, tokens, theChildren)
122
143
  reduce_binary_operator(production, range, tokens, theChildren)
@@ -141,7 +162,7 @@ module Loxxy
141
162
  def reduce_logic_and_plus_more(production, range, tokens, theChildren)
142
163
  reduce_binary_plus_more(production, range, tokens, theChildren)
143
164
  end
144
-
165
+
145
166
  # rule('conjunct_plus' => 'AND equality')
146
167
  def reduce_logic_and_plus_end(production, range, tokens, theChildren)
147
168
  reduce_binary_plus_end(production, range, tokens, theChildren)
@@ -39,75 +39,55 @@ module Loxxy
39
39
  top.accept(self)
40
40
  end
41
41
 
42
- # Visit event. The visitor is visiting the
43
- # given terminal node containing a datatype object.
44
- # @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
45
- def visit_literal_expr(aLiteralExpr)
46
- broadcast(:before_literal_expr, aLiteralExpr)
47
- broadcast(:after_literal_expr, aLiteralExpr)
42
+ # Visit event. The visitor is about to visit the ptree.
43
+ # @param aParseTree [Rley::PTre::ParseTree] the ptree to visit.
44
+ def start_visit_ptree(aParseTree)
45
+ broadcast(:before_ptree, aParseTree)
48
46
  end
49
47
 
50
- =begin
51
-
48
+ # Visit event. The visitor has completed the visit of the ptree.
49
+ # @param aParseTree [Rley::PTre::ParseTree] the visited ptree.
50
+ def end_visit_ptree(aParseTree)
51
+ broadcast(:after_ptree, aParseTree)
52
+ end
52
53
 
53
- def visit_compound_datum(aCompoundDatum)
54
- broadcast(:before_compound_datum, aCompoundDatum)
55
- traverse_children(aCompoundDatum)
56
- broadcast(:after_compound_datum, aCompoundDatum)
54
+ # Visit event. The visitor is about to visit a print statement expression.
55
+ # @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
56
+ def visit_print_stmt(aPrintStmt)
57
+ broadcast(:before_print_stmt, aPrintStmt)
58
+ traverse_subnodes(aPrintStmt)
59
+ broadcast(:after_print_stmt, aPrintStmt)
57
60
  end
58
61
 
59
62
  # Visit event. The visitor is visiting the
60
- # given empty list object.
61
- # @param anEmptyList [SkmEmptyList] the empty list object to visit.
62
- def visit_empty_list(anEmptyList)
63
- broadcast(:before_empty_list, anEmptyList)
64
- broadcast(:after_empty_list, anEmptyList)
63
+ # given terminal node containing a datatype object.
64
+ # @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
65
+ def visit_literal_expr(aLiteralExpr)
66
+ broadcast(:before_literal_expr, aLiteralExpr)
67
+ broadcast(:after_literal_expr, aLiteralExpr)
65
68
  end
66
69
 
67
- def visit_pair(aPair)
68
- broadcast(:before_pair, aPair)
69
- traverse_car_cdr(aPair)
70
- broadcast(:after_pair, aPair)
71
- end
72
- =end
73
- =begin
74
- # Visit event. The visitor is about to visit the given non terminal node.
75
- # @param aNonTerminalNode [NonTerminalNode] the node to visit.
76
- def visit_nonterminal(aNonTerminalNode)
77
- if @traversal == :post_order
78
- broadcast(:before_non_terminal, aNonTerminalNode)
79
- traverse_subnodes(aNonTerminalNode)
80
- else
81
- traverse_subnodes(aNonTerminalNode)
82
- broadcast(:before_non_terminal, aNonTerminalNode)
70
+ # Visit event. The visitor is about to visit the given non terminal node.
71
+ # @param aNonTerminalNode [Rley::PTre::NonTerminalNode] the node to visit.
72
+ def visit_nonterminal(_non_terminal_node)
73
+ # Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
74
+ # that it cannot handle.
75
+ raise NotImplementedError, 'Loxxy cannot execute this code yet.'
83
76
  end
84
- broadcast(:after_non_terminal, aNonTerminalNode)
85
- end
86
- =end
87
77
 
88
78
  private
89
79
 
90
- def traverse_children(aParent)
91
- children = aParent.children
92
- broadcast(:before_children, aParent, children)
80
+ # Visit event. The visitor is about to visit the subnodes of a non
81
+ # terminal node.
82
+ # @param aParentNode [Ast::LocCompoundExpr] the parent node.
83
+ def traverse_subnodes(aParentNode)
84
+ subnodes = aParentNode.subnodes
85
+ broadcast(:before_subnodes, aParentNode, subnodes)
93
86
 
94
- # Let's proceed with the visit of children
95
- children.each { |a_child| a_child.accept(self) }
87
+ # Let's proceed with the visit of subnodes
88
+ subnodes.each { |a_node| a_node.accept(self) }
96
89
 
97
- broadcast(:after_children, aParent, children)
98
- end
99
-
100
- def traverse_car_cdr(aPair)
101
- if aPair.car
102
- broadcast(:before_car, aPair, aPair.car)
103
- aPair.car.accept(self)
104
- broadcast(:after_car, aPair, aPair.car)
105
- end
106
- if aPair.cdr
107
- broadcast(:before_cdr, aPair, aPair.cdr)
108
- aPair.cdr.accept(self)
109
- broadcast(:after_cdr, aPair, aPair.cdr)
110
- end
90
+ broadcast(:after_subnodes, aParentNode, subnodes)
111
91
  end
112
92
 
113
93
  # Send a notification to all subscribers.
@@ -117,7 +97,7 @@ module Loxxy
117
97
  subscribers.each do |subscr|
118
98
  next unless subscr.respond_to?(msg) || subscr.respond_to?(:accept_all)
119
99
 
120
- subscr.send(msg, runtime, *args)
100
+ subscr.send(msg, *args)
121
101
  end
122
102
  end
123
103
  end # class
@@ -5,7 +5,7 @@ require_relative 'lox_node'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxCompoundExpr < LoxNode
8
- # @return [Array<Ast::LoxNode>]
8
+ # @return [Array<Ast::LoxNode>] the children nodes (sub-expressions)
9
9
  attr_reader :subnodes
10
10
 
11
11
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
@@ -11,6 +11,11 @@ module Loxxy
11
11
  @position = aPosition
12
12
  end
13
13
 
14
+ # Notification that the parsing was successfully completed
15
+ def done!
16
+ # Default: do nothing ...
17
+ end
18
+
14
19
  # Abstract method.
15
20
  # Part of the 'visitee' role in Visitor design pattern.
16
21
  # @param _visitor [LoxxyTreeVisitor] the visitor
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxNoopExpr < LoxNode
8
+ # Abstract method.
9
+ # Part of the 'visitee' role in Visitor design pattern.
10
+ # @param _visitor [LoxxyTreeVisitor] the visitor
11
+ def accept(_visitor)
12
+ # Do nothing...
13
+ end
14
+ end # class
15
+ end # module
16
+ end # module
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxPrintStmt < LoxCompoundExpr
8
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
9
+ # @param anExpression [Ast::LoxNode] expression to output
10
+ def initialize(aPosition, anExpression)
11
+ super(aPosition, [anExpression])
12
+ end
13
+
14
+ # Abstract method.
15
+ # Part of the 'visitee' role in Visitor design pattern.
16
+ # @param visitor [Ast::ASTVisitor] the visitor
17
+ def accept(visitor)
18
+ visitor.visit_print_stmt(self)
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load all the classes implementing AST nodes
4
+ require_relative '../ast/all_lox_nodes'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # An instance of this class executes the statements as when they
9
+ # occur during the abstract syntax tree walking.
10
+ # @note WIP: very crude implementation.
11
+ class Engine
12
+ # @return [Hash] A set of configuration options
13
+ attr_reader :config
14
+
15
+ # @return [Array] Data stack used for passing data between statements
16
+ attr_reader :stack
17
+
18
+ # @param theOptions [Hash]
19
+ def initialize(theOptions)
20
+ @config = theOptions
21
+ @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
22
+ @stack = []
23
+ end
24
+
25
+ # Given an abstract syntax parse tree visitor, luanch the visit
26
+ # and execute the visit events in the output stream.
27
+ # @param aVisitor [AST::ASTVisitor]
28
+ def execute(aVisitor)
29
+ aVisitor.subscribe(self)
30
+ aVisitor.start
31
+ aVisitor.unsubscribe(self)
32
+ stack.empty? ? Datatype::Nil.instance : stack.pop
33
+ end
34
+
35
+ # Visit event handling
36
+
37
+ def after_print_stmt(_printStmt)
38
+ tos = stack.pop
39
+ @ostream.print tos.to_str
40
+ end
41
+
42
+ # @param literalExpr [Ast::LoxLiteralExpr]
43
+ def before_literal_expr(literalExpr)
44
+ stack.push(literalExpr.literal)
45
+ end
46
+ end # class
47
+ end # module
48
+ end # module
@@ -14,6 +14,12 @@ module Loxxy
14
14
  @value = validated_value(aValue)
15
15
  end
16
16
 
17
+ # Method called from Lox to obtain the text representation of the boolean.
18
+ # @return [String]
19
+ def to_str
20
+ value.to_s # Default implementation...
21
+ end
22
+
17
23
  protected
18
24
 
19
25
  def validated_value(aValue)
@@ -21,6 +21,12 @@ module Loxxy
21
21
  end
22
22
  end
23
23
 
24
+ # Method called from Lox to obtain the text representation of the object.
25
+ # @return [String]
26
+ def to_str
27
+ value
28
+ end
29
+
24
30
  protected
25
31
 
26
32
  def validated_value(aValue)
@@ -13,6 +13,12 @@ module Loxxy
13
13
  def initialize
14
14
  super(nil)
15
15
  end
16
+
17
+ # Method called from Lox to obtain the text representation of nil.
18
+ # @return [String]
19
+ def to_str
20
+ 'nil'
21
+ end
16
22
  end # class
17
23
 
18
24
  Nil.instance.freeze # Make the sole instance immutable
@@ -26,8 +26,8 @@ module Loxxy
26
26
  add_terminals('EOF')
27
27
 
28
28
  # Top-level rule that matches an entire Lox program
29
- rule('program' => 'EOF')
30
- rule('program' => 'declaration_plus EOF')
29
+ rule('program' => 'EOF').as 'null_program'
30
+ rule('program' => 'declaration_plus EOF').as 'lox_program'
31
31
 
32
32
  # Declarations: bind an identifier to something
33
33
  rule('declaration_plus' => 'declaration_plus declaration')
@@ -73,7 +73,7 @@ module Loxxy
73
73
  rule('elsePart_opt' => 'ELSE statement')
74
74
  rule('elsePart_opt' => [])
75
75
 
76
- rule('printStmt' => 'PRINT expression SEMICOLON')
76
+ rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
77
77
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
78
78
  rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
79
79
  rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './front_end/parser'
4
+ require_relative './ast/ast_visitor'
5
+ require_relative './back_end/engine'
6
+
7
+ module Loxxy
8
+ # A Lox tree-walking interpreter.
9
+ # It acts as a facade object that:
10
+ # - hides the internal plumbings of the front-end and back-end parts.
11
+ # - delegates all the core work to its subordinate objects.
12
+ # @note WIP: very crude implementation.
13
+ class Interpreter
14
+ # return [Hash]
15
+ attr_reader :config
16
+
17
+ # @param theOptions [Hash]
18
+ def initialize(theOptions = {})
19
+ @config = theOptions
20
+ end
21
+
22
+ # Evaluate the given Lox program
23
+ # @param lox_input [String] Lox program to evaluate
24
+ def evaluate(lox_input)
25
+ # Front-end scans, parses the input and blurps an AST...
26
+ parser = FrontEnd::Parser.new
27
+
28
+ # The AST is the data object passed to the back-end
29
+ ast_tree = parser.parse(lox_input)
30
+ visitor = Ast::ASTVisitor.new(ast_tree)
31
+
32
+ # Back-end launches the tree walking & reponds to visit events
33
+ # by executing the code determined by the visited AST node.
34
+ engine = BackEnd::Engine.new(config)
35
+ engine.execute(visitor)
36
+ end
37
+ end # class
38
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.11'
4
+ VERSION = '0.0.12'
5
5
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require 'stringio'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/loxxy/back_end/engine'
8
+
9
+ module Loxxy
10
+ module BackEnd
11
+ describe Engine do
12
+ let(:sample_options) do
13
+ { ostream: StringIO.new }
14
+ end
15
+ subject { Engine.new(sample_options) }
16
+
17
+ context 'Initialization:' do
18
+ it 'should accept a option Hash at initialization' do
19
+ expect { Engine.new(sample_options) }.not_to raise_error
20
+ end
21
+
22
+ it 'should know its config options' do
23
+ expect(subject.config).to eq(sample_options)
24
+ end
25
+
26
+ it 'should have an empty stack' do
27
+ expect(subject.stack).to be_empty
28
+ end
29
+ end
30
+
31
+ context 'Listening to visitor events:' do
32
+ let(:greeting) { Datatype::LXString.new('Hello, world') }
33
+ let(:sample_pos) { double('fake-position') }
34
+ let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
35
+
36
+ it "should react to 'before_literal_expr' event" do
37
+ expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
38
+ expect(subject.stack.pop).to eq(greeting)
39
+ end
40
+ end
41
+ end # describe
42
+ end # module
43
+ end # module
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Boolean do
11
+ let(:thruth_value) { false }
12
+ subject { Boolean.new(thruth_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a boolean value at initialization' do
16
+ expect { Boolean.new(thruth_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(thruth_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq('false')
27
+ end
28
+ end
29
+ end # describe
30
+ end # module
31
+ end # module
@@ -22,6 +22,10 @@ module Loxxy
22
22
  end
23
23
 
24
24
  context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq(sample_text)
27
+ end
28
+
25
29
  it 'compares with another string' do
26
30
  expect(subject).to eq(sample_text)
27
31
  expect(subject).to eq(LXString.new(sample_text.dup))
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Nil do
11
+ subject { Nil.instance }
12
+
13
+ context 'Initialization:' do
14
+ it 'should know its value' do
15
+ expect(subject.value).to be_nil
16
+ end
17
+ end
18
+
19
+ context 'Provided services:' do
20
+ it 'should give its display representation' do
21
+ expect(subject.to_str).to eq('nil')
22
+ end
23
+ end
24
+ end # describe
25
+ end # module
26
+ end # module
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/datatype/number'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Number do
11
+ let(:sample_value) { -12.34 }
12
+ subject { Number.new(sample_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a Numeric value at initialization' do
16
+ expect { Number.new(sample_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(sample_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq(sample_value.to_s)
27
+ end
28
+ end
29
+ end # describe
30
+ end # module
31
+ end # module
@@ -37,7 +37,7 @@ module Loxxy
37
37
  # Parse results MUST to comply to grammar rule:
38
38
  # program => declaration_star EOF
39
39
  # where the declaration_star MUST be empty
40
- expect(aParseTree.root.symbol.name).to eq('EOF')
40
+ expect(aParseTree.root).to be_kind_of(Ast::LoxNoopExpr)
41
41
  end
42
42
 
43
43
  it 'should cope with an empty input' do
@@ -64,7 +64,7 @@ module Loxxy
64
64
  it 'should parse a false literal' do
65
65
  input = 'false;'
66
66
  ptree = subject.parse(input)
67
- leaf = walk_subnodes(ptree.root, [0, 0])
67
+ leaf = ptree.root.subnodes[0]
68
68
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
69
69
  expect(leaf.literal).to be_equal(Datatype::False.instance)
70
70
  end
@@ -72,7 +72,7 @@ module Loxxy
72
72
  it 'should parse a true literal' do
73
73
  input = 'true;'
74
74
  ptree = subject.parse(input)
75
- leaf = walk_subnodes(ptree.root, [0, 0])
75
+ leaf = ptree.root.subnodes[0]
76
76
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
77
77
  expect(leaf.literal).to be_equal(Datatype::True.instance)
78
78
  end
@@ -81,7 +81,7 @@ module Loxxy
81
81
  inputs = %w[1234; 12.34;]
82
82
  inputs.each do |source|
83
83
  ptree = subject.parse(source)
84
- leaf = walk_subnodes(ptree.root, [0, 0])
84
+ leaf = ptree.root.subnodes[0]
85
85
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
86
86
  expect(leaf.literal).to be_kind_of(Datatype::Number)
87
87
  expect(leaf.literal.value).to eq(source.to_f)
@@ -96,7 +96,7 @@ module Loxxy
96
96
  ]
97
97
  inputs.each do |source|
98
98
  ptree = subject.parse(source)
99
- leaf = walk_subnodes(ptree.root, [0, 0])
99
+ leaf = ptree.root.subnodes[0]
100
100
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
101
101
  expect(leaf.literal).to be_kind_of(Datatype::LXString)
102
102
  expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
@@ -106,7 +106,7 @@ module Loxxy
106
106
  it 'should parse a nil literal' do
107
107
  input = 'nil;'
108
108
  ptree = subject.parse(input)
109
- leaf = walk_subnodes(ptree.root, [0, 0])
109
+ leaf = ptree.root.subnodes[0]
110
110
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
111
111
  expect(leaf.literal).to be_equal(Datatype::Nil.instance)
112
112
  end
@@ -119,21 +119,11 @@ module Loxxy
119
119
  print "Hello, world!";
120
120
  LOX_END
121
121
  ptree = subject.parse(program)
122
- root = ptree.root
123
- expect(root.symbol.name).to eq('program')
124
- (prnt_stmt, eof) = root.subnodes
125
- expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
126
- expect(prnt_stmt.symbol.name).to eq('printStmt')
127
- expect(prnt_stmt.subnodes.size).to eq(3)
128
- expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
129
- expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
130
- expect(prnt_stmt.subnodes[1]).to be_kind_of(Loxxy::Ast::LoxLiteralExpr)
131
- expect(prnt_stmt.subnodes[1].literal).to be_kind_of(Loxxy::Datatype::LXString)
132
- expect(prnt_stmt.subnodes[1].literal.value).to eq('Hello, world!')
133
- expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
134
- expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
135
- expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
136
- expect(eof.symbol.name).to eq('EOF')
122
+ prnt_stmt = ptree.root
123
+ expect(prnt_stmt).to be_kind_of(Ast::LoxPrintStmt)
124
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Ast::LoxLiteralExpr)
125
+ expect(prnt_stmt.subnodes[0].literal).to be_kind_of(Loxxy::Datatype::LXString)
126
+ expect(prnt_stmt.subnodes[0].literal.value).to eq('Hello, world!')
137
127
  end
138
128
  end # context
139
129
 
@@ -141,7 +131,7 @@ LOX_END
141
131
  it 'should parse the addition of two number literals' do
142
132
  input = '123 + 456;'
143
133
  ptree = subject.parse(input)
144
- parent = ptree.root.subnodes[0]
134
+ parent = ptree.root
145
135
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
146
136
  expect(parent.symbol.name).to eq('exprStmt')
147
137
  expr = parent.subnodes[0]
@@ -154,7 +144,7 @@ LOX_END
154
144
  it 'should parse the subtraction of two number literals' do
155
145
  input = '4 - 3;'
156
146
  ptree = subject.parse(input)
157
- parent = ptree.root.subnodes[0]
147
+ parent = ptree.root
158
148
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
159
149
  expect(parent.symbol.name).to eq('exprStmt')
160
150
  expr = parent.subnodes[0]
@@ -167,7 +157,7 @@ LOX_END
167
157
  it 'should parse multiple additive operations' do
168
158
  input = '5 + 2 - 3;'
169
159
  ptree = subject.parse(input)
170
- parent = ptree.root.subnodes[0]
160
+ parent = ptree.root
171
161
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
172
162
  expect(parent.symbol.name).to eq('exprStmt')
173
163
  expr = parent.subnodes[0]
@@ -183,7 +173,7 @@ LOX_END
183
173
  it 'should parse the division of two number literals' do
184
174
  input = '8 / 2;'
185
175
  ptree = subject.parse(input)
186
- parent = ptree.root.subnodes[0]
176
+ parent = ptree.root
187
177
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
188
178
  expect(parent.symbol.name).to eq('exprStmt')
189
179
  expr = parent.subnodes[0]
@@ -196,7 +186,7 @@ LOX_END
196
186
  it 'should parse the product of two number literals' do
197
187
  input = '12.34 * 0.3;'
198
188
  ptree = subject.parse(input)
199
- parent = ptree.root.subnodes[0]
189
+ parent = ptree.root
200
190
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
201
191
  expect(parent.symbol.name).to eq('exprStmt')
202
192
  expr = parent.subnodes[0]
@@ -209,7 +199,7 @@ LOX_END
209
199
  it 'should parse multiple multiplicative operations' do
210
200
  input = '5 * 2 / 3;'
211
201
  ptree = subject.parse(input)
212
- parent = ptree.root.subnodes[0]
202
+ parent = ptree.root
213
203
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
214
204
  expect(parent.symbol.name).to eq('exprStmt')
215
205
  expr = parent.subnodes[0]
@@ -225,7 +215,7 @@ LOX_END
225
215
  it 'should parse combination of terms and factors' do
226
216
  input = '5 + 2 / 3;'
227
217
  ptree = subject.parse(input)
228
- parent = ptree.root.subnodes[0]
218
+ parent = ptree.root
229
219
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
230
220
  expect(parent.symbol.name).to eq('exprStmt')
231
221
  expr = parent.subnodes[0]
@@ -243,7 +233,7 @@ LOX_END
243
233
  it 'should parse the concatenation of two string literals' do
244
234
  input = '"Lo" + "ve";'
245
235
  ptree = subject.parse(input)
246
- parent = ptree.root.subnodes[0]
236
+ parent = ptree.root
247
237
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
248
238
  expect(parent.symbol.name).to eq('exprStmt')
249
239
  expr = parent.subnodes[0]
@@ -259,7 +249,7 @@ LOX_END
259
249
  %w[> >= < <=].each do |predicate|
260
250
  input = "3 #{predicate} 2;"
261
251
  ptree = subject.parse(input)
262
- parent = ptree.root.subnodes[0]
252
+ parent = ptree.root
263
253
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
264
254
  expect(parent.symbol.name).to eq('exprStmt')
265
255
  expr = parent.subnodes[0]
@@ -276,7 +266,7 @@ LOX_END
276
266
  %w[!= ==].each do |predicate|
277
267
  input = "3 #{predicate} 2;"
278
268
  ptree = subject.parse(input)
279
- parent = ptree.root.subnodes[0]
269
+ parent = ptree.root
280
270
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
281
271
  expect(parent.symbol.name).to eq('exprStmt')
282
272
  expr = parent.subnodes[0]
@@ -290,7 +280,7 @@ LOX_END
290
280
  it 'should parse combination of equality expressions' do
291
281
  input = '5 != 2 == false; // A bit contrived example'
292
282
  ptree = subject.parse(input)
293
- parent = ptree.root.subnodes[0]
283
+ parent = ptree.root
294
284
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
295
285
  expect(parent.symbol.name).to eq('exprStmt')
296
286
  expr = parent.subnodes[0]
@@ -309,7 +299,7 @@ LOX_END
309
299
  %w[or and].each do |connector|
310
300
  input = "5 > 2 #{connector} 3 <= 4;"
311
301
  ptree = subject.parse(input)
312
- parent = ptree.root.subnodes[0]
302
+ parent = ptree.root
313
303
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
314
304
  expect(parent.symbol.name).to eq('exprStmt')
315
305
  expr = parent.subnodes[0]
@@ -329,7 +319,7 @@ LOX_END
329
319
  it 'should parse a combinations of logical expressions' do
330
320
  input = '4 > 3 and 1 < 2 or 4 >= 5;'
331
321
  ptree = subject.parse(input)
332
- parent = ptree.root.subnodes[0]
322
+ parent = ptree.root
333
323
  expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
334
324
  expect(parent.symbol.name).to eq('exprStmt')
335
325
  expr = parent.subnodes[0]
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper' # Use the RSpec framework
4
+ require 'stringio'
5
+
6
+ # Load the class under test
7
+ require_relative '../lib/loxxy/interpreter'
8
+
9
+ module Loxxy
10
+ # This spec contains the bare bones test for the Interpreter class.
11
+ # The execution of Lox code is tested elsewhere.
12
+ describe Interpreter do
13
+ let(:sample_cfg) do
14
+ { ostream: StringIO.new }
15
+ end
16
+ subject { Interpreter.new(sample_cfg) }
17
+
18
+ context 'Initialization:' do
19
+ it 'should accept a option Hash at initialization' do
20
+ expect { Interpreter.new(sample_cfg) }.not_to raise_error
21
+ end
22
+
23
+ it 'should know its config options' do
24
+ expect(subject.config).to eq(sample_cfg)
25
+ end
26
+ end # context
27
+
28
+ context 'Evaluating Lox code:' do
29
+ let(:hello_world) { 'print "Hello, world!";' }
30
+
31
+ it 'should print the hello world message' do
32
+ expect { subject.evaluate(hello_world) }.not_to raise_error
33
+ expect(sample_cfg[:ostream].string).to eq('Hello, world!')
34
+ end
35
+ end # context
36
+ end # describe
37
+ end # module
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loxxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-08 00:00:00.000000000 Z
11
+ date: 2021-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -84,12 +84,16 @@ files:
84
84
  - README.md
85
85
  - Rakefile
86
86
  - lib/loxxy.rb
87
+ - lib/loxxy/ast/all_lox_nodes.rb
87
88
  - lib/loxxy/ast/ast_builder.rb
88
89
  - lib/loxxy/ast/ast_visitor.rb
89
90
  - lib/loxxy/ast/lox_binary_expr.rb
90
91
  - lib/loxxy/ast/lox_compound_expr.rb
91
92
  - lib/loxxy/ast/lox_literal_expr.rb
92
93
  - lib/loxxy/ast/lox_node.rb
94
+ - lib/loxxy/ast/lox_noop_expr.rb
95
+ - lib/loxxy/ast/lox_print_stmt.rb
96
+ - lib/loxxy/back_end/engine.rb
93
97
  - lib/loxxy/datatype/all_datatypes.rb
94
98
  - lib/loxxy/datatype/boolean.rb
95
99
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -103,12 +107,18 @@ files:
103
107
  - lib/loxxy/front_end/parser.rb
104
108
  - lib/loxxy/front_end/raw_parser.rb
105
109
  - lib/loxxy/front_end/scanner.rb
110
+ - lib/loxxy/interpreter.rb
106
111
  - lib/loxxy/version.rb
107
112
  - loxxy.gemspec
113
+ - spec/back_end/engine_spec.rb
114
+ - spec/datatype/boolean_spec.rb
108
115
  - spec/datatype/lx_string_spec.rb
116
+ - spec/datatype/nil_spec.rb
117
+ - spec/datatype/number_spec.rb
109
118
  - spec/front_end/parser_spec.rb
110
119
  - spec/front_end/raw_parser_spec.rb
111
120
  - spec/front_end/scanner_spec.rb
121
+ - spec/interpreter_spec.rb
112
122
  - spec/loxxy_spec.rb
113
123
  - spec/spec_helper.rb
114
124
  homepage: https://github.com/famished-tiger/loxxy
@@ -136,8 +146,13 @@ signing_key:
136
146
  specification_version: 4
137
147
  summary: An implementation of the Lox programming language. WIP
138
148
  test_files:
149
+ - spec/back_end/engine_spec.rb
150
+ - spec/datatype/boolean_spec.rb
139
151
  - spec/datatype/lx_string_spec.rb
152
+ - spec/datatype/nil_spec.rb
153
+ - spec/datatype/number_spec.rb
140
154
  - spec/front_end/parser_spec.rb
141
155
  - spec/front_end/raw_parser_spec.rb
142
156
  - spec/front_end/scanner_spec.rb
157
+ - spec/interpreter_spec.rb
143
158
  - spec/loxxy_spec.rb