electr 0.0.5 → 0.0.6

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