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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 129c7c8560ec169ade1a934c574de8a045e3dbc0
4
- data.tar.gz: c04ece5fd979bad30c69e5d67b64899b841ac72e
3
+ metadata.gz: 6be27b7b5efaeb5cb477f1bda270e4d1b95d4fec
4
+ data.tar.gz: 086e216808aa9e6709ee47ff5a648a3d175779f7
5
5
  SHA512:
6
- metadata.gz: faee90b0d4a72a8bf8c74c9530c697062e7edde33c6c96b0135d56aa93bda5573e1d78647279b7104622300bab369900f1ffd5d2d2be0b30915af20bc5430b6e
7
- data.tar.gz: c04497b5911bb940656a495eaab0232be5394b456baf27c4f1a209a3aa2e86f27630e986b59b2499ba3c417a0bf557305c1f04fbe698a1795f6dc3a6e23fd23f
6
+ metadata.gz: 9d206038f0b0f77a76fc388c1f152416f623af280a766cb93f67fdf547e200c4ad3d135f6b2db4a2c4ce2e560eb55ef43bd3dde0bc14cf83c534bc3b352c272c
7
+ data.tar.gz: 3877f48e19951b1b2cb78b4fa0b3ef9197b16229246e63a18e1aefa575b054cade8fd5497815beb237204c5624e58a00a39fcf417d1725743b3747f0eff4adc4
data/.coco.yml ADDED
@@ -0,0 +1,4 @@
1
+ :excludes:
2
+ - lib/electr/version.rb
3
+ - lib/electr/option.rb
4
+ :always_run: false
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  TODO
16
+ tags
data/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased] - unreleased
6
+ ### Added
7
+ - Quit gracefuly with Ctrl-C
8
+ - 10,000 and 10_000 are the same as 10000
9
+ - Electr supports negative numbers
10
+ - Coco, a code coverage tool as a developer dependency
11
+ - Enable Travis Continuous Integration tool
12
+ ### Modified
13
+ - One can write floating point number without a leading zero (ie `.678`).
6
14
 
7
15
  ## [0.0.2] - 2015-09-04
8
16
  ### Added
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Electr
2
- ======
1
+ # Electr [![Gem Version](https://badge.fury.io/rb/electr.png)](http://badge.fury.io/rb/electr)
3
2
 
4
3
  Interactive language for electronic formulas.
5
4
 
@@ -117,16 +116,16 @@ Electr is at a very early stage and it miss a lot of (basic) things!
117
116
  You can expect that the following features will be implemented in the
118
117
  next couple of days/weeks:
119
118
 
120
- [ ] Negative numbers
121
- [ ] `*` for the multiplication if one want to
122
- [ ] for an alternative to square root
123
- [ ] Floating point number without a leading zero (ie `.678`)
124
- [ ] More buitin functions (sin, cos, tan, etc)
125
- [ ] Exponent
126
- [ ] Shortcuts for function's names (ie sq and sqr for sqrt)
127
- [ ] Readline lib in the REPL for a better user experience
128
- [ ] 10_000 or 10,000 will be the same as 10000
129
- [ ] All units and prefix used in electronic
119
+ - [x] Negative numbers
120
+ - [x] Floating point number without a leading zero (ie `.678`)
121
+ - [x] 10_000 or 10,000 will be the same as 10000
122
+ - [ ] `*` for the multiplication if one want to
123
+ - [ ] for an alternative to square root
124
+ - [ ] More builtin functions (sin, cos, tan, etc)
125
+ - [ ] Exponent
126
+ - [ ] Shortcuts for function's names (ie sq and sqr for sqrt)
127
+ - [ ] Readline lib in the REPL for a better user experience
128
+ - [ ] All units and prefix used in electronic
130
129
 
131
130
  ## What's next?
132
131
 
@@ -177,7 +176,7 @@ Why not having custom functions?
177
176
 
178
177
  ## Contributing
179
178
 
180
- 1. Fork it ( https://github.com/[my-github-username]/electr/fork )
179
+ 1. Fork it ( https://github.com/lkdjiin/electr/fork )
181
180
  2. **PLEASE Create your feature branch** (`git checkout -b my-new-feature`)
182
181
  3. Commit your changes (`git commit -am 'Add some feature'`)
183
182
  4. Push to the branch (`git push origin my-new-feature`)
data/bin/electr CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  require 'electr'
4
4
 
5
+ trap("INT") { puts "\nBye."; exit }
6
+
5
7
  opt = Electr::Option.new
6
8
 
7
9
  if opt[:expression]
8
10
  if opt[:ast]
9
- compiler = Electr::Compiler.new
10
- ast = compiler.compile_to_ast(opt[:expression])
11
+ ast = Electr::Compiler.compile_to_ast(opt[:expression])
11
12
  ast.display
12
13
  else
13
14
  # FIXME Should be under lib/ to be tested and there is a lot of
14
15
  # common code with Repl.
15
- compiler = Electr::Compiler.new
16
- temp = compiler.compile_to_pn(opt[:expression])
16
+ temp = Electr::Compiler.compile_to_pn(opt[:expression])
17
17
  evaluator = Electr::Evaluator.new
18
18
  temp = evaluator.evaluate_pn(temp)
19
19
  if temp == temp.truncate
@@ -23,5 +23,10 @@ if opt[:expression]
23
23
  end
24
24
  end
25
25
  else
26
- Electr::Repl.new(opt).run
26
+ if opt[:ast]
27
+ Electr::Repl.new(Electr::ASTReader.new, Electr::NilEvaluator.new,
28
+ Electr::ASTPrinter).run
29
+ else
30
+ Electr::Repl.new.run
31
+ end
27
32
  end
@@ -0,0 +1,21 @@
1
+ The parsing process
2
+ ===================
3
+
4
+ The aim of the parsing process it to transform a source code into an AST.
5
+
6
+ AST: [Abstract Syntax Tree][AST]
7
+
8
+ SYA: [Shunting Yard Algorithm][SYA]
9
+
10
+ PN: [Prefix Notation][PN]
11
+
12
+ This is the stages of the parser:
13
+
14
+ 1. Tokenizer
15
+ 2. Lexer
16
+ 3. Syntaxer
17
+ 4. AST (using SYA for formulas)
18
+
19
+ [AST]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
20
+ [SYA]: https://en.wikipedia.org/wiki/Shunting-yard_algorithm
21
+ [PN]: https://en.wikipedia.org/wiki/Polish_notation
@@ -0,0 +1,34 @@
1
+ # Grammar of Electr in EBNF
2
+
3
+ expression = [`(`], number {[op] expression}, [`)`];
4
+
5
+ number = numeric | value | constant | f-call;
6
+
7
+ numeric = integer | float;
8
+
9
+ integer = ? integer ?;
10
+
11
+ float = ? floating point number ?;
12
+
13
+ value = ? integer or floating point directly followed by a unit ?;
14
+
15
+ constant = `pi`;
16
+
17
+ (* function call *)
18
+ f-call = f-name, `(`, expression, `)`;
19
+
20
+ (* function name *)
21
+ f-name = ? regexp: [a-z]{1,} ?;
22
+
23
+ (* operators *)
24
+ op = `-` | `+` | `/`;
25
+
26
+ ## Precisions
27
+
28
+ ### Negative numbers
29
+
30
+ `-123` is a negative number.<br/>
31
+ `- 123` is not.
32
+
33
+ This is because the multiplication is implicit, ie `2 - 3` equals `-1` and
34
+ `2 -3` equals `-6`.
@@ -0,0 +1,8 @@
1
+ Precedence Table
2
+ ================
3
+
4
+ Operator | Description | Associativity
5
+ :-------: | ------------- | --------------
6
+ () | Function call | Left
7
+ * / | Multiplication and division | Left
8
+ + - | Addition and substraction | Left
data/electr.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "bundler", "~> 1.7"
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
24
  spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "coco"
25
26
  end
data/lib/electr.rb CHANGED
@@ -5,16 +5,27 @@ require "electr/exceptions"
5
5
  require "electr/parser"
6
6
  require "electr/compiler"
7
7
  require "electr/ast"
8
- require "electr/rules"
9
- require "electr/evaluator"
10
8
  require "electr/repl"
11
9
  require "electr/option"
12
10
 
13
11
  module Electr
14
12
 
13
+ UNARY_MINUS_INTERNAL_SYMBOL = '€'
14
+
15
15
  # f - function
16
16
  SYMBOL_TABLE = {
17
17
  'sqrt' => 'f',
18
18
  }
19
19
 
20
+ PRECEDENCE = {
21
+ '()' => {assoc: 'L', val: 99},
22
+ ')' => {assoc: 'L', val: 99},
23
+ '(' => {assoc: 'L', val: 99},
24
+ UNARY_MINUS_INTERNAL_SYMBOL => {assoc: 'R', val: 80},
25
+ '*' => {assoc: 'L', val: 10},
26
+ '/' => {assoc: 'L', val: 10},
27
+ '-' => {assoc: 'L', val: 1},
28
+ '+' => {assoc: 'L', val: 1},
29
+ }
30
+
20
31
  end
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # Base class for the abstract syntax tree.
3
4
  class AST
4
5
 
5
6
  def initialize(name)
@@ -12,6 +13,8 @@ module Electr
12
13
  @value = nil
13
14
  end
14
15
 
16
+ attr_reader :value, :name
17
+
15
18
  # Public: Add a child node to the end of the children's list.
16
19
  #
17
20
  # child - An AST node to add to the list of children.
@@ -32,12 +35,8 @@ module Electr
32
35
  end
33
36
 
34
37
  def display(indent = 0)
35
- print " " * indent + @name
36
- if leaf?
37
- print " ::= #@value"
38
- else
39
- print " (#@value)" if @value
40
- end
38
+ print name_for_display(indent)
39
+ print value_for_display
41
40
  puts
42
41
  @children.each {|child| child.display(indent + 2) }
43
42
  end
@@ -46,8 +45,23 @@ module Electr
46
45
  [* Pn.new(@value, @name)] + @children.map {|child| child.to_pn }.flatten
47
46
  end
48
47
 
48
+ private
49
+
50
+ def name_for_display(indent)
51
+ " " * indent + @name
52
+ end
53
+
54
+ def value_for_display
55
+ if leaf?
56
+ " ::= #@value"
57
+ else
58
+ " (#@value)" if @value
59
+ end
60
+ end
61
+
49
62
  end
50
63
 
64
+ # Element of a prefix notation.
51
65
  # TODO Pn is not a good name!
52
66
  class Pn
53
67
 
@@ -57,6 +71,10 @@ module Electr
57
71
  end
58
72
 
59
73
  attr_reader :value, :name
74
+
75
+ def to_f
76
+ value.tr(',', '_').to_f
77
+ end
60
78
  end
61
79
 
62
80
  end
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A constant node in the AST, ie the number pi.
3
4
  class ConstantAST < AST
4
5
 
5
6
  def initialize(value)
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to hold arguments of a function.
3
4
  class FuncArgsAST < AST
4
5
 
5
6
  def initialize
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to represent a function.
3
4
  class FuncAST < AST
4
5
 
5
6
  def initialize
@@ -1,5 +1,7 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to represent the name of a function, ie sqrt, sin
4
+ # or cos.
3
5
  class FuncNameAST < AST
4
6
 
5
7
  def initialize(value)
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to represent a numeric, ie 234 or 17.89
3
4
  class NumericAST < AST
4
5
 
5
6
  def initialize(value)
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to hold an operator, ie +, -, etc.
3
4
  class OperatorAST < AST
4
5
 
5
6
  def initialize(value)
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # The root node of the AST.
3
4
  class RootAST < AST
4
5
 
5
6
  def initialize
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # A node in the AST to hold a value, ie 10mA or 3V.
3
4
  class ValueAST < AST
4
5
 
5
6
  def initialize(value)
@@ -1,22 +1,26 @@
1
1
  module Electr
2
2
 
3
+ # The toolchain that takes a code at input and outputs it in another
4
+ # structure.
5
+ #
6
+ # The compiler could outputs the code either as an AST or as a list of
7
+ # element in the prefix notation.
3
8
  class Compiler
4
9
 
5
- def compile_to_ast(code)
10
+ def self.compile_to_ast(code)
6
11
  units = []
7
12
  tokenizer = Tokenizer.new(code)
8
- lexer = Lexer.new
9
13
  while tokenizer.has_more_token?
10
- units << lexer.lexify(tokenizer.next_token)
14
+ units << Lexer.lexify(tokenizer.next_token)
11
15
  end
12
16
 
13
17
  syntaxer = Syntaxer.new(units.dup)
14
- ast = syntaxer.run
18
+ syntaxer.run
15
19
  end
16
20
 
17
21
  # To Prefix Notation.
18
- def compile_to_pn(code)
19
- ast = compile_to_ast(code)
22
+ def self.compile_to_pn(code)
23
+ ast = self.compile_to_ast(code)
20
24
  ast.to_pn
21
25
  end
22
26
 
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # The good old syntax error ;)
3
4
  class SyntaxError < StandardError
4
5
  end
5
6
 
data/lib/electr/parser.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "electr/parser/tokenizer"
2
+ require "electr/parser/token"
2
3
  require "electr/parser/lexer"
3
4
  require "electr/parser/lexical_unit"
5
+ require "electr/parser/rules"
4
6
  require "electr/parser/syntaxer"
5
7
  require "electr/parser/sya"
@@ -1,50 +1,27 @@
1
1
  module Electr
2
2
 
3
- class Lexer
3
+ # Lexical analysis.
4
+ module Lexer
5
+
6
+ using Token
4
7
 
5
8
  # Public: Build a LexicalUnit from a given token.
6
9
  #
7
10
  # token - A String token to transform in LexicalUnit.
8
11
  #
9
12
  # Returns a LexicalUnit.
10
- def lexify(token)
11
- if numeric?(token)
12
- LexicalUnit.numeric(token)
13
- elsif operator?(token)
14
- LexicalUnit.operator(token)
15
- elsif constant?(token)
16
- LexicalUnit.constant(token)
17
- elsif value?(token)
18
- LexicalUnit.value(token)
19
- elsif token == '('
20
- LexicalUnit.open_parenthesis()
21
- elsif token == ')'
22
- LexicalUnit.closed_parenthesis()
23
- elsif SYMBOL_TABLE[token] == 'f'
24
- LexicalUnit.fname(token)
25
- else
26
- LexicalUnit.name(token)
13
+ def self.lexify(token)
14
+ case token
15
+ when ->(x) { x.numeric? } then LexicalUnit.numeric(token)
16
+ when ->(x) { x.operator? } then LexicalUnit.operator(token)
17
+ when ->(x) { x.constant? } then LexicalUnit.constant(token)
18
+ when ->(x) { x.value? } then LexicalUnit.value(token)
19
+ when ->(x) { x == '(' } then LexicalUnit.open_parenthesis
20
+ when ->(x) { x == ')' } then LexicalUnit.closed_parenthesis
21
+ when ->(x) { SYMBOL_TABLE[x] == 'f' } then LexicalUnit.fname(token)
22
+ else LexicalUnit.name(token)
27
23
  end
28
24
  end
29
25
 
30
- private
31
-
32
- def numeric?(token)
33
- token =~ /[0-9.]\Z/
34
- end
35
-
36
- def operator?(token)
37
- %w( + - / ).include?(token)
38
- end
39
-
40
- def constant?(token)
41
- %w( pi ).include?(token)
42
- end
43
-
44
- def value?(token)
45
- # The unit part is redondant with Tokenizer.
46
- token =~ /[0-9.][kKRuFpΩμAmWV]{1,}\Z/
47
- end
48
-
49
26
  end
50
27
  end