electr 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.coco.yml +4 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +8 -0
  5. data/README.md +12 -13
  6. data/bin/electr +10 -5
  7. data/documentation/developer.md +21 -0
  8. data/documentation/grammar.md +34 -0
  9. data/documentation/precedence.md +8 -0
  10. data/electr.gemspec +1 -0
  11. data/lib/electr.rb +13 -2
  12. data/lib/electr/ast/ast.rb +24 -6
  13. data/lib/electr/ast/constant_ast.rb +1 -0
  14. data/lib/electr/ast/func_args_ast.rb +1 -0
  15. data/lib/electr/ast/func_ast.rb +1 -0
  16. data/lib/electr/ast/func_name_ast.rb +2 -0
  17. data/lib/electr/ast/numeric_ast.rb +1 -0
  18. data/lib/electr/ast/operator_ast.rb +1 -0
  19. data/lib/electr/ast/root_ast.rb +1 -0
  20. data/lib/electr/ast/value_ast.rb +1 -0
  21. data/lib/electr/compiler.rb +10 -6
  22. data/lib/electr/exceptions.rb +1 -0
  23. data/lib/electr/parser.rb +2 -0
  24. data/lib/electr/parser/lexer.rb +14 -37
  25. data/lib/electr/parser/lexical_unit.rb +7 -4
  26. data/lib/electr/{rules.rb → parser/rules.rb} +0 -0
  27. data/lib/electr/parser/rules/base_rule.rb +5 -13
  28. data/lib/electr/parser/rules/expression_rule.rb +18 -17
  29. data/lib/electr/parser/rules/root_rule.rb +5 -2
  30. data/lib/electr/parser/sya.rb +22 -21
  31. data/lib/electr/parser/syntaxer.rb +4 -1
  32. data/lib/electr/parser/token.rb +26 -0
  33. data/lib/electr/parser/tokenizer.rb +47 -7
  34. data/lib/electr/repl.rb +8 -56
  35. data/lib/electr/repl/ast_printer.rb +11 -0
  36. data/lib/electr/repl/ast_reader.rb +17 -0
  37. data/lib/electr/repl/base_reader.rb +23 -0
  38. data/lib/electr/{evaluator.rb → repl/evaluator.rb} +15 -1
  39. data/lib/electr/repl/nil_evaluator.rb +11 -0
  40. data/lib/electr/repl/printer.rb +12 -0
  41. data/lib/electr/repl/reader.rb +16 -0
  42. data/lib/electr/repl/repl.rb +28 -0
  43. data/lib/electr/version.rb +1 -1
  44. data/spec/ast/ast_spec.rb +17 -0
  45. data/spec/compiler_spec.rb +4 -6
  46. data/spec/parser/base_rule_spec.rb +13 -0
  47. data/spec/{lexer_spec.rb → parser/lexer_spec.rb} +5 -2
  48. data/spec/parser/pn_spec.rb +17 -0
  49. data/spec/{sya_spec.rb → parser/sya_spec.rb} +35 -0
  50. data/spec/{tokenizer_spec.rb → parser/tokenizer_spec.rb} +89 -0
  51. data/spec/repl/ast_printer_spec.rb +18 -0
  52. data/spec/repl/ast_reader_spec.rb +19 -0
  53. data/spec/{evaluator_spec.rb → repl/evaluator_spec.rb} +15 -5
  54. data/spec/repl/nil_evaluator_spec.rb +11 -0
  55. data/spec/repl/printer_spec.rb +19 -0
  56. data/spec/repl/reader_spec.rb +25 -0
  57. data/spec/repl/repl_spec.rb +32 -0
  58. data/spec/spec_helper.rb +1 -0
  59. 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! modify the units and
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 @series.first.numeric? || @series.first.constant? ||
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 first_unit_number?
36
- @units.first && @units.first.number?
37
- end
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.size < 2
49
+ if unit && unit.numeric? && there_is_room?(node)
54
50
  node.add_child(NumericAST.new(unit.value))
55
- elsif unit && unit.constant? && node.size < 2
51
+ elsif unit && unit.constant? && there_is_room?(node)
56
52
  node.add_child(ConstantAST.new(unit.value))
57
- elsif unit && unit.value? && node.size < 2
53
+ elsif unit && unit.value? && there_is_room?(node)
58
54
  node.add_child(ValueAST.new(unit.value))
59
- elsif unit && unit.fname? && node.size < 2
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
@@ -6,18 +6,10 @@ module Electr
6
6
  # polish notation to ease producing the AST.
7
7
  class Sya
8
8
 
9
- PRECEDENCE = {
10
- '()' => {assoc: 'L', val: 99},
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
- temp = []
82
+ new_units = []
91
83
  while unit = @units.shift
92
- temp << unit
93
- if @units.first
94
- if (@units.first.number? && unit.number?) ||
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 = temp
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 @look_ahead =~ /[0-9]/
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 while @look_ahead =~ /[0-9.]/
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
- module Electr
2
-
3
- class Repl
4
-
5
- BANNER = "\nElectr version #{Electr::VERSION}\n" +
6
- "A tiny language for electronic formulas\n\n" +
7
- "Hit Ctrl+C to quit Electr\n\n"
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"