rley 0.7.03 → 0.7.08
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/.rubocop.yml +362 -62
- data/.travis.yml +6 -7
- data/CHANGELOG.md +20 -1
- data/LICENSE.txt +1 -1
- data/README.md +6 -7
- data/Rakefile +2 -0
- data/appveyor.yml +2 -4
- data/examples/NLP/benchmark_pico_en.rb +2 -0
- data/examples/NLP/engtagger.rb +193 -188
- data/examples/NLP/nano_eng/nano_en_demo.rb +2 -0
- data/examples/NLP/nano_eng/nano_grammar.rb +7 -5
- data/examples/NLP/pico_en_demo.rb +2 -0
- data/examples/data_formats/JSON/cli_options.rb +3 -1
- data/examples/data_formats/JSON/json_ast_builder.rb +14 -9
- data/examples/data_formats/JSON/json_ast_nodes.rb +14 -21
- data/examples/data_formats/JSON/json_demo.rb +2 -0
- data/examples/data_formats/JSON/json_grammar.rb +4 -2
- data/examples/data_formats/JSON/json_lexer.rb +10 -8
- data/examples/data_formats/JSON/json_minifier.rb +3 -1
- data/examples/general/calc_iter1/calc_ast_builder.rb +15 -10
- data/examples/general/calc_iter1/calc_ast_nodes.rb +25 -37
- data/examples/general/calc_iter1/calc_demo.rb +2 -0
- data/examples/general/calc_iter1/calc_grammar.rb +4 -2
- data/examples/general/calc_iter1/calc_lexer.rb +8 -4
- data/examples/general/calc_iter1/spec/calculator_spec.rb +7 -5
- data/examples/general/calc_iter2/calc_ast_builder.rb +7 -3
- data/examples/general/calc_iter2/calc_ast_nodes.rb +29 -43
- data/examples/general/calc_iter2/calc_demo.rb +2 -0
- data/examples/general/calc_iter2/calc_grammar.rb +5 -3
- data/examples/general/calc_iter2/calc_lexer.rb +13 -10
- data/examples/general/calc_iter2/spec/calculator_spec.rb +28 -26
- data/examples/general/left.rb +4 -2
- data/examples/general/right.rb +4 -2
- data/lib/rley.rb +2 -0
- data/lib/rley/base/base_parser.rb +2 -0
- data/lib/rley/base/dotted_item.rb +38 -41
- data/lib/rley/base/grm_items_builder.rb +2 -0
- data/lib/rley/constants.rb +5 -3
- data/lib/rley/engine.rb +22 -24
- data/lib/rley/formatter/asciitree.rb +6 -4
- data/lib/rley/formatter/base_formatter.rb +2 -0
- data/lib/rley/formatter/bracket_notation.rb +3 -8
- data/lib/rley/formatter/debug.rb +8 -6
- data/lib/rley/formatter/json.rb +4 -2
- data/lib/rley/gfg/call_edge.rb +3 -1
- data/lib/rley/gfg/edge.rb +7 -5
- data/lib/rley/gfg/end_vertex.rb +4 -6
- data/lib/rley/gfg/epsilon_edge.rb +3 -5
- data/lib/rley/gfg/grm_flow_graph.rb +31 -25
- data/lib/rley/gfg/item_vertex.rb +12 -22
- data/lib/rley/gfg/non_terminal_vertex.rb +6 -4
- data/lib/rley/gfg/return_edge.rb +2 -0
- data/lib/rley/gfg/scan_edge.rb +3 -1
- data/lib/rley/gfg/shortcut_edge.rb +4 -2
- data/lib/rley/gfg/start_vertex.rb +6 -8
- data/lib/rley/gfg/vertex.rb +47 -41
- data/lib/rley/lexical/token.rb +3 -1
- data/lib/rley/lexical/token_range.rb +8 -6
- data/lib/rley/parse_forest_visitor.rb +7 -5
- data/lib/rley/parse_rep/ast_base_builder.rb +11 -11
- data/lib/rley/parse_rep/cst_builder.rb +7 -4
- data/lib/rley/parse_rep/parse_forest_builder.rb +36 -25
- data/lib/rley/parse_rep/parse_forest_factory.rb +5 -3
- data/lib/rley/parse_rep/parse_rep_creator.rb +18 -13
- data/lib/rley/parse_rep/parse_tree_builder.rb +15 -15
- data/lib/rley/parse_rep/parse_tree_factory.rb +27 -25
- data/lib/rley/parse_tree_visitor.rb +3 -1
- data/lib/rley/parser/error_reason.rb +9 -8
- data/lib/rley/parser/gfg_chart.rb +54 -22
- data/lib/rley/parser/gfg_earley_parser.rb +3 -1
- data/lib/rley/parser/gfg_parsing.rb +51 -31
- data/lib/rley/parser/parse_entry.rb +29 -33
- data/lib/rley/parser/parse_entry_set.rb +32 -27
- data/lib/rley/parser/parse_entry_tracker.rb +6 -4
- data/lib/rley/parser/parse_state.rb +18 -21
- data/lib/rley/parser/parse_state_tracker.rb +6 -4
- data/lib/rley/parser/parse_tracer.rb +15 -13
- data/lib/rley/parser/parse_walker_factory.rb +28 -29
- data/lib/rley/parser/state_set.rb +11 -10
- data/lib/rley/ptree/non_terminal_node.rb +10 -6
- data/lib/rley/ptree/parse_tree.rb +6 -4
- data/lib/rley/ptree/parse_tree_node.rb +7 -5
- data/lib/rley/ptree/terminal_node.rb +9 -7
- data/lib/rley/rley_error.rb +12 -10
- data/lib/rley/sppf/alternative_node.rb +8 -6
- data/lib/rley/sppf/composite_node.rb +9 -7
- data/lib/rley/sppf/epsilon_node.rb +5 -3
- data/lib/rley/sppf/leaf_node.rb +5 -3
- data/lib/rley/sppf/non_terminal_node.rb +2 -0
- data/lib/rley/sppf/parse_forest.rb +19 -17
- data/lib/rley/sppf/sppf_node.rb +9 -8
- data/lib/rley/sppf/token_node.rb +5 -3
- data/lib/rley/syntax/grammar.rb +7 -5
- data/lib/rley/syntax/grammar_builder.rb +11 -9
- data/lib/rley/syntax/grm_symbol.rb +8 -6
- data/lib/rley/syntax/literal.rb +2 -0
- data/lib/rley/syntax/non_terminal.rb +11 -15
- data/lib/rley/syntax/production.rb +13 -11
- data/lib/rley/syntax/symbol_seq.rb +10 -10
- data/lib/rley/syntax/terminal.rb +6 -5
- data/lib/rley/syntax/verbatim_symbol.rb +5 -3
- data/lib/support/base_tokenizer.rb +23 -20
- data/spec/rley/base/dotted_item_spec.rb +4 -2
- data/spec/rley/base/grm_items_builder_spec.rb +2 -0
- data/spec/rley/engine_spec.rb +47 -9
- data/spec/rley/formatter/asciitree_spec.rb +11 -9
- data/spec/rley/formatter/bracket_notation_spec.rb +16 -14
- data/spec/rley/formatter/debug_spec.rb +4 -2
- data/spec/rley/formatter/json_spec.rb +5 -3
- data/spec/rley/gfg/call_edge_spec.rb +2 -0
- data/spec/rley/gfg/edge_spec.rb +2 -0
- data/spec/rley/gfg/end_vertex_spec.rb +7 -5
- data/spec/rley/gfg/epsilon_edge_spec.rb +2 -0
- data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -0
- data/spec/rley/gfg/item_vertex_spec.rb +12 -10
- data/spec/rley/gfg/non_terminal_vertex_spec.rb +5 -3
- data/spec/rley/gfg/return_edge_spec.rb +2 -0
- data/spec/rley/gfg/scan_edge_spec.rb +2 -0
- data/spec/rley/gfg/shortcut_edge_spec.rb +3 -1
- data/spec/rley/gfg/start_vertex_spec.rb +7 -5
- data/spec/rley/gfg/vertex_spec.rb +5 -3
- data/spec/rley/lexical/token_range_spec.rb +18 -16
- data/spec/rley/lexical/token_spec.rb +4 -2
- data/spec/rley/parse_forest_visitor_spec.rb +167 -163
- data/spec/rley/parse_rep/ambiguous_parse_spec.rb +46 -44
- data/spec/rley/parse_rep/ast_builder_spec.rb +8 -6
- data/spec/rley/parse_rep/cst_builder_spec.rb +7 -5
- data/spec/rley/parse_rep/groucho_spec.rb +25 -25
- data/spec/rley/parse_rep/parse_forest_builder_spec.rb +28 -26
- data/spec/rley/parse_rep/parse_forest_factory_spec.rb +8 -6
- data/spec/rley/parse_rep/parse_tree_factory_spec.rb +4 -2
- data/spec/rley/parse_tree_visitor_spec.rb +12 -8
- data/spec/rley/parser/error_reason_spec.rb +8 -6
- data/spec/rley/parser/gfg_chart_spec.rb +17 -4
- data/spec/rley/parser/gfg_earley_parser_spec.rb +16 -11
- data/spec/rley/parser/gfg_parsing_spec.rb +41 -252
- data/spec/rley/parser/parse_entry_set_spec.rb +2 -0
- data/spec/rley/parser/parse_entry_spec.rb +21 -19
- data/spec/rley/parser/parse_state_spec.rb +7 -5
- data/spec/rley/parser/parse_tracer_spec.rb +16 -14
- data/spec/rley/parser/parse_walker_factory_spec.rb +10 -8
- data/spec/rley/parser/state_set_spec.rb +24 -22
- data/spec/rley/ptree/non_terminal_node_spec.rb +7 -3
- data/spec/rley/ptree/parse_tree_node_spec.rb +6 -4
- data/spec/rley/ptree/parse_tree_spec.rb +2 -0
- data/spec/rley/ptree/terminal_node_spec.rb +8 -6
- data/spec/rley/sppf/alternative_node_spec.rb +8 -6
- data/spec/rley/sppf/non_terminal_node_spec.rb +5 -3
- data/spec/rley/sppf/token_node_spec.rb +6 -4
- data/spec/rley/support/ambiguous_grammar_helper.rb +5 -4
- data/spec/rley/support/expectation_helper.rb +2 -0
- data/spec/rley/support/grammar_abc_helper.rb +4 -4
- data/spec/rley/support/grammar_ambig01_helper.rb +6 -5
- data/spec/rley/support/grammar_arr_int_helper.rb +6 -5
- data/spec/rley/support/grammar_b_expr_helper.rb +6 -5
- data/spec/rley/support/grammar_helper.rb +2 -0
- data/spec/rley/support/grammar_l0_helper.rb +15 -16
- data/spec/rley/support/grammar_pb_helper.rb +8 -5
- data/spec/rley/support/grammar_sppf_helper.rb +3 -1
- data/spec/rley/syntax/grammar_builder_spec.rb +7 -5
- data/spec/rley/syntax/grammar_spec.rb +8 -6
- data/spec/rley/syntax/grm_symbol_spec.rb +3 -1
- data/spec/rley/syntax/literal_spec.rb +2 -0
- data/spec/rley/syntax/non_terminal_spec.rb +10 -8
- data/spec/rley/syntax/production_spec.rb +15 -13
- data/spec/rley/syntax/symbol_seq_spec.rb +4 -2
- data/spec/rley/syntax/terminal_spec.rb +7 -5
- data/spec/rley/syntax/verbatim_symbol_spec.rb +3 -1
- data/spec/spec_helper.rb +2 -12
- data/spec/support/base_tokenizer_spec.rb +9 -2
- metadata +21 -63
- data/.simplecov +0 -7
- data/Gemfile +0 -8
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Grammar for simple arithmetical expressions
|
2
4
|
require 'rley' # Load the gem
|
3
5
|
|
@@ -16,9 +18,9 @@ builder = Rley::Syntax::GrammarBuilder.new do
|
|
16
18
|
rule 'factor' => 'NUMBER'
|
17
19
|
rule 'factor' => %w[LPAREN expression RPAREN]
|
18
20
|
rule 'add_operator' => 'PLUS'
|
19
|
-
rule 'add_operator' => 'MINUS'
|
21
|
+
rule 'add_operator' => 'MINUS'
|
20
22
|
rule 'mul_operator' => 'STAR'
|
21
|
-
rule 'mul_operator' => 'DIVIDE'
|
23
|
+
rule 'mul_operator' => 'DIVIDE'
|
22
24
|
end
|
23
25
|
|
24
26
|
# And now build the grammar...
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# File: calc_lexer.rb
|
2
4
|
# Lexer for a basic arithmetical expression parser
|
3
5
|
require 'strscan'
|
@@ -26,7 +28,7 @@ class CalcLexer
|
|
26
28
|
@lineno = 1
|
27
29
|
end
|
28
30
|
|
29
|
-
def tokens
|
31
|
+
def tokens
|
30
32
|
tok_sequence = []
|
31
33
|
until @scanner.eos?
|
32
34
|
token = _next_token
|
@@ -38,7 +40,8 @@ class CalcLexer
|
|
38
40
|
|
39
41
|
private
|
40
42
|
|
41
|
-
|
43
|
+
# rubocop: disable Lint/DuplicateBranch
|
44
|
+
def _next_token
|
42
45
|
skip_whitespaces
|
43
46
|
curr_ch = scanner.peek(1)
|
44
47
|
return nil if curr_ch.nil?
|
@@ -61,18 +64,19 @@ class CalcLexer
|
|
61
64
|
erroneous = curr_ch.nil? ? '' : curr_ch
|
62
65
|
sequel = scanner.scan(/.{1,20}/)
|
63
66
|
erroneous += sequel unless sequel.nil?
|
64
|
-
raise ScanError
|
67
|
+
raise ScanError, "Unknown token #{erroneous}"
|
65
68
|
end
|
66
69
|
|
67
70
|
return token
|
68
71
|
end
|
72
|
+
# rubocop: enable Lint/DuplicateBranch
|
69
73
|
|
70
74
|
def build_token(aSymbolName, aLexeme)
|
71
75
|
pos = Rley::Lexical::Position.new(1, scanner.pos)
|
72
76
|
return Rley::Lexical::Token.new(aLexeme, aSymbolName, pos)
|
73
77
|
end
|
74
78
|
|
75
|
-
def skip_whitespaces
|
79
|
+
def skip_whitespaces
|
76
80
|
scanner.scan(/[ \t\f\n\r]+/)
|
77
81
|
end
|
78
82
|
end # class
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec' # Use the RSpec framework
|
2
4
|
require_relative '../calc_lexer'
|
3
5
|
require_relative '../calc_grammar'
|
@@ -10,13 +12,13 @@ describe 'Calculator' do
|
|
10
12
|
engine = Rley::Engine.new do |cfg|
|
11
13
|
cfg.repr_builder = CalcASTBuilder
|
12
14
|
end
|
13
|
-
|
15
|
+
|
14
16
|
engine.use_grammar(CalcGrammar)
|
15
17
|
raw_result = parse_expression(engine, anExpression)
|
16
18
|
ast = engine.to_ptree(raw_result)
|
17
19
|
return expect(ast.root.interpret)
|
18
20
|
end
|
19
|
-
|
21
|
+
|
20
22
|
def parse_expression(anEngine, anExpression)
|
21
23
|
lexer = CalcLexer.new(anExpression)
|
22
24
|
result = anEngine.parse(lexer.tokens)
|
@@ -29,7 +31,7 @@ describe 'Calculator' do
|
|
29
31
|
end
|
30
32
|
|
31
33
|
return result
|
32
|
-
end
|
34
|
+
end
|
33
35
|
|
34
36
|
it 'should evaluate simple number literals' do
|
35
37
|
expect_expr('2').to eq(2)
|
@@ -54,7 +56,7 @@ describe 'Calculator' do
|
|
54
56
|
it 'should evaluate parentheses' do
|
55
57
|
expect_expr('2 * (2.1 + 1)').to eq(6.2)
|
56
58
|
end
|
57
|
-
|
59
|
+
|
58
60
|
it 'should evaluate regardless of whitespace' do
|
59
61
|
expect_expr("2*(1+\t1)").to eq(4)
|
60
62
|
end
|
@@ -65,6 +67,6 @@ describe 'Calculator' do
|
|
65
67
|
|
66
68
|
it 'should evaluate multiple levels of parentheses' do
|
67
69
|
expect_expr('2*(1/(1+3))').to eq(0.5)
|
68
|
-
end
|
70
|
+
end
|
69
71
|
end # describe
|
70
72
|
# End of file
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'calc_ast_nodes'
|
2
4
|
|
3
5
|
# The purpose of a CalcASTBuilder is to build piece by piece an AST
|
@@ -13,8 +15,7 @@ class CalcASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
|
13
15
|
# The unary negation operator on one hand, the binary substraction operator
|
14
16
|
'MINUS' => { 'add_operator_1' => Rley::PTree::TerminalNode,
|
15
17
|
'simple_factor_2' => CalcNegateNode,
|
16
|
-
'sign_1' => CalcNegateNode
|
17
|
-
},
|
18
|
+
'sign_1' => CalcNegateNode },
|
18
19
|
'NUMBER' => CalcNumberNode,
|
19
20
|
'PI' => CalcConstantNode,
|
20
21
|
'E' => CalcConstantNode,
|
@@ -23,7 +24,7 @@ class CalcASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
|
23
24
|
|
24
25
|
protected
|
25
26
|
|
26
|
-
def terminal2node
|
27
|
+
def terminal2node
|
27
28
|
Terminal2NodeClass
|
28
29
|
end
|
29
30
|
|
@@ -34,6 +35,8 @@ class CalcASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
|
34
35
|
return operator_node
|
35
36
|
end
|
36
37
|
|
38
|
+
# rubocop: disable Naming/VariableNumber
|
39
|
+
|
37
40
|
# rule 'simple_expression' => %w[simple_expression add_operator term]
|
38
41
|
def reduce_simple_expression_1(_production, _range, _tokens, theChildren)
|
39
42
|
reduce_binary_operator(theChildren)
|
@@ -104,5 +107,6 @@ class CalcASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
|
104
107
|
def reduce_mul_operator_1(_production, aRange, _tokens, theChildren)
|
105
108
|
return CalcDivideNode.new(theChildren[0].symbol, aRange)
|
106
109
|
end
|
110
|
+
# rubocop: enable Naming/VariableNumber
|
107
111
|
end # class
|
108
112
|
# End of file
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Classes that implement nodes of Abstract Syntax Trees (AST) representing
|
2
4
|
# calculator parse results.
|
3
5
|
|
4
|
-
|
5
6
|
CalcTerminalNode = Struct.new(:token, :value, :position) do
|
6
7
|
def initialize(aToken, aPosition)
|
7
8
|
self.token = aToken
|
@@ -14,15 +15,15 @@ CalcTerminalNode = Struct.new(:token, :value, :position) do
|
|
14
15
|
self.value = aLiteral.dup
|
15
16
|
end
|
16
17
|
|
17
|
-
def symbol
|
18
|
+
def symbol
|
18
19
|
token.terminal
|
19
20
|
end
|
20
21
|
|
21
|
-
def interpret
|
22
|
-
|
22
|
+
def interpret
|
23
|
+
value
|
23
24
|
end
|
24
|
-
|
25
|
-
def done!
|
25
|
+
|
26
|
+
def done!
|
26
27
|
# Do nothing
|
27
28
|
end
|
28
29
|
|
@@ -43,9 +44,9 @@ class CalcNumberNode < CalcTerminalNode
|
|
43
44
|
self.value = aLiteral.to_f
|
44
45
|
end
|
45
46
|
end
|
46
|
-
|
47
|
+
|
47
48
|
# Overriding the unary minus operator
|
48
|
-
def -@
|
49
|
+
def -@
|
49
50
|
self.value = - value
|
50
51
|
return self
|
51
52
|
end
|
@@ -81,8 +82,8 @@ class CalcCompositeNode
|
|
81
82
|
def accept(aVisitor)
|
82
83
|
aVisitor.visit_nonterminal(self)
|
83
84
|
end
|
84
|
-
|
85
|
-
def done!
|
85
|
+
|
86
|
+
def done!
|
86
87
|
# Do nothing
|
87
88
|
end
|
88
89
|
|
@@ -90,44 +91,36 @@ class CalcCompositeNode
|
|
90
91
|
end # class
|
91
92
|
|
92
93
|
class CalcUnaryOpNode < CalcCompositeNode
|
93
|
-
def initialize(aSymbol, aPosition)
|
94
|
-
super(aSymbol, aPosition)
|
95
|
-
end
|
96
|
-
|
97
94
|
alias members children
|
98
95
|
end # class
|
99
96
|
|
100
|
-
class CalcNegateNode < CalcUnaryOpNode
|
101
|
-
def interpret
|
97
|
+
class CalcNegateNode < CalcUnaryOpNode
|
98
|
+
def interpret
|
102
99
|
return -children[0].interpret
|
103
100
|
end
|
104
101
|
end # class
|
105
102
|
|
106
103
|
class CalcUnaryFunction < CalcCompositeNode
|
107
|
-
@@name_mapping = begin
|
104
|
+
@@name_mapping = begin
|
108
105
|
map = Hash.new { |me, key| me[key] = key }
|
109
106
|
map['ln'] = 'log'
|
110
107
|
map['log'] = 'log10'
|
111
108
|
map
|
112
109
|
end
|
113
110
|
attr_accessor(:func_name)
|
114
|
-
|
115
|
-
|
116
|
-
def interpret
|
111
|
+
|
112
|
+
|
113
|
+
def interpret
|
117
114
|
argument = children[0].interpret
|
118
115
|
internal_name = @@name_mapping[@func_name]
|
119
116
|
return Math.send(internal_name.to_sym, argument)
|
120
|
-
end
|
117
|
+
end
|
121
118
|
end
|
122
119
|
|
123
120
|
class CalcBinaryOpNode < CalcCompositeNode
|
124
|
-
def initialize(aSymbol, aRange)
|
125
|
-
super(aSymbol, aRange)
|
126
|
-
end
|
127
|
-
|
128
121
|
protected
|
129
122
|
|
130
|
-
def retrieve_operands
|
123
|
+
def retrieve_operands
|
131
124
|
operands = []
|
132
125
|
children.each do |child|
|
133
126
|
oper = child.respond_to?(:interpret) ? child.interpret : child
|
@@ -140,52 +133,45 @@ end # class
|
|
140
133
|
|
141
134
|
class CalcAddNode < CalcBinaryOpNode
|
142
135
|
# TODO
|
143
|
-
def interpret
|
136
|
+
def interpret
|
144
137
|
operands = retrieve_operands
|
145
138
|
|
146
|
-
|
147
|
-
return sum
|
139
|
+
operands[0] + operands[1]
|
148
140
|
end
|
149
141
|
end # class
|
150
142
|
|
151
|
-
|
152
143
|
class CalcSubtractNode < CalcBinaryOpNode
|
153
144
|
# TODO
|
154
|
-
def interpret
|
145
|
+
def interpret
|
155
146
|
operands = retrieve_operands
|
156
147
|
|
157
|
-
|
158
|
-
return substraction
|
148
|
+
operands[0] - operands[1]
|
159
149
|
end
|
160
150
|
end # class
|
161
151
|
|
162
152
|
class CalcMultiplyNode < CalcBinaryOpNode
|
163
153
|
# TODO
|
164
|
-
def interpret
|
154
|
+
def interpret
|
165
155
|
operands = retrieve_operands
|
166
|
-
|
167
|
-
return multiplication
|
156
|
+
operands[0] * operands[1]
|
168
157
|
end
|
169
158
|
end # class
|
170
159
|
|
171
160
|
class CalcDivideNode < CalcBinaryOpNode
|
172
161
|
# TODO
|
173
|
-
def interpret
|
162
|
+
def interpret
|
174
163
|
operands = retrieve_operands
|
175
164
|
numerator = operands[0].to_f
|
176
165
|
denominator = operands[1]
|
177
|
-
|
178
|
-
return division
|
166
|
+
numerator / denominator
|
179
167
|
end
|
180
168
|
end # class
|
181
169
|
|
182
|
-
|
183
170
|
class PowerNode < CalcBinaryOpNode
|
184
171
|
# TODO
|
185
|
-
def interpret
|
172
|
+
def interpret
|
186
173
|
operands = retrieve_operands
|
187
|
-
|
188
|
-
return exponentiation
|
174
|
+
operands[0]**operands[1]
|
189
175
|
end
|
190
176
|
end # class
|
191
177
|
# End of file
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Grammar for simple arithmetical expressions
|
2
4
|
require 'rley' # Load the gem
|
3
5
|
|
@@ -7,7 +9,7 @@ builder = Rley::Syntax::GrammarBuilder.new do
|
|
7
9
|
add_terminals('NUMBER')
|
8
10
|
add_terminals('PLUS', 'MINUS') # For '+', '-' operators or sign
|
9
11
|
add_terminals('STAR', 'DIVIDE', 'POWER') # For '*', '/', '**' operators
|
10
|
-
add_terminals('LPAREN', 'RPAREN') # For '(', ')' delimiters
|
12
|
+
add_terminals('LPAREN', 'RPAREN') # For '(', ')' delimiters
|
11
13
|
add_terminals('PI', 'E') # For numeric constants
|
12
14
|
add_terminals('RESERVED') # Reserved identifiers
|
13
15
|
|
@@ -21,13 +23,13 @@ builder = Rley::Syntax::GrammarBuilder.new do
|
|
21
23
|
rule 'simple_factor' => %w[sign scalar]
|
22
24
|
rule 'simple_factor' => %w[unary_function in_parenthesis]
|
23
25
|
rule 'simple_factor' => %w[MINUS in_parenthesis]
|
24
|
-
rule 'simple_factor' => 'in_parenthesis'
|
26
|
+
rule 'simple_factor' => 'in_parenthesis'
|
25
27
|
rule 'sign' => 'PLUS'
|
26
28
|
rule 'sign' => 'MINUS'
|
27
29
|
rule 'sign' => []
|
28
30
|
rule 'scalar' => 'NUMBER'
|
29
31
|
rule 'scalar' => 'PI'
|
30
|
-
rule 'scalar' => 'E'
|
32
|
+
rule 'scalar' => 'E'
|
31
33
|
rule 'unary_function' => 'RESERVED'
|
32
34
|
rule 'in_parenthesis' => %w[LPAREN expression RPAREN]
|
33
35
|
rule 'add_operator' => 'PLUS'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# File: calc_lexer.rb
|
2
4
|
# Lexer for a basic arithmetical expression parser
|
3
5
|
require 'strscan'
|
@@ -8,7 +10,6 @@ class CalcLexer
|
|
8
10
|
attr_reader(:lineno)
|
9
11
|
attr_reader(:line_start)
|
10
12
|
|
11
|
-
|
12
13
|
@@lexeme2name = {
|
13
14
|
'(' => 'LPAREN',
|
14
15
|
')' => 'RPAREN',
|
@@ -20,7 +21,7 @@ class CalcLexer
|
|
20
21
|
'PI' => 'PI',
|
21
22
|
'E' => 'E'
|
22
23
|
}.freeze
|
23
|
-
|
24
|
+
|
24
25
|
@@unary_functions = %w[
|
25
26
|
sin cos tan asin acos atan
|
26
27
|
sqrt cbrt exp ln log
|
@@ -33,7 +34,7 @@ class CalcLexer
|
|
33
34
|
@lineno = 1
|
34
35
|
end
|
35
36
|
|
36
|
-
def tokens
|
37
|
+
def tokens
|
37
38
|
@unary_f_pattern = Regexp.new(@@unary_functions.join('|'))
|
38
39
|
tok_sequence = []
|
39
40
|
until @scanner.eos?
|
@@ -46,11 +47,12 @@ class CalcLexer
|
|
46
47
|
|
47
48
|
private
|
48
49
|
|
49
|
-
|
50
|
+
# rubocop: disable Lint/DuplicateBranch
|
51
|
+
def _next_token
|
50
52
|
skip_whitespaces
|
51
53
|
curr_ch = scanner.peek(1)
|
52
54
|
return nil if curr_ch.nil?
|
53
|
-
|
55
|
+
|
54
56
|
token = nil
|
55
57
|
|
56
58
|
if '()+-/'.include? curr_ch
|
@@ -61,25 +63,26 @@ class CalcLexer
|
|
61
63
|
elsif (lexeme = scanner.scan(/\*|PI|E/))
|
62
64
|
token = build_token(@@lexeme2name[lexeme], lexeme)
|
63
65
|
elsif (lexeme = scanner.scan(@unary_f_pattern))
|
64
|
-
token = build_token('RESERVED', lexeme)
|
66
|
+
token = build_token('RESERVED', lexeme)
|
65
67
|
elsif (lexeme = scanner.scan(/[0-9]+(\.[0-9]+)?([eE][-+]?[0-9])?/))
|
66
68
|
token = build_token('NUMBER', lexeme)
|
67
69
|
else # Unknown token
|
68
70
|
erroneous = curr_ch.nil? ? '' : curr_ch
|
69
71
|
sequel = scanner.scan(/.{1,20}/)
|
70
72
|
erroneous += sequel unless sequel.nil?
|
71
|
-
raise ScanError
|
73
|
+
raise ScanError, "Unknown token #{erroneous}"
|
72
74
|
end
|
73
75
|
|
74
76
|
return token
|
75
77
|
end
|
76
|
-
|
78
|
+
# rubocop: enable Lint/DuplicateBranch
|
79
|
+
|
77
80
|
def build_token(aSymbolName, aLexeme)
|
78
81
|
pos = Rley::Lexical::Position.new(1, scanner.pos)
|
79
|
-
return Rley::Lexical::Token.new(aLexeme, aSymbolName, pos)
|
82
|
+
return Rley::Lexical::Token.new(aLexeme, aSymbolName, pos)
|
80
83
|
end
|
81
84
|
|
82
|
-
def skip_whitespaces
|
85
|
+
def skip_whitespaces
|
83
86
|
scanner.scan(/[ \t\f\n\r]+/)
|
84
87
|
end
|
85
88
|
end # class
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec' # Use the RSpec framework
|
2
4
|
require_relative '../calc_lexer'
|
3
5
|
require_relative '../calc_grammar'
|
@@ -15,13 +17,13 @@ describe 'Calculator' do
|
|
15
17
|
engine = Rley::Engine.new do |cfg|
|
16
18
|
cfg.repr_builder = CalcASTBuilder
|
17
19
|
end
|
18
|
-
|
20
|
+
|
19
21
|
engine.use_grammar(CalcGrammar)
|
20
22
|
raw_result = parse_expression(engine, anExpression)
|
21
23
|
ast = engine.to_ptree(raw_result)
|
22
24
|
return expect(ast.root.interpret)
|
23
25
|
end
|
24
|
-
|
26
|
+
|
25
27
|
def parse_expression(anEngine, anExpression)
|
26
28
|
lexer = CalcLexer.new(anExpression)
|
27
29
|
result = anEngine.parse(lexer.tokens)
|
@@ -38,7 +40,7 @@ describe 'Calculator' do
|
|
38
40
|
|
39
41
|
context 'Parsing valid expressions' do
|
40
42
|
let(:epsilon) { 0.0000000001 }
|
41
|
-
|
43
|
+
|
42
44
|
it 'should evaluate simple integer literals' do
|
43
45
|
expect_expr('2').to eq(2)
|
44
46
|
end
|
@@ -70,18 +72,18 @@ describe 'Calculator' do
|
|
70
72
|
it 'should evaluate the pi constant' do
|
71
73
|
expect_expr('PI').to eq(3.141592653589793)
|
72
74
|
end
|
73
|
-
|
75
|
+
|
74
76
|
it 'should evaluate the negated pi constant' do
|
75
77
|
expect_expr('-PI').to eq(-3.141592653589793)
|
76
|
-
end
|
78
|
+
end
|
77
79
|
|
78
80
|
it "should evaluate Neper's e constant" do
|
79
81
|
expect_expr('E').to eq(2.718281828459045)
|
80
82
|
end
|
81
|
-
|
83
|
+
|
82
84
|
it "should evaluate negated Neper's e constant" do
|
83
85
|
expect_expr('-E').to eq(-2.718281828459045)
|
84
|
-
end
|
86
|
+
end
|
85
87
|
|
86
88
|
it 'should evaluate integer addition' do
|
87
89
|
expect_expr('2 + 2').to eq(4)
|
@@ -154,7 +156,7 @@ describe 'Calculator' do
|
|
154
156
|
it 'should evaluate multiple levels of parentheses' do
|
155
157
|
expect_expr('2*(1/(1+3))').to eq(0.5)
|
156
158
|
end
|
157
|
-
|
159
|
+
|
158
160
|
# Some special functions
|
159
161
|
it 'should evaluate square root of expressions' do
|
160
162
|
expect_expr('sqrt(0)').to eq(0)
|
@@ -168,69 +170,69 @@ describe 'Calculator' do
|
|
168
170
|
expect_expr('cbrt(1)').to eq(1)
|
169
171
|
expect_expr('cbrt(1 + 1)').to eq(Math.cbrt(2))
|
170
172
|
expect_expr('cbrt(5 * 5 * 5)').to eq(5)
|
171
|
-
end
|
173
|
+
end
|
172
174
|
|
173
175
|
it 'should evaluate exponential of expressions' do
|
174
|
-
expect_expr('exp(-1)').to eq(1 / Math::E)
|
176
|
+
expect_expr('exp(-1)').to eq(1 / Math::E)
|
175
177
|
expect_expr('exp(0)').to eq(1)
|
176
178
|
expect_expr('exp(1)').to eq(Math::E)
|
177
179
|
expect_expr('exp(2)').to be_within(epsilon).of(Math::E * Math::E)
|
178
180
|
end
|
179
181
|
|
180
182
|
it 'should evaluate natural logarithm of expressions' do
|
181
|
-
expect_expr('ln(1/E)').to eq(-1)
|
182
|
-
expect_expr('ln(1)').to eq(0)
|
183
|
+
expect_expr('ln(1/E)').to eq(-1)
|
184
|
+
expect_expr('ln(1)').to eq(0)
|
183
185
|
expect_expr('ln(E)').to eq(1)
|
184
186
|
expect_expr('ln(E * E)').to eq(2)
|
185
|
-
end
|
187
|
+
end
|
186
188
|
|
187
189
|
it 'should evaluate the logarithm base 10 of expressions' do
|
188
|
-
expect_expr('log(1/10)').to eq(-1)
|
189
|
-
expect_expr('log(1)').to eq(0)
|
190
|
+
expect_expr('log(1/10)').to eq(-1)
|
191
|
+
expect_expr('log(1)').to eq(0)
|
190
192
|
expect_expr('log(10)').to eq(1)
|
191
193
|
expect_expr('log(10 * 10 * 10)').to eq(3)
|
192
|
-
end
|
193
|
-
|
194
|
+
end
|
195
|
+
|
194
196
|
# Trigonometric functions
|
195
|
-
|
197
|
+
|
196
198
|
it 'should compute the sinus of an expression' do
|
197
199
|
expect_expr('sin(0)').to eq(0)
|
198
200
|
expect_expr('sin(PI/6)').to be_within(epsilon).of(0.5)
|
199
201
|
expect_expr('sin(PI/2)').to eq(1)
|
200
202
|
end
|
201
|
-
|
203
|
+
|
202
204
|
it 'should compute the cosinus of an expression' do
|
203
205
|
expect_expr('cos(0)').to eq(1)
|
204
206
|
expect_expr('cos(PI/3)').to be_within(epsilon).of(0.5)
|
205
207
|
expect_expr('cos(PI/2)').to be_within(epsilon).of(0)
|
206
|
-
end
|
208
|
+
end
|
207
209
|
|
208
210
|
it 'should compute the tangent of an expression' do
|
209
211
|
expect_expr('tan(0)').to eq(0)
|
210
212
|
expect_expr('tan(PI/4)').to be_within(epsilon).of(1)
|
211
213
|
expect_expr('tan(5*PI/12)').to be_within(epsilon).of(2 + Math.sqrt(3))
|
212
|
-
end
|
213
|
-
|
214
|
+
end
|
215
|
+
|
214
216
|
# Inverse trigonometric functions
|
215
|
-
|
217
|
+
|
216
218
|
it 'should compute the arcsinus of an expression' do
|
217
219
|
expect_expr('asin(0)').to eq(0)
|
218
220
|
expect_expr('asin(0.5)').to be_within(epsilon).of(Math::PI / 6)
|
219
221
|
expect_expr('asin(1)').to eq(Math::PI / 2)
|
220
222
|
end
|
221
|
-
|
223
|
+
|
222
224
|
it 'should compute the arccosinus of an expression' do
|
223
225
|
expect_expr('acos(1)').to eq(0)
|
224
226
|
expect_expr('acos(0.5)').to be_within(epsilon).of(Math::PI / 3)
|
225
227
|
expect_expr('acos(0)').to be_within(epsilon).of(Math::PI / 2)
|
226
|
-
end
|
228
|
+
end
|
227
229
|
|
228
230
|
it 'should compute the tangent of an expression' do
|
229
231
|
expect_expr('atan(0)').to eq(0)
|
230
232
|
pi = Math::PI
|
231
233
|
expect_expr('atan(1)').to be_within(epsilon).of(pi / 4)
|
232
234
|
expect_expr('atan(2 + sqrt(3))').to be_within(epsilon).of(5 * pi / 12)
|
233
|
-
end
|
235
|
+
end
|
234
236
|
end # context
|
235
237
|
end # describe
|
236
238
|
# End of file
|