electr 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 86d47b60ef5f815792290a73257593f50f6819a1
4
- data.tar.gz: 638a91fb8edea30cf2c782a972c21591efea09f8
3
+ metadata.gz: 9db135b700b2ed083ff6ffe4e1d9e70be31f831a
4
+ data.tar.gz: f3bd4535fbf09f65ca28e2a621ed2c292fd1e0a1
5
5
  SHA512:
6
- metadata.gz: abdf7b5b3f6282134294825cfeeb277d5d2116573f90aa50e1fb4e836ca9b9d99dcee00fccee571225631f83a3e86e856f70c9a091151edcd304355c8b391354
7
- data.tar.gz: 745e6f0654ab7fea487954ebd0f7443ac18471f89a084f7dca7860c2d332c6276b8d794899c96f951fe9b5b3f0fc3ebdedb077d45c681d43b044cd90ff2c07e8
6
+ metadata.gz: 7c4a03f6968baed4cd6759d33648fa34b4a141bdd04e47fc74af87e4bce658595be2e3cf6f753cde03a98c08d4807d5f05de3e219a7edc5add366bceaf8d3ae1
7
+ data.tar.gz: e4fadd225f4ccc5f7e1f16f590f702915e0df75f05aecbd1f5c295a08c86164f822af98ae3b382c7b076d8c83eb7b0e400f940b3d71e0a529681b961999bcc3f
@@ -4,6 +4,20 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
 
6
6
  ## [Unreleased] - unreleased
7
+ ### Added
8
+ - Variables! One can now doing things like `R1 = 100`, `R1 + 100`,
9
+ `R1 R2` and so forth. The name of a variable must be an uppercase letter
10
+ followed by 1 (or more) digit.
11
+ Assignment can be chained, that is you can write `R1 = R2 = 100`.
12
+ - A new section «What to do?» in the readme, as a TODO list.
13
+
14
+ ### Modified
15
+ - Reworded the help screen
16
+ - Internal result of an evaluation is now a type
17
+
18
+ ### Removed
19
+ - Removed the folder `documentation` and its content as it was useless.
20
+
7
21
 
8
22
  ## [0.0.5] - 2015-09-28
9
23
  ### Added
data/README.md CHANGED
@@ -23,12 +23,6 @@ proof of concept. And then, maybe, a more portable version in C.
23
23
  Tell me what you think on [twitter](https://twitter.com/lkdjiin) or better,
24
24
  open an issue here on Github. In any cases feel free to start a discussion.
25
25
 
26
- The current version is an early one:
27
-
28
- 1. Don't expect too much
29
- 2. Expect a lot of bugs
30
- 3. Please be kind enough to report any bugs here in Github
31
-
32
26
  ## Installation
33
27
 
34
28
  Install it with:
@@ -54,7 +48,7 @@ switch:
54
48
  120
55
49
 
56
50
  To display the AST instead of doing the computation, use the `--ast` switch
57
- (*this is intended only for the developers*):
51
+ (*this is normally intended only for the developers*):
58
52
 
59
53
  $ electr --ast -e "3V / 25mA"
60
54
  ast
@@ -63,18 +57,15 @@ To display the AST instead of doing the computation, use the `--ast` switch
63
57
  value ::= 3V
64
58
  value ::= 25mA
65
59
 
66
- ### Resistors in serie
60
+ ### Some simple computations
67
61
 
68
- Start simple to illustrate the addition. We have a 10,000 Ohm resistor (10k) and
69
- a 200 Ohm resistor (200R):
62
+ We have a 10,000 Ohm resistor (10k) and a 200 Ohm resistor (200R):
70
63
 
71
64
  E> 10k + 200R
72
65
  10200
73
66
 
74
67
  *Should it be `K`, `k`, `kΩ` or the three is still open to debate.*
75
68
 
76
- ### Ohm's law
77
-
78
69
  Divide Volts (V) by milliamps (mA) to get some Ohms:
79
70
 
80
71
  E> 3V / 25mA
@@ -85,6 +76,8 @@ There is no symbol for the multiplication. Simply put values side by side:
85
76
  E> 1mA 3k
86
77
  3
87
78
 
79
+ Actually you *can* use the `*` for the multiplication if you really want to ;)
80
+
88
81
  ### Frequency of an oscillator
89
82
 
90
83
  A little bit more complex is the computation of a frequency for an oscillator.
@@ -95,6 +88,28 @@ is in Hertz.
95
88
  E> 1 / (2 pi 0.5uF sqrt(11k 22k))
96
89
  20.4617344581
97
90
 
91
+ ### Variables
92
+
93
+ A variable name must be an uppercase letter followed by a digit or more:
94
+
95
+ R1 = 22k
96
+
97
+ The same formula as above can be written using variables:
98
+
99
+ E> C1 = 0.5uF
100
+ E> R1 = 11k
101
+ E> R2 = 22k
102
+ E> 1 / (2 pi C1 sqrt(R1 R2))
103
+ 20.4617344581
104
+
105
+ Assignments can be chained:
106
+
107
+ E> R1 = R2 = R3 = 100
108
+ E> R3
109
+ 100
110
+ E> R1 + R2 + R3
111
+ 300
112
+
98
113
  ### Units, Prefixes and Abbreviations
99
114
 
100
115
  Electr knows the following units:
@@ -133,26 +148,11 @@ n | nano farad
133
148
  p | pico farad
134
149
  k K | kilo ohm
135
150
 
136
- ### What is missing?
137
-
138
- Electr is at a very early stage and it miss a lot of (basic) things!
139
- You can expect that the following features will be implemented in the
140
- next couple of days/weeks:
141
-
142
- - [x] Negative numbers
143
- - [x] Floating point number without a leading zero (ie `.678`)
144
- - [x] 10_000 or 10,000 will be the same as 10000
145
- - [x] More builtin functions (sin, cos, tan)
146
- - [x] Exponent
147
- - [x] Readline lib in the REPL for a better user experience
148
- - [x] All units and prefix used in electronic
149
- - [x] `*` for the multiplication if one want to
150
- - [x] √ for an alternative to square root
151
- - [ ] Shortcuts for function's names (ie sq and sqr for sqrt)
152
-
153
151
  ## What's next?
154
152
 
155
- Maybe Electr could infer the resulting unit:
153
+ Here are some features I would like to implement soon.
154
+
155
+ Electr could infer the resulting unit:
156
156
 
157
157
  E> 10k + 200R
158
158
  10.2kΩ
@@ -161,8 +161,8 @@ Maybe Electr could infer the resulting unit:
161
161
  E> 3V / 1mA
162
162
  3kΩ
163
163
 
164
- One are less prone to typing errors (less parenthesis) if one enter a complex
165
- expression on two lines:
164
+ One are less prone to typing errors (less parenthesis) if one could enter a
165
+ complex expression on two lines:
166
166
 
167
167
  E> 1 /
168
168
  E> 2 pi 0.5uF sq(11K 22K)
@@ -181,14 +181,6 @@ ask us for the components values, then gives us the result.
181
181
  C2=?> 470pF
182
182
  7.04kHz
183
183
 
184
- Or acts like a tiny programming language.
185
-
186
- E> R1 = 33k
187
- E> C1 = 1000pF
188
- E> C2 = 470pF
189
- E> 1 / (2 pi R1 sq(C1 C2))
190
- 7.04kHz
191
-
192
184
  Why not having custom functions?
193
185
 
194
186
  E> func = { 1 / (2 pi R1 sq(C1 C2)) }
@@ -197,18 +189,8 @@ Why not having custom functions?
197
189
 
198
190
  *The above syntax is just one possibility amongst a lot of others.*
199
191
 
200
- ## Contributing
201
-
202
- 1. Fork it ( https://github.com/lkdjiin/electr/fork )
203
- 2. **PLEASE Create your feature branch** (`git checkout -b my-new-feature`)
204
- 3. Commit your changes (`git commit -am 'Add some feature'`)
205
- 4. Push to the branch (`git push origin my-new-feature`)
206
- 5. Create a new Pull Request
207
-
208
192
  ## Alternatives
209
193
 
210
- Some people point me to two existing softwares:
211
-
212
194
  - [Frink](https://futureboy.us/frinkdocs/)
213
195
  - [GNU Units](https://en.wikipedia.org/wiki/GNU_Units)
214
196
 
@@ -220,3 +202,48 @@ furlongs ;)
220
202
  ## License
221
203
 
222
204
  [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
205
+
206
+ ## Contributing
207
+
208
+ ### Good
209
+
210
+ 1. Fork it ( https://github.com/lkdjiin/electr/fork )
211
+ 2. **PLEASE Create your feature branch** (`git checkout -b my-new-feature`)
212
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
213
+ 4. Push to the branch (`git push origin my-new-feature`)
214
+ 5. Create a new Pull Request
215
+
216
+ ### Better
217
+
218
+ Before anything else, please open an issue to say what you are intended to do.
219
+
220
+ ### What to do?
221
+
222
+ You want to help but you don't know where to start? Here are some ideas.
223
+ The list is more or less sorted. But feel free to pick any item you want. If
224
+ an item is marked as *pending* and you want to work on it, please first contact
225
+ me.
226
+
227
+ - [ ] Display a nice message on any error (not an abrupt fail as it is for now)
228
+ - [ ] Add interactivity (see «Beyond the calculator» section in this readme)
229
+ - [ ] Reply with a unit (*pending*)
230
+ - [ ] Write 10mA as well as 10 mA
231
+ - [ ] Use `;` to separate expressions (useful for command line option -e)
232
+ - [ ] Underscore value. The `_` refers to the last computed value.
233
+ - [ ] Add autocompletion with the readline library. For example see http://bogojoker.com/readline/
234
+ - [ ] `foo()` should display an error message like "Error: Undefined function 'foo'"
235
+ - [ ] BUG Unknown function `foo(2)` evaluate to 2. Instead it must report an error
236
+ - [ ] A command to quit, instead of Ctrl-C. Should it be `quit`, `exit`, `quit()`, `exit()`?
237
+ - [ ] Is there any benefits to have the @name attribute of an AST node as a symbol instead of a string?
238
+ - [ ] Be sure that the AST can be unparsed (prove it, do it)
239
+ - [ ] Simplify the sequences of multiplication in the AST
240
+ - [ ] Quit gracefuly with Ctrl+d
241
+
242
+ Here are some ideas to experiment:
243
+
244
+ - exponent shortcuts `^^` for `^ 2`, `^^^` for `^ 3`, etc
245
+ - Give multiplication an higher precedence than division: it can remove the need for parenthesis very often.
246
+ - Compute Electr code from a source file
247
+ - Give a try to travelling ruby (http://phusion.github.io/traveling-ruby/). It
248
+ could be very handy for people without knowledge of Ruby.
249
+ - Colors in the console!
data/bin/electr CHANGED
@@ -16,11 +16,7 @@ if opt[:expression]
16
16
  temp = Electr::Compiler.compile_to_pn(opt[:expression])
17
17
  evaluator = Electr::Evaluator.new
18
18
  temp = evaluator.evaluate_pn(temp)
19
- if temp == temp.truncate
20
- puts temp.truncate
21
- else
22
- puts temp.round(10)
23
- end
19
+ Electr::Printer.run(temp)
24
20
  end
25
21
  else
26
22
  if opt[:ast]
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Electr::VERSION
9
9
  spec.authors = ["lkdjiin"]
10
10
  spec.email = ["xavier.nayrac@gmail.com"]
11
- spec.summary = %q{Interactive language for electronic formulas}
11
+ spec.summary = %q{Interactive tiny language for electronic formulas}
12
12
  spec.homepage = "https://github.com/lkdjiin/electr"
13
13
  spec.license = "MIT"
14
14
 
@@ -25,15 +25,16 @@ module Electr
25
25
  }
26
26
 
27
27
  PRECEDENCE = {
28
- '()' => {assoc: 'L', val: 100},
29
- ')' => {assoc: 'L', val: 100},
30
- '(' => {assoc: 'L', val: 100},
31
- '^' => {assoc: 'L', val: 90},
32
- UNARY_MINUS_INTERNAL_SYMBOL => {assoc: 'R', val: 80},
33
- '*' => {assoc: 'L', val: 10},
34
- '/' => {assoc: 'L', val: 10},
35
- '-' => {assoc: 'L', val: 1},
36
- '+' => {assoc: 'L', val: 1},
28
+ '()' => {assoc: 'L', val: 1000},
29
+ ')' => {assoc: 'L', val: 1000},
30
+ '(' => {assoc: 'L', val: 1000},
31
+ '^' => {assoc: 'L', val: 900},
32
+ UNARY_MINUS_INTERNAL_SYMBOL => {assoc: 'R', val: 800},
33
+ '*' => {assoc: 'L', val: 100},
34
+ '/' => {assoc: 'L', val: 100},
35
+ '-' => {assoc: 'L', val: 90},
36
+ '+' => {assoc: 'L', val: 90},
37
+ '=' => {assoc: 'R', val: 10},
37
38
  }
38
39
 
39
40
  UNITS = %w( A Hz W C V F R Ω S ℧ H )
@@ -7,3 +7,4 @@ require "electr/ast/operator_ast"
7
7
  require "electr/ast/func_ast"
8
8
  require "electr/ast/func_name_ast"
9
9
  require "electr/ast/func_args_ast"
10
+ require "electr/ast/variable_ast"
@@ -63,6 +63,7 @@ module Electr
63
63
 
64
64
  # Element of a prefix notation.
65
65
  # TODO Pn is not a good name!
66
+ # TODO Move in its own file!
66
67
  class Pn
67
68
 
68
69
  def initialize(value, name)
@@ -0,0 +1,13 @@
1
+ module Electr
2
+
3
+ # A node in the AST to represent a variable.
4
+ class VariableAST < AST
5
+
6
+ def initialize(value)
7
+ super("variable")
8
+ @value = value
9
+ end
10
+
11
+ end
12
+ end
13
+
@@ -13,7 +13,6 @@ module Electr
13
13
  while tokenizer.has_more_token?
14
14
  units << Lexer.lexify(tokenizer.next_token)
15
15
  end
16
-
17
16
  syntaxer = Syntaxer.new(units.dup)
18
17
  syntaxer.run
19
18
  end
@@ -4,4 +4,10 @@ module Electr
4
4
  class SyntaxError < StandardError
5
5
  end
6
6
 
7
+ class UnboundVariableError < StandardError
8
+ end
9
+
10
+ class NilEvaluationError < StandardError
11
+ end
12
+
7
13
  end
@@ -43,10 +43,10 @@ module Electr
43
43
  end
44
44
 
45
45
  def parse(opts)
46
- opts.on(nil, '--ast', 'Display AST and quit') do
46
+ opts.on(nil, '--ast', 'Display AST instead of actual computation') do
47
47
  @options[:ast] = true
48
48
  end
49
- opts.on('-e', '--expression EXP', 'Compute EXP and quit') do |arg|
49
+ opts.on('-e', '--expression EXP', 'Compute EXP and exit') do |arg|
50
50
  @options[:expression] = arg
51
51
  end
52
52
  opts.on('-v', '--version', 'Print version number and exit') do
@@ -16,8 +16,10 @@ module Electr
16
16
  when ->(x) { x.operator? } then LexicalUnit.operator(token)
17
17
  when ->(x) { x.constant? } then LexicalUnit.constant(token)
18
18
  when ->(x) { x.value? } then LexicalUnit.value(token)
19
+ when ->(x) { x.variable? } then LexicalUnit.variable(token)
19
20
  when ->(x) { x == '(' } then LexicalUnit.open_parenthesis
20
21
  when ->(x) { x == ')' } then LexicalUnit.closed_parenthesis
22
+ when ->(x) { x == '=' } then LexicalUnit.assign
21
23
  when ->(x) { SYMBOL_TABLE[x] == 'f' } then LexicalUnit.fname(token)
22
24
  else LexicalUnit.name(token)
23
25
  end
@@ -23,8 +23,10 @@ module Electr
23
23
  def self.name(value) ; new(:name, value) ; end
24
24
  def self.fname(value) ; new(:fname, value) ; end
25
25
  def self.fcall(value) ; new(:fcall, value) ; end
26
+ def self.variable(value) ; new(:variable, value) ; end
26
27
  def self.open_parenthesis ; new(:open_parenthesis, "(") ; end
27
28
  def self.closed_parenthesis ; new(:closed_parenthesis, ")") ; end
29
+ def self.assign ; new(:assign, "=") ; end
28
30
 
29
31
  def ==(other)
30
32
  @type == other.type && @value == other.value
@@ -66,5 +68,12 @@ module Electr
66
68
  @type == :open_parenthesis
67
69
  end
68
70
 
71
+ def assign?
72
+ @type == :assign
73
+ end
74
+
75
+ def variable?
76
+ @type == :variable
77
+ end
69
78
  end
70
79
  end
@@ -27,6 +27,10 @@ module Electr
27
27
  dig_series(@ast_node)
28
28
  else
29
29
  unit = @series.shift
30
+ # If we want to handle = differently than other operators, we
31
+ # can do something like the following:
32
+ # node_class = unit.assign? ? AssignAST : OperatorAST
33
+ # node = node_class.new(unit.value)
30
34
  node = OperatorAST.new(unit.value)
31
35
  dig_series(node) while @series.size > 0
32
36
  @ast_node.add_child(node)
@@ -37,7 +41,9 @@ module Electr
37
41
 
38
42
  def number?
39
43
  first = @series.first
40
- first.numeric? || first.constant? || first.value? || first.fname?
44
+ # Is it make sense to say all this things are «numbers»?
45
+ first.numeric? || first.constant? || first.value? || first.fname? ||
46
+ first.variable?
41
47
  end
42
48
 
43
49
  def first_unit_fname?
@@ -46,12 +52,16 @@ module Electr
46
52
 
47
53
  def dig_series(node)
48
54
  while unit = @series.shift
55
+
49
56
  if unit && unit.numeric? && there_is_room?(node)
50
57
  node.add_child(NumericAST.new(unit.value))
58
+
51
59
  elsif unit && unit.constant? && there_is_room?(node)
52
60
  node.add_child(ConstantAST.new(unit.value))
61
+
53
62
  elsif unit && unit.value? && there_is_room?(node)
54
63
  node.add_child(ValueAST.new(unit.value))
64
+
55
65
  elsif unit && unit.fname? && there_is_room?(node)
56
66
  func = FuncAST.new
57
67
  func_name = FuncNameAST.new(unit.value)
@@ -60,10 +70,15 @@ module Electr
60
70
  func.add_child(func_name)
61
71
  func.add_child(func_args)
62
72
  node.add_child(func)
63
- elsif unit && unit.operator?
73
+
74
+ elsif unit && (unit.operator? || unit.assign?)
64
75
  new_node = OperatorAST.new(unit.value)
65
76
  dig_series(new_node)
66
77
  node.add_child(new_node)
78
+
79
+ elsif unit && unit.variable?
80
+ node.add_child(VariableAST.new(unit.value))
81
+
67
82
  else
68
83
  @series.unshift(unit)
69
84
  break
@@ -4,6 +4,8 @@ module Electr
4
4
  #
5
5
  # It produces prefix notation instead of the most common reverse
6
6
  # polish notation to ease producing the AST.
7
+ #
8
+ # For a starter, see https://en.wikipedia.org/wiki/Shunting-yard_algorithm
7
9
  class Sya
8
10
 
9
11
  # Creates a new Sya.
@@ -19,10 +21,10 @@ module Electr
19
21
 
20
22
  # Public: Run the Shunting Yard Algorithm.
21
23
  #
22
- # Returns Array of LexicalUnit ordered with prefix notation.
24
+ # Returns Array of LexicalUnit ordered in prefix notation.
23
25
  def run
24
26
  while unit = @units.pop
25
- if unit.number?
27
+ if unit.number? || unit.variable?
26
28
  @output.push(unit)
27
29
  elsif unit.fname?
28
30
  @operator.push(unit)
@@ -33,19 +35,17 @@ module Electr
33
35
  @operator.last.type != :closed_parenthesis
34
36
  @output.push(@operator.pop)
35
37
  end
36
- rparen = @operator.pop
38
+ @operator.pop # Pop the right parenthesis.
37
39
  if @operator.last && @operator.last.type == :fname
38
40
  @output.push(@operator.pop)
39
41
  end
40
42
  # If the stack runs out without finding a right parenthesis,
41
43
  # then there are mismatched parentheses.
42
- elsif unit.operator?
43
- while @operator.size > 0 &&
44
- (@operator.last.operator? || @operator.last.fname?) &&
45
- (precedence(unit) < precedence(@operator.last))
46
- @output.push(@operator.pop)
47
- end
44
+ elsif unit.operator? || unit.assign?
45
+
46
+ @output.push(@operator.pop) while rules_for_operator_are_met(unit)
48
47
  @operator.push(unit)
48
+
49
49
  end
50
50
  end
51
51
 
@@ -56,6 +56,39 @@ module Electr
56
56
 
57
57
  private
58
58
 
59
+ def rules_for_operator_are_met(unit)
60
+ size_rule && (operator_rule && associative_rule(unit))
61
+ end
62
+
63
+ def size_rule
64
+ @operator.size > 0
65
+ end
66
+
67
+ def operator_rule
68
+ test = @operator.last
69
+ test.operator? || test.fname? || test.assign?
70
+ end
71
+
72
+ def associative_rule(test)
73
+ left_associative_rule(test) || right_associative_rule(test)
74
+ end
75
+
76
+ def left_associative_rule(test)
77
+ left_associative(test) && left_assoc_precedence_rule(test)
78
+ end
79
+
80
+ def left_assoc_precedence_rule(test)
81
+ precedence(test) < precedence(@operator.last)
82
+ end
83
+
84
+ def right_associative_rule(test)
85
+ right_associative(test) && right_assoc_precedence_rule(test)
86
+ end
87
+
88
+ def right_assoc_precedence_rule(test)
89
+ precedence(test) <= precedence(@operator.last)
90
+ end
91
+
59
92
  # Look up the precedence of an operator.
60
93
  #
61
94
  # operator - LexicalUnit operator.
@@ -69,6 +102,32 @@ module Electr
69
102
  end
70
103
  end
71
104
 
105
+ # Check the left associativity of an operator.
106
+ #
107
+ # operator - LexicalUnit operator.
108
+ #
109
+ # Returns true if the operator is left associative, false otherwise.
110
+ def left_associative(operator)
111
+ if operator.fname?
112
+ PRECEDENCE['()'][:assoc] == 'L'
113
+ else
114
+ PRECEDENCE[operator.value][:assoc] == 'L'
115
+ end
116
+ end
117
+
118
+ # Check the right associativity of an operator.
119
+ #
120
+ # operator - LexicalUnit operator.
121
+ #
122
+ # Returns true if the operator is right associative, false otherwise.
123
+ def right_associative(operator)
124
+ if operator.fname?
125
+ PRECEDENCE['()'][:assoc] == 'R'
126
+ else
127
+ PRECEDENCE[operator.value][:assoc] == 'R'
128
+ end
129
+ end
130
+
72
131
  # Insert the multiplication operator (`*`) where it is needed inside
73
132
  # the @units member. This is because in Electr the `*` is implicit.
74
133
  #
@@ -90,12 +149,12 @@ module Electr
90
149
  end
91
150
 
92
151
  def maybe_insertion_needed?(unit)
93
- unit_ahead && unit.number?
152
+ unit_ahead && (unit.number? || unit.variable?)
94
153
  end
95
154
 
96
155
  def insertion_needed?
97
156
  unit_ahead.number? || unit_ahead.open_parenthesis? ||
98
- unit_ahead.fname? || unit_ahead.unary_minus?
157
+ unit_ahead.fname? || unit_ahead.unary_minus? || unit_ahead.variable?
99
158
  end
100
159
 
101
160
  def unit_ahead