electr 0.0.2 → 0.0.3
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/.coco.yml +4 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +12 -13
- data/bin/electr +10 -5
- data/documentation/developer.md +21 -0
- data/documentation/grammar.md +34 -0
- data/documentation/precedence.md +8 -0
- data/electr.gemspec +1 -0
- data/lib/electr.rb +13 -2
- data/lib/electr/ast/ast.rb +24 -6
- data/lib/electr/ast/constant_ast.rb +1 -0
- data/lib/electr/ast/func_args_ast.rb +1 -0
- data/lib/electr/ast/func_ast.rb +1 -0
- data/lib/electr/ast/func_name_ast.rb +2 -0
- data/lib/electr/ast/numeric_ast.rb +1 -0
- data/lib/electr/ast/operator_ast.rb +1 -0
- data/lib/electr/ast/root_ast.rb +1 -0
- data/lib/electr/ast/value_ast.rb +1 -0
- data/lib/electr/compiler.rb +10 -6
- data/lib/electr/exceptions.rb +1 -0
- data/lib/electr/parser.rb +2 -0
- data/lib/electr/parser/lexer.rb +14 -37
- data/lib/electr/parser/lexical_unit.rb +7 -4
- data/lib/electr/{rules.rb → parser/rules.rb} +0 -0
- data/lib/electr/parser/rules/base_rule.rb +5 -13
- data/lib/electr/parser/rules/expression_rule.rb +18 -17
- data/lib/electr/parser/rules/root_rule.rb +5 -2
- data/lib/electr/parser/sya.rb +22 -21
- data/lib/electr/parser/syntaxer.rb +4 -1
- data/lib/electr/parser/token.rb +26 -0
- data/lib/electr/parser/tokenizer.rb +47 -7
- data/lib/electr/repl.rb +8 -56
- data/lib/electr/repl/ast_printer.rb +11 -0
- data/lib/electr/repl/ast_reader.rb +17 -0
- data/lib/electr/repl/base_reader.rb +23 -0
- data/lib/electr/{evaluator.rb → repl/evaluator.rb} +15 -1
- data/lib/electr/repl/nil_evaluator.rb +11 -0
- data/lib/electr/repl/printer.rb +12 -0
- data/lib/electr/repl/reader.rb +16 -0
- data/lib/electr/repl/repl.rb +28 -0
- data/lib/electr/version.rb +1 -1
- data/spec/ast/ast_spec.rb +17 -0
- data/spec/compiler_spec.rb +4 -6
- data/spec/parser/base_rule_spec.rb +13 -0
- data/spec/{lexer_spec.rb → parser/lexer_spec.rb} +5 -2
- data/spec/parser/pn_spec.rb +17 -0
- data/spec/{sya_spec.rb → parser/sya_spec.rb} +35 -0
- data/spec/{tokenizer_spec.rb → parser/tokenizer_spec.rb} +89 -0
- data/spec/repl/ast_printer_spec.rb +18 -0
- data/spec/repl/ast_reader_spec.rb +19 -0
- data/spec/{evaluator_spec.rb → repl/evaluator_spec.rb} +15 -5
- data/spec/repl/nil_evaluator_spec.rb +11 -0
- data/spec/repl/printer_spec.rb +19 -0
- data/spec/repl/reader_spec.rb +25 -0
- data/spec/repl/repl_spec.rb +32 -0
- data/spec/spec_helper.rb +1 -0
- metadata +56 -12
@@ -1,11 +1,14 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
+
# Holds some data about a lexica unit (lexeme) of the language.
|
3
4
|
class LexicalUnit
|
4
5
|
|
5
6
|
private_class_method :new
|
6
7
|
|
7
8
|
attr_reader :type, :value
|
8
9
|
|
10
|
+
# Create a new LexicalUnit.
|
11
|
+
#
|
9
12
|
# type - Symbol
|
10
13
|
# value - String
|
11
14
|
def initialize(type, value)
|
@@ -35,6 +38,10 @@ module Electr
|
|
35
38
|
@type == :operator
|
36
39
|
end
|
37
40
|
|
41
|
+
def unary_minus?
|
42
|
+
operator? && @value == UNARY_MINUS_INTERNAL_SYMBOL
|
43
|
+
end
|
44
|
+
|
38
45
|
def constant?
|
39
46
|
@type == :constant
|
40
47
|
end
|
@@ -47,10 +54,6 @@ module Electr
|
|
47
54
|
@type == :fname
|
48
55
|
end
|
49
56
|
|
50
|
-
def fcall?
|
51
|
-
@type == :fcall
|
52
|
-
end
|
53
|
-
|
54
57
|
def number?
|
55
58
|
numeric? || constant? || value?
|
56
59
|
end
|
File without changes
|
@@ -1,9 +1,11 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
+
# Base class for the rules. «Just» follow the grammar's rules to
|
4
|
+
# produce the AST.
|
3
5
|
class BaseRule
|
4
6
|
|
5
7
|
# Initialize a new rule. The units and ast_node objects
|
6
|
-
# given in arguments will be modified by #apply
|
8
|
+
# given in arguments will be modified by #apply.
|
7
9
|
#
|
8
10
|
# units - Array of LexicalUnits.
|
9
11
|
# ast_node - An AST object, for adding children, etc.
|
@@ -14,9 +16,9 @@ module Electr
|
|
14
16
|
end
|
15
17
|
|
16
18
|
# Public: Apply the rule to the list of lexical units, creating
|
17
|
-
# and/or modifying the ast node. #apply
|
19
|
+
# and/or modifying the ast node. #apply modify the units and
|
18
20
|
# ast_node objects given to #initialize.
|
19
|
-
def apply
|
21
|
+
def apply
|
20
22
|
raise NotImplementedError
|
21
23
|
end
|
22
24
|
|
@@ -45,16 +47,6 @@ module Electr
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
# A shortcut method to #accept. Usefull when we have several calls
|
49
|
-
# to #accept. Calls #accept for each of the symbol in args.
|
50
|
-
#
|
51
|
-
# args - A series of Symbol.
|
52
|
-
#
|
53
|
-
# Returns nothing.
|
54
|
-
def accept_all(*args)
|
55
|
-
args.each {|arg| accept(arg) }
|
56
|
-
end
|
57
|
-
|
58
50
|
end
|
59
51
|
end
|
60
52
|
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
+
# Rules an Electr expression.
|
4
|
+
#
|
5
|
+
# TODO For now almost everything is an expression in Electr, so this
|
6
|
+
# is a somewhat complicated class. It should be refactored a lot.
|
3
7
|
class ExpressionRule < BaseRule
|
4
8
|
|
5
|
-
def apply
|
9
|
+
def apply
|
6
10
|
while @units.size > 0
|
7
11
|
if first_unit_fname?
|
8
12
|
accept(:fname)
|
@@ -19,8 +23,7 @@ module Electr
|
|
19
23
|
sya = Sya.new(@series.dup)
|
20
24
|
@series = sya.run
|
21
25
|
|
22
|
-
if
|
23
|
-
@series.first.value? || @series.first.fname?
|
26
|
+
if number?
|
24
27
|
dig_series(@ast_node)
|
25
28
|
else
|
26
29
|
unit = @series.shift
|
@@ -32,16 +35,9 @@ module Electr
|
|
32
35
|
|
33
36
|
private
|
34
37
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
def first_unit_numeric?
|
40
|
-
@units.first && @units.first.numeric?
|
41
|
-
end
|
42
|
-
|
43
|
-
def first_unit_operator?
|
44
|
-
@units.first && @units.first.operator?
|
38
|
+
def number?
|
39
|
+
first = @series.first
|
40
|
+
first.numeric? || first.constant? || first.value? || first.fname?
|
45
41
|
end
|
46
42
|
|
47
43
|
def first_unit_fname?
|
@@ -50,13 +46,13 @@ module Electr
|
|
50
46
|
|
51
47
|
def dig_series(node)
|
52
48
|
while unit = @series.shift
|
53
|
-
if unit && unit.numeric? && node
|
49
|
+
if unit && unit.numeric? && there_is_room?(node)
|
54
50
|
node.add_child(NumericAST.new(unit.value))
|
55
|
-
elsif unit && unit.constant? && node
|
51
|
+
elsif unit && unit.constant? && there_is_room?(node)
|
56
52
|
node.add_child(ConstantAST.new(unit.value))
|
57
|
-
elsif unit && unit.value? && node
|
53
|
+
elsif unit && unit.value? && there_is_room?(node)
|
58
54
|
node.add_child(ValueAST.new(unit.value))
|
59
|
-
elsif unit && unit.fname? && node
|
55
|
+
elsif unit && unit.fname? && there_is_room?(node)
|
60
56
|
func = FuncAST.new
|
61
57
|
func_name = FuncNameAST.new(unit.value)
|
62
58
|
func_args = FuncArgsAST.new
|
@@ -75,5 +71,10 @@ module Electr
|
|
75
71
|
end
|
76
72
|
end
|
77
73
|
|
74
|
+
def there_is_room?(node)
|
75
|
+
(node.value == UNARY_MINUS_INTERNAL_SYMBOL && node.size < 1) ||
|
76
|
+
(node.value != UNARY_MINUS_INTERNAL_SYMBOL && node.size < 2)
|
77
|
+
end
|
78
|
+
|
78
79
|
end
|
79
80
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
+
# Starting point for the rules.
|
4
|
+
#
|
5
|
+
# TODO Is this really needed?
|
3
6
|
class RootRule < BaseRule
|
4
7
|
|
5
|
-
def apply
|
8
|
+
def apply
|
6
9
|
root_node = RootAST.new
|
7
10
|
while more_units?
|
8
|
-
ExpressionRule.new(@units, root_node).apply
|
11
|
+
ExpressionRule.new(@units, root_node).apply
|
9
12
|
end
|
10
13
|
@ast_node.add_child(root_node)
|
11
14
|
end
|
data/lib/electr/parser/sya.rb
CHANGED
@@ -6,18 +6,10 @@ module Electr
|
|
6
6
|
# polish notation to ease producing the AST.
|
7
7
|
class Sya
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
')' => {assoc: 'L', val: 99},
|
12
|
-
'(' => {assoc: 'L', val: 99},
|
13
|
-
'*' => {assoc: 'L', val: 10},
|
14
|
-
'/' => {assoc: 'L', val: 10},
|
15
|
-
'-' => {assoc: 'L', val: 1},
|
16
|
-
'+' => {assoc: 'L', val: 1},
|
17
|
-
}
|
18
|
-
|
9
|
+
# Creates a new Sya.
|
10
|
+
#
|
19
11
|
# lexical_units - Array of LexicalUnit in the order they have been
|
20
|
-
# parsed (natural ordering).
|
12
|
+
# parsed (natural/infix ordering).
|
21
13
|
def initialize(lexical_units)
|
22
14
|
@units = lexical_units
|
23
15
|
insert_multiplication
|
@@ -25,7 +17,7 @@ module Electr
|
|
25
17
|
@operator = []
|
26
18
|
end
|
27
19
|
|
28
|
-
# Run the Shunting Yard Algorithm.
|
20
|
+
# Public: Run the Shunting Yard Algorithm.
|
29
21
|
#
|
30
22
|
# Returns Array of LexicalUnit ordered with prefix notation.
|
31
23
|
def run
|
@@ -87,18 +79,27 @@ module Electr
|
|
87
79
|
#
|
88
80
|
# Returns nothing.
|
89
81
|
def insert_multiplication
|
90
|
-
|
82
|
+
new_units = []
|
91
83
|
while unit = @units.shift
|
92
|
-
|
93
|
-
if
|
94
|
-
|
95
|
-
(@units.first.open_parenthesis? && unit.number?) ||
|
96
|
-
(@units.first.fname? && unit.number?)
|
97
|
-
temp << LexicalUnit.operator('*')
|
98
|
-
end
|
84
|
+
new_units << unit
|
85
|
+
if maybe_insertion_needed?(unit)
|
86
|
+
new_units << LexicalUnit.operator('*') if insertion_needed?
|
99
87
|
end
|
100
88
|
end
|
101
|
-
@units =
|
89
|
+
@units = new_units
|
90
|
+
end
|
91
|
+
|
92
|
+
def maybe_insertion_needed?(unit)
|
93
|
+
unit_ahead && unit.number?
|
94
|
+
end
|
95
|
+
|
96
|
+
def insertion_needed?
|
97
|
+
unit_ahead.number? || unit_ahead.open_parenthesis? ||
|
98
|
+
unit_ahead.fname? || unit_ahead.unary_minus?
|
99
|
+
end
|
100
|
+
|
101
|
+
def unit_ahead
|
102
|
+
@units.first
|
102
103
|
end
|
103
104
|
|
104
105
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
3
|
# A Syntaxer transform a list of lexical units into an AST.
|
4
|
+
#
|
5
|
+
# There isn't a monolithic class that does this work. There is
|
6
|
+
# actually some rule's classes to make the AST.
|
4
7
|
class Syntaxer
|
5
8
|
|
6
9
|
# lexical_units - Array of LexicalUnits
|
@@ -13,7 +16,7 @@ module Electr
|
|
13
16
|
#
|
14
17
|
# Returns the AST.
|
15
18
|
def run
|
16
|
-
RootRule.new(@units, @ast).apply
|
19
|
+
RootRule.new(@units, @ast).apply
|
17
20
|
@ast
|
18
21
|
end
|
19
22
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Electr
|
2
|
+
module Token
|
3
|
+
|
4
|
+
refine String do
|
5
|
+
|
6
|
+
def numeric?
|
7
|
+
self =~ /[0-9.]\Z/
|
8
|
+
end
|
9
|
+
|
10
|
+
def operator?
|
11
|
+
['+', '-', '/', UNARY_MINUS_INTERNAL_SYMBOL].include?(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def constant?
|
15
|
+
%w( pi ).include?(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def value?
|
19
|
+
# The unit part is redondant with Tokenizer.
|
20
|
+
self =~ /[0-9.][kKRuFpΩμAmWV]{1,}\Z/
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -1,9 +1,22 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
+
# The tokenizer is the first step of the parser. It explodes a code in
|
4
|
+
# its constituants (token), ie # `sqrt(3+2)` becomes the following
|
5
|
+
# list of tokens:
|
6
|
+
#
|
7
|
+
# - `sqrt`
|
8
|
+
# - `(`
|
9
|
+
# - `3`
|
10
|
+
# - `+`
|
11
|
+
# - `2`
|
12
|
+
# - `)`
|
3
13
|
class Tokenizer
|
4
14
|
|
5
15
|
ONE_CHAR_SYMBOLS = %w( + - / )
|
6
16
|
|
17
|
+
# Create a new Tokenizer for a specific code.
|
18
|
+
#
|
19
|
+
# string - The String code to tokenize.
|
7
20
|
def initialize(string)
|
8
21
|
@index = 0
|
9
22
|
@token = ''
|
@@ -12,10 +25,16 @@ module Electr
|
|
12
25
|
forward_look_ahead
|
13
26
|
end
|
14
27
|
|
28
|
+
# Public: Check if the tokenizer is able to give another token.
|
29
|
+
#
|
30
|
+
# Returns Boolean true if there is another waiting token.
|
15
31
|
def has_more_token?
|
16
32
|
@index <= @codeline.size
|
17
33
|
end
|
18
34
|
|
35
|
+
# Public: Get the next token.
|
36
|
+
#
|
37
|
+
# Returns a String token.
|
19
38
|
def next_token
|
20
39
|
ret = produce_next_token
|
21
40
|
skip_white
|
@@ -30,9 +49,16 @@ module Electr
|
|
30
49
|
@index += 1
|
31
50
|
end
|
32
51
|
|
52
|
+
def add_look_ahead
|
53
|
+
@token << @look_ahead
|
54
|
+
forward_look_ahead
|
55
|
+
end
|
56
|
+
|
33
57
|
def produce_next_token
|
34
|
-
if
|
58
|
+
if begin_like_number?
|
35
59
|
get_number
|
60
|
+
elsif unary_minus?
|
61
|
+
get_unary_minus
|
36
62
|
elsif ONE_CHAR_SYMBOLS.include?(@look_ahead)
|
37
63
|
add_this_char
|
38
64
|
elsif @look_ahead == '('
|
@@ -48,19 +74,33 @@ module Electr
|
|
48
74
|
forward_look_ahead while @look_ahead == ' '
|
49
75
|
end
|
50
76
|
|
77
|
+
def begin_like_number?
|
78
|
+
@look_ahead =~ /[0-9.]/ ||
|
79
|
+
(@look_ahead == '-' && @codeline[@index] =~ /[0-9.]/)
|
80
|
+
end
|
81
|
+
|
82
|
+
def unary_minus?
|
83
|
+
# We are able to recognize the unary minus operator only when it
|
84
|
+
# is followed by a name (constant or function) or an open
|
85
|
+
# parenthesis.
|
86
|
+
@look_ahead == '-' && @codeline[@index] =~ /[a-z\(]/
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_unary_minus
|
90
|
+
@look_ahead = UNARY_MINUS_INTERNAL_SYMBOL
|
91
|
+
add_look_ahead
|
92
|
+
@token
|
93
|
+
end
|
94
|
+
|
51
95
|
def get_number
|
52
|
-
add_look_ahead
|
96
|
+
add_look_ahead if @look_ahead == '-'
|
97
|
+
add_look_ahead while @look_ahead =~ /[0-9._,]/
|
53
98
|
if @token[-1] != '.'
|
54
99
|
add_look_ahead while @look_ahead =~ /[kKRuFpΩμAmWV]/
|
55
100
|
end
|
56
101
|
@token
|
57
102
|
end
|
58
103
|
|
59
|
-
def add_look_ahead
|
60
|
-
@token << @look_ahead
|
61
|
-
forward_look_ahead
|
62
|
-
end
|
63
|
-
|
64
104
|
def get_word
|
65
105
|
add_look_ahead while @look_ahead =~ /\w/
|
66
106
|
@token
|
data/lib/electr/repl.rb
CHANGED
@@ -1,56 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(options)
|
10
|
-
@options = options
|
11
|
-
puts BANNER
|
12
|
-
end
|
13
|
-
|
14
|
-
def run
|
15
|
-
if @options[:ast]
|
16
|
-
print_ast(read_ast) while true
|
17
|
-
else
|
18
|
-
_print(_eval(_read)) while true
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def _read
|
25
|
-
print("E> ")
|
26
|
-
line = STDIN.gets.chomp
|
27
|
-
compiler = Compiler.new
|
28
|
-
compiler.compile_to_pn(line)
|
29
|
-
end
|
30
|
-
|
31
|
-
def read_ast
|
32
|
-
print("E--ast> ")
|
33
|
-
line = STDIN.gets.chomp
|
34
|
-
compiler = Compiler.new
|
35
|
-
compiler.compile_to_ast(line)
|
36
|
-
end
|
37
|
-
|
38
|
-
def _eval(pns)
|
39
|
-
evaluator = Evaluator.new
|
40
|
-
evaluator.evaluate_pn(pns)
|
41
|
-
end
|
42
|
-
|
43
|
-
def _print(result)
|
44
|
-
if result == result.truncate
|
45
|
-
puts result.truncate
|
46
|
-
else
|
47
|
-
puts result.round(10)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def print_ast(ast)
|
52
|
-
ast.display
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
1
|
+
require "electr/repl/evaluator"
|
2
|
+
require "electr/repl/nil_evaluator"
|
3
|
+
require "electr/repl/base_reader"
|
4
|
+
require "electr/repl/reader"
|
5
|
+
require "electr/repl/ast_reader"
|
6
|
+
require "electr/repl/printer"
|
7
|
+
require "electr/repl/ast_printer"
|
8
|
+
require "electr/repl/repl"
|