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.
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"