electr 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|