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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6be27b7b5efaeb5cb477f1bda270e4d1b95d4fec
|
4
|
+
data.tar.gz: 086e216808aa9e6709ee47ff5a648a3d175779f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d206038f0b0f77a76fc388c1f152416f623af280a766cb93f67fdf547e200c4ad3d135f6b2db4a2c4ce2e560eb55ef43bd3dde0bc14cf83c534bc3b352c272c
|
7
|
+
data.tar.gz: 3877f48e19951b1b2cb78b4fa0b3ef9197b16229246e63a18e1aefa575b054cade8fd5497815beb237204c5624e58a00a39fcf417d1725743b3747f0eff4adc4
|
data/.coco.yml
ADDED
data/.gitignore
CHANGED
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
|
-
[
|
121
|
-
[
|
122
|
-
[
|
123
|
-
[ ]
|
124
|
-
[ ]
|
125
|
-
[ ]
|
126
|
-
[ ]
|
127
|
-
[ ]
|
128
|
-
[ ]
|
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/
|
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
|
-
|
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
|
-
|
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
|
-
|
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`.
|
data/electr.gemspec
CHANGED
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
|
data/lib/electr/ast/ast.rb
CHANGED
@@ -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
|
36
|
-
|
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
|
data/lib/electr/ast/func_ast.rb
CHANGED
data/lib/electr/ast/root_ast.rb
CHANGED
data/lib/electr/ast/value_ast.rb
CHANGED
data/lib/electr/compiler.rb
CHANGED
@@ -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 <<
|
14
|
+
units << Lexer.lexify(tokenizer.next_token)
|
11
15
|
end
|
12
16
|
|
13
17
|
syntaxer = Syntaxer.new(units.dup)
|
14
|
-
|
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
|
|
data/lib/electr/exceptions.rb
CHANGED
data/lib/electr/parser.rb
CHANGED
data/lib/electr/parser/lexer.rb
CHANGED
@@ -1,50 +1,27 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|