loxxy 0.0.8 → 0.0.13

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.
@@ -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,49 @@
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
+ # @return [Loxxy::Datatype::BuiltinDatatype]
29
+ def execute(aVisitor)
30
+ aVisitor.subscribe(self)
31
+ aVisitor.start
32
+ aVisitor.unsubscribe(self)
33
+ stack.empty? ? Datatype::Nil.instance : stack.pop
34
+ end
35
+
36
+ # Visit event handling
37
+
38
+ def after_print_stmt(_printStmt)
39
+ tos = stack.pop
40
+ @ostream.print tos.to_str
41
+ end
42
+
43
+ # @param literalExpr [Ast::LoxLiteralExpr]
44
+ def before_literal_expr(literalExpr)
45
+ stack.push(literalExpr.literal)
46
+ end
47
+ end # class
48
+ end # module
49
+ 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')
@@ -58,7 +58,7 @@ module Loxxy
58
58
  rule('statement' => 'whileStmt')
59
59
  rule('statement' => 'block')
60
60
 
61
- rule('exprStmt' => 'expression SEMICOLON')
61
+ rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
62
62
 
63
63
  rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
64
64
  rule('forControl' => 'forInitialization forTest forUpdate')
@@ -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')
@@ -88,23 +88,23 @@ module Loxxy
88
88
  rule('owner_opt' => 'call DOT')
89
89
  rule('owner_opt' => [])
90
90
  rule('logic_or' => 'logic_and')
91
- rule('logic_or' => 'logic_and disjunct_plus')
92
- rule('disjunct_plus' => 'disjunct_plus OR logic_and')
93
- rule('disjunct_plus' => 'OR logic_and')
91
+ rule('logic_or' => 'logic_and disjunct_plus').as 'logic_or_plus'
92
+ rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'logic_or_plus_more'
93
+ rule('disjunct_plus' => 'OR logic_and').as 'logic_or_plus_end'
94
94
  rule('logic_and' => 'equality')
95
- rule('logic_and' => 'equality conjunct_plus')
96
- rule('conjunct_plus' => 'conjunct_plus AND equality')
97
- rule('conjunct_' => 'AND equality')
95
+ rule('logic_and' => 'equality conjunct_plus').as 'logic_and_plus'
96
+ rule('conjunct_plus' => 'conjunct_plus AND equality').as 'logic_and_plus_more'
97
+ rule('conjunct_plus' => 'AND equality').as 'logic_and_plus_end'
98
98
  rule('equality' => 'comparison')
99
- rule('equality' => 'comparison equalityTest_plus')
100
- rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
101
- rule('equalityTest_star' => 'equalityTest comparison')
99
+ rule('equality' => 'comparison equalityTest_plus').as 'equality_plus'
100
+ rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'equality_t_plus_more'
101
+ rule('equalityTest_plus' => 'equalityTest comparison').as 'equality_t_plus_end'
102
102
  rule('equalityTest' => 'BANG_EQUAL')
103
103
  rule('equalityTest' => 'EQUAL_EQUAL')
104
104
  rule('comparison' => 'term')
105
- rule('comparison' => 'term comparisonTest_plus')
106
- rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term')
107
- rule('comparisonTest_plus' => 'comparisonTest term')
105
+ rule('comparison' => 'term comparisonTest_plus').as 'comparison_plus'
106
+ rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
107
+ rule('comparisonTest_plus' => 'comparisonTest term').as 'comparison_t_plus_end'
108
108
  rule('comparisonTest' => 'GREATER')
109
109
  rule('comparisonTest' => 'GREATER_EQUAL')
110
110
  rule('comparisonTest' => 'LESS')
@@ -0,0 +1,40 @@
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
+ # Return the result of the last executed expression (if any)
24
+ # @param lox_input [String] Lox program to evaluate
25
+ # @return [Loxxy::Datatype::BuiltinDatatype]
26
+ def evaluate(lox_input)
27
+ # Front-end scans, parses the input and blurps an AST...
28
+ parser = FrontEnd::Parser.new
29
+
30
+ # The AST is the data object passed to the back-end
31
+ ast_tree = parser.parse(lox_input)
32
+ visitor = Ast::ASTVisitor.new(ast_tree)
33
+
34
+ # Back-end launches the tree walking & reponds to visit events
35
+ # by executing the code determined by the visited AST node.
36
+ engine = BackEnd::Engine.new(config)
37
+ engine.execute(visitor)
38
+ end
39
+ end # class
40
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.8'
4
+ VERSION = '0.0.13'
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