skeem 0.0.13 → 0.0.14

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: ba107706c9483fb97e9a628b621ca7932c00f16d
4
- data.tar.gz: 14ddf9ec1cebab58ae123ffbef3786f306824cb3
3
+ metadata.gz: 4738bc3d483df0325cce143200b02e5c8cac7afe
4
+ data.tar.gz: 8f4560eaa88c6e754bd423bdf3c1f62a35063b75
5
5
  SHA512:
6
- metadata.gz: bfb3e078734b6e7ed68161f095c5c5c3287afd8c4fd1583b7a0e7f1d79c112aead08dd4e95d804ee68d7bfc7c1da73841252ef8eb8acdf88700d929682a2dc35
7
- data.tar.gz: 5fd3ed354914cb9af65fd0e7836d3a474c45f980fcbf4a0535162d08d8944f14e72f8168e723ea024f46b8861292d47ae0e53ba419250f38e4a2a5fd1df2aaf3
6
+ metadata.gz: 99e55d28f19fe7d4cb9a2d18a066a12fa43ffc2dfe59b1e5ac3e2795202c67b122e0fce2a9862871accb936eb58320870588c7eb622b4ee714c3e7bcb22eb5bc
7
+ data.tar.gz: 12e216d00ceb2e16ca2d27711bc47449e0ff49438ad9b42d52ae677368624cf15c3c80babef8a70aa740e0cdee8b619697aae871fb2962767e87af2f633068c0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.0.14] - 2018-09-23
2
+ Added `lambda` (anonymous function). This is an initial implementation that doesn't support recursion yet.
3
+
4
+ ### Added
5
+ - Class `SkmLambda` for representing a specific definition.
6
+
7
+ ### Changed
8
+ - Class `Environment`. Now supports the nesting of environments. If an environment doesn't find a variable, then it forwards the serach to the outer environment.
9
+ - File `grammar.rb` Added syntax rules for lambda expression.
10
+ - Class `Runtime` added methods `nest` and `unnest` that adds or removes an nested environment.
11
+ - Class `SkmBuilder`. Added method to implement the semantic action for `lambda` expressions.
12
+ - Class `Tokenizer` Added keyword `lambda`
13
+ - File `README.md` Added demo snippet with example of lambda expression.
14
+ - Files `*_spec.rb` Added more tests. Skeem passes the 100 'RSpec examples' mark.
15
+
1
16
  ## [0.0.13] - 2018-09-18
2
17
  Added primitive `if` (conditional form)
3
18
 
data/README.md CHANGED
@@ -27,11 +27,13 @@ Or install it yourself as:
27
27
  The __Skeem__ project has just started.
28
28
  At this stage, the gem consists of a bare-bones interpreter.
29
29
 
30
+ ### Example 1 (Variable definition)
31
+
30
32
  ```ruby
31
33
  require 'skeem'
32
34
 
33
35
  schemer = Skeem::Interpreter.new
34
-
36
+
35
37
  scheme_code =<<-SKEEM
36
38
  ; This heredoc consists of Scheme code...
37
39
  ; Let's define a Scheme variable
@@ -52,6 +54,32 @@ At this stage, the gem consists of a bare-bones interpreter.
52
54
  puts result.value # => 1764
53
55
  ```
54
56
 
57
+ ### Example 2 (Defining a function)
58
+ Remark: Skeem 0.0.14 doesn't support recursive functions yet.
59
+
60
+ ```ruby
61
+ require 'skeem'
62
+
63
+ schemer = Skeem::Interpreter.new
64
+
65
+ scheme_code =<<-SKEEM
66
+ ; Let's implement the 'min' function
67
+ (define min (lambda(x y) (if (< x y) x y)))
68
+
69
+ ; What is the minimum of 2 and 3?
70
+ (min 2 3)
71
+ SKEEM
72
+
73
+ # Ask Ruby to execute Scheme code
74
+ result = schemer.run(scheme_code)
75
+ puts result.value # => 2
76
+
77
+ # Let's retry with other values
78
+ scheme_code = '(min 42 3)'
79
+ result = schemer.run(scheme_code)
80
+ puts result.value # => 3
81
+ ```
82
+
55
83
  Roadmap:
56
84
  - Extend language support
57
85
  - Implement REPL
@@ -1,19 +1,51 @@
1
- require 'forwardable'
2
-
3
1
  module Skeem
4
2
  class Environment
5
- extend Forwardable
6
- def_delegator :@bindings, :empty?
7
-
3
+ attr_reader :outer
4
+
8
5
  attr_reader(:bindings)
9
6
 
10
- def initialize()
7
+ def initialize(outerEnv = nil)
11
8
  @bindings = {}
9
+ @outer = outerEnv
12
10
  end
13
11
 
14
12
  def define(anIdentifier, anExpression)
15
13
  raise StandardError, anIdentifier unless anIdentifier.kind_of?(String)
16
14
  @bindings[anIdentifier] = anExpression
17
15
  end
16
+
17
+ def fetch(anIdentifier)
18
+ found = bindings[anIdentifier]
19
+ if found.nil? && outer
20
+ found = outer.fetch(anIdentifier)
21
+ end
22
+
23
+ found
24
+ end
25
+
26
+ def empty?
27
+ my_result = bindings.empty?
28
+ if my_result && outer
29
+ my_result = outer.empty?
30
+ end
31
+
32
+ my_result
33
+ end
34
+
35
+ def size
36
+ my_result = bindings.size
37
+ my_result += outer.size if outer
38
+
39
+ my_result
40
+ end
41
+
42
+ def include?(anIdentifier)
43
+ my_result = bindings.include?(anIdentifier)
44
+ if my_result == false && outer
45
+ my_result = outer.include?(anIdentifier)
46
+ end
47
+
48
+ my_result
49
+ end
18
50
  end # class
19
51
  end # module
data/lib/skeem/grammar.rb CHANGED
@@ -11,14 +11,14 @@ module Skeem
11
11
  # Delimiters, separators...
12
12
  # add_terminals('APOSTROPHE', 'BACKQUOTE')
13
13
  add_terminals('LPAREN', 'RPAREN')
14
- # add_terminals('PERIOD')
14
+ add_terminals('PERIOD')
15
15
 
16
16
  # Literal values...
17
17
  add_terminals('BOOLEAN', 'INTEGER', 'REAL')
18
18
  add_terminals('STRING_LIT', 'IDENTIFIER')
19
19
 
20
20
  # Keywords...
21
- add_terminals('DEFINE', 'IF')
21
+ add_terminals('DEFINE', 'IF', 'LAMBDA')
22
22
 
23
23
  rule('program' => 'cmd_or_def_plus').as 'main'
24
24
  rule('cmd_or_def_plus' => 'cmd_or_def_plus cmd_or_def').as 'multiple_cmd_def'
@@ -30,6 +30,7 @@ module Skeem
30
30
  rule('expression' => 'IDENTIFIER').as 'variable_reference'
31
31
  rule 'expression' => 'literal'
32
32
  rule 'expression' => 'procedure_call'
33
+ rule 'expression' => 'lambda_expression'
33
34
  rule 'expression' => 'conditional'
34
35
  rule 'literal' => 'self-evaluating'
35
36
  rule 'self-evaluating' => 'BOOLEAN'
@@ -41,6 +42,20 @@ module Skeem
41
42
  rule('operand_plus' => 'operand').as 'last_operand'
42
43
  rule 'operator' => 'expression'
43
44
  rule 'operand' => 'expression'
45
+ rule('lambda_expression' => 'LPAREN LAMBDA formals body RPAREN').as 'lambda_expression'
46
+ rule('formals' => 'LPAREN identifier_star RPAREN').as 'identifiers_as_formals'
47
+ rule 'formals' => 'IDENTIFIER'
48
+ rule 'formals' => 'LPAREN identifier_plus PERIOD IDENTIFIER RPAREN'
49
+ rule('identifier_star' => 'identifier_star IDENTIFIER').as 'identifier_star'
50
+ rule('identifier_star' => []).as 'no_identifier_yet'
51
+ rule 'identifier_plus' => 'identifier_plus IDENTIFIER'
52
+ rule 'identifier_plus' => 'IDENTIFIER'
53
+ rule('body' => 'definition_star sequence').as 'body'
54
+ rule 'definition_star' => 'definition_star definition'
55
+ rule 'definition_star' => []
56
+ rule('sequence' => 'command_star expression').as 'sequence'
57
+ rule('command_star' => 'command_star command').as 'multiple_commands'
58
+ rule('command_star' => []).as 'no_command_yet'
44
59
  rule('conditional' => 'LPAREN IF test consequent alternate RPAREN').as 'conditional'
45
60
  rule 'test' => 'expression'
46
61
  rule 'consequent' => 'expression'
@@ -5,11 +5,12 @@ module Skeem
5
5
  attr_reader(:identifier)
6
6
  attr_reader(:code)
7
7
 
8
- def initialize(anId, aLambda)
8
+ def initialize(anId, aRubyLambda)
9
9
  @identifier = anId.kind_of?(String) ? SkmIdentifier.create(anId) : anId
10
- @code = aLambda
10
+ @code = aRubyLambda
11
11
  end
12
12
 
13
+ # Arguments are positional in a primitive procedure.
13
14
  def call(aRuntime, aProcedureCall)
14
15
  args = aProcedureCall.operands
15
16
  return @code.call(aRuntime, args)
data/lib/skeem/runtime.rb CHANGED
@@ -7,12 +7,23 @@ module Skeem
7
7
  end
8
8
 
9
9
  def include?(anIdentifier)
10
- environment.bindings.include?(normalize_key(anIdentifier))
10
+ environment.include?(normalize_key(anIdentifier))
11
11
  end
12
12
 
13
13
  def define(aKey, anEntry)
14
14
  environment.define(normalize_key(aKey), anEntry)
15
15
  end
16
+
17
+ def nest()
18
+ nested = Environment.new(environment)
19
+ @environment = nested
20
+ end
21
+
22
+ def unnest()
23
+ raise StandardError, 'Cannot unnest environment' unless environment.outer
24
+ environment.bindings.clear
25
+ @environment = environment.outer
26
+ end
16
27
 
17
28
  private
18
29
 
@@ -82,10 +82,53 @@ module Skeem
82
82
  [theChildren.last]
83
83
  end
84
84
 
85
+ # rule('lambda_expression' => 'LPAREN LAMBDA formals body RPAREN').as 'lambda_expression'
86
+ def reduce_lambda_expression(_production, _range, _tokens, theChildren)
87
+ lmbd = SkmLambda.new(_range, theChildren[2], theChildren[3])
88
+ # puts lmbd.inspect
89
+ lmbd
90
+ end
91
+
92
+ # rule('formals' => 'LPAREN identifier_star RPAREN').as 'identifiers_as_formals'
93
+ def reduce_identifiers_as_formals(_production, _range, _tokens, theChildren)
94
+ theChildren[1]
95
+ end
96
+
97
+ # rule('identifier_star' => 'identifier_star IDENTIFIER').as 'identifier_star'
98
+ def reduce_identifier_star(_production, _range, _tokens, theChildren)
99
+ theChildren[0] << theChildren[1]
100
+ end
101
+
102
+ # rule('identifier_star' => []).as 'no_identifier_yet'
103
+ def reduce_no_identifier_yet(_production, _range, _tokens, theChildren)
104
+ []
105
+ end
106
+
107
+ # rule('body' => 'definition_star sequence').as 'body'
108
+ def reduce_body(_production, _range, _tokens, theChildren)
109
+ definitions = theChildren[0].nil? ? [] : theChildren[0]
110
+ { defs: definitions, sequence: theChildren[1] }
111
+ end
112
+
113
+ # rule('sequence' => 'command_star expression').as 'sequence'
114
+ def reduce_sequence(_production, _range, _tokens, theChildren)
115
+ SkmList.new(theChildren[0] << theChildren[1])
116
+ end
117
+
118
+ # rule('command_star' => 'command_star command').as 'multiple_commands'
119
+ def reduce_multiple_commands(_production, _range, _tokens, theChildren)
120
+ theChildren[0] << theChildren[1]
121
+ end
122
+
123
+ # rule('command_star' => []).as 'no_command_yet'
124
+ def reduce_no_command_yet(_production, _range, _tokens, theChildren)
125
+ []
126
+ end
127
+
85
128
  # rule('conditional' => 'LPAREN IF test consequent alternate RPAREN').as 'conditional'
86
129
  def reduce_conditional(_production, aRange, _tokens, theChildren)
87
130
  SkmCondition.new(aRange, theChildren[2], theChildren[3], theChildren[4])
88
- end
131
+ end
89
132
  end # class
90
133
  end # module
91
134
  # End of file
@@ -36,18 +36,18 @@ module Skeem
36
36
  def integer?
37
37
  false
38
38
  end
39
-
39
+
40
40
  def boolean?
41
41
  false
42
42
  end
43
43
 
44
44
  def string?
45
45
  false
46
- end
46
+ end
47
47
 
48
48
  def symbol?
49
49
  false
50
- end
50
+ end
51
51
 
52
52
  # Abstract method.
53
53
  # Part of the 'visitee' role in Visitor design pattern.
@@ -55,6 +55,20 @@ module Skeem
55
55
  def accept(_visitor)
56
56
  raise NotImplementedError
57
57
  end
58
+
59
+ def inspect()
60
+ raise NotImplementedError
61
+ end
62
+
63
+ protected
64
+
65
+ def inspect_prefix
66
+ "<#{self.class.name}: "
67
+ end
68
+
69
+ def inspect_suffix
70
+ '>'
71
+ end
58
72
  end # struct
59
73
 
60
74
  # Abstract class. Root of class hierarchy needed for Interpreter
@@ -75,11 +89,6 @@ module Skeem
75
89
  return lightweight
76
90
  end
77
91
 
78
- # This method can be overriden
79
- def init_value(aValue)
80
- @value = aValue
81
- end
82
-
83
92
  def symbol()
84
93
  token.terminal
85
94
  end
@@ -88,6 +97,10 @@ module Skeem
88
97
  return self
89
98
  end
90
99
 
100
+ def inspect()
101
+ inspect_prefix + value.to_s + inspect_suffix
102
+ end
103
+
91
104
  def done!()
92
105
  # Do nothing
93
106
  end
@@ -97,12 +110,17 @@ module Skeem
97
110
  def accept(aVisitor)
98
111
  aVisitor.visit_terminal(self)
99
112
  end
113
+
114
+ # This method can be overriden
115
+ def init_value(aValue)
116
+ @value = aValue
117
+ end
100
118
  end # class
101
119
 
102
120
  class SkmBoolean < SkmTerminal
103
121
  def boolean?
104
122
  true
105
- end
123
+ end
106
124
  end # class
107
125
 
108
126
  class SkmNumber < SkmTerminal
@@ -128,10 +146,10 @@ module Skeem
128
146
  def init_value(aValue)
129
147
  super(aValue.dup)
130
148
  end
131
-
149
+
132
150
  def string?
133
151
  true
134
- end
152
+ end
135
153
  end # class
136
154
 
137
155
  class SkmIdentifier < SkmTerminal
@@ -139,10 +157,10 @@ module Skeem
139
157
  def init_value(aValue)
140
158
  super(aValue.dup)
141
159
  end
142
-
160
+
143
161
  def symbol?
144
162
  true
145
- end
163
+ end
146
164
  end # class
147
165
 
148
166
  class SkmReserved < SkmIdentifier
@@ -153,7 +171,7 @@ module Skeem
153
171
  attr_accessor(:members)
154
172
  extend Forwardable
155
173
 
156
- def_delegators :@members, :first, :length, :empty?
174
+ def_delegators :@members, :each, :first, :length, :empty?
157
175
 
158
176
  def initialize(theMembers)
159
177
  super(nil)
@@ -202,8 +220,17 @@ module Skeem
202
220
  # Do nothing
203
221
  end
204
222
 
223
+ def inspect()
224
+ result = inspect_prefix
225
+ members.each { |elem| result << elem.inspect + ', ' }
226
+ result.sub!(/, $/, '')
227
+ result << inspect_suffix
228
+ result
229
+ end
230
+
205
231
  alias children members
206
232
  alias subnodes members
233
+ alias rest tail
207
234
  end # class
208
235
 
209
236
  class SkmDefinition < SkmElement
@@ -219,13 +246,32 @@ module Skeem
219
246
  def evaluate(aRuntime)
220
247
  var_key = variable.evaluate(aRuntime)
221
248
  aRuntime.define(var_key, self)
249
+ expression.evaluate(aRuntime) if expression.kind_of?(SkmLambda)
222
250
  self
223
251
  end
252
+
253
+ # call method should only invoked when the expression is a SkmLambda
254
+ def call(aRuntime, aProcedureCall)
255
+ unless expression.kind_of?(SkmLambda)
256
+ err_msg = "Expected a SkmLambda instead of #{expression.class}"
257
+ raise StandardError, err_msg
258
+ end
259
+ expression.call(aRuntime, aProcedureCall)
260
+ end
261
+
262
+ def inspect
263
+ result = inspect_prefix
264
+ result << variable.inspect
265
+ result << ', '
266
+ result << expression.inspect
267
+ result << inspect_suffix
268
+ result
269
+ end
224
270
  end # class
225
-
271
+
226
272
  class SkmVariableReference < SkmElement
227
273
  attr_reader :variable
228
-
274
+
229
275
  def initialize(aPosition, aVariable)
230
276
  super(aPosition)
231
277
  @variable = aVariable
@@ -236,10 +282,10 @@ module Skeem
236
282
  unless aRuntime.include?(var_key.value)
237
283
  err = StandardError
238
284
  key = var_key.kind_of?(SkmIdentifier) ? var_key.value : var_key
239
- err_msg = "Unknown variable '#{key}'"
285
+ err_msg = "Unbound variable: '#{key}'"
240
286
  raise err, err_msg
241
287
  end
242
- definition = aRuntime.environment.bindings[var_key.value]
288
+ definition = aRuntime.environment.fetch(var_key.value)
243
289
  result = definition.expression.evaluate(aRuntime)
244
290
  end
245
291
 
@@ -248,7 +294,12 @@ module Skeem
248
294
  def value()
249
295
  variable.value
250
296
  end
251
- end
297
+
298
+ def inspect
299
+ result = inspect_prefix + variable.inspect + inspect_suffix
300
+ result
301
+ end
302
+ end # class
252
303
 
253
304
  class ProcedureCall < SkmElement
254
305
  attr_reader :operator
@@ -273,13 +324,19 @@ module Skeem
273
324
  err_msg = "Unknown procedure '#{key}'"
274
325
  raise err, err_msg
275
326
  end
276
- procedure = aRuntime.environment.bindings[var_key.value]
327
+ procedure = aRuntime.environment.fetch(var_key.value)
277
328
  result = procedure.call(aRuntime, self)
278
329
  end
279
330
 
331
+ def inspect
332
+ result = inspect_prefix + operator.inspect + ', '
333
+ result << operands.inspect + inspect_suffix
334
+ result
335
+ end
336
+
280
337
  alias children operands
281
338
  end # class
282
-
339
+
283
340
  class SkmCondition < SkmElement
284
341
  attr_reader :test
285
342
  attr_reader :consequent
@@ -302,6 +359,95 @@ module Skeem
302
359
  condition_result = consequent.evaluate(aRuntime)
303
360
  end
304
361
  end
362
+
363
+ def inspect
364
+ result = inspect_prefix + '@test ' + test.inspect + ', '
365
+ result << '@consequent ' + consequent.inspect + ', '
366
+ result << '@alternate ' + alternate.inspect + inspect_suffix
367
+ result
368
+ end
369
+ end # class
370
+
371
+ =begin
372
+ <Skeem::SkmLambda:
373
+ @formals [<Skeem::SkmIdentifier: x>]
374
+ @definitions nil
375
+ @sequence <Skeem::SkmList:
376
+ <Skeem::SkmCondition:
377
+ @test <Skeem::ProcedureCall: <Skeem::SkmIdentifier: <>,
378
+ <Skeem::SkmList: <Skeem::SkmVariableReference: <Skeem::SkmIdentifier: x>>,
379
+ <Skeem::SkmInteger: 0>>>,
380
+ @consequent <Skeem::ProcedureCall:
381
+ <Skeem::SkmIdentifier: ->,
382
+ <Skeem::SkmList: <Skeem::SkmVariableReference: <Skeem::SkmIdentifier: x>>>>,
383
+ @alternate <Skeem::SkmVariableReference: <Skeem::SkmIdentifier: x>>>>>
384
+ =end
385
+
386
+ class SkmLambda < SkmElement
387
+ # @!attribute [r] formals
388
+ # @return [Array<SkmIdentifier>] the argument names
389
+ attr_reader :formals
390
+ attr_reader :definitions
391
+ attr_reader :sequence
392
+
393
+ def initialize(aPosition, theFormals, aBody)
394
+ super(aPosition)
395
+ @formals = theFormals
396
+ @definitions = aBody[:defs]
397
+ @sequence = aBody[:sequence]
398
+ end
399
+
400
+ def evaluate(aRuntime)
401
+ formals.map! { |param| param.evaluate(aRuntime) }
402
+ end
403
+
404
+ def call(aRuntime, aProcedureCall)
405
+ aRuntime.nest
406
+ bind_locals(aRuntime, aProcedureCall)
407
+ # TODO remove next line
408
+ # puts aRuntime.environment.inspect
409
+ result = evaluate_defs(aRuntime)
410
+ result = evaluate_sequence(aRuntime)
411
+ aRuntime.unnest
412
+
413
+ result
414
+ end
415
+
416
+ def inspect
417
+ result = inspect_prefix + '@formals ' + formals.inspect + ', '
418
+ result << '@definitions ' + definitions.inspect + ', '
419
+ result << '@sequence ' + sequence.inspect + inspect_suffix
420
+ result
421
+ end
422
+
423
+ private
424
+
425
+ def bind_locals(aRuntime, aProcedureCall)
426
+ arguments = aProcedureCall.operands.members
427
+ formals.each_with_index do |arg_name, index|
428
+ arg = arguments[index]
429
+ if arg.nil?
430
+ p aProcedureCall.operands.members
431
+ raise StandardError, "Unbound variable: '#{arg_name.value}'"
432
+ end
433
+
434
+ a_def = SkmDefinition.new(position, arg_name, arg)
435
+ a_def.evaluate(aRuntime)
436
+ end
437
+ end
438
+
439
+ def evaluate_defs(aRuntime)
440
+ definitions.each { |a_def| a_def.evaluate(runtime) }
441
+ end
442
+
443
+ def evaluate_sequence(aRuntime)
444
+ result = nil
445
+ if sequence
446
+ sequence.each { |cmd| result = cmd.evaluate(aRuntime) }
447
+ end
448
+
449
+ result
450
+ end
305
451
  end # class
306
452
  end # module
307
453
  # End of file
@@ -30,6 +30,7 @@ module Skeem
30
30
  BEGIN
31
31
  IF
32
32
  DEFINE
33
+ LAMBDA
33
34
  ].map { |x| [x, x] } .to_h
34
35
 
35
36
  class ScanError < StandardError; end
data/lib/skeem/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Skeem
2
- VERSION = '0.0.13'.freeze
2
+ VERSION = '0.0.14'.freeze
3
3
  end
@@ -4,12 +4,12 @@ require_relative '../../lib/skeem/environment' # Load the class under test
4
4
  module Skeem
5
5
  describe Environment do
6
6
  context 'Initialization:' do
7
- it 'should be initialized without argument' do
7
+ it 'should be initialized with opional argument' do
8
8
  expect { Environment.new() }.not_to raise_error
9
9
  end
10
10
 
11
- it 'should have no bindings' do
12
- expect(subject.bindings).to be_empty
11
+ it 'should have no default bindings' do
12
+ expect(subject).to be_empty
13
13
  end
14
14
  end # context
15
15
 
@@ -17,11 +17,53 @@ module Skeem
17
17
  it 'should add entries' do
18
18
  entry = double('dummy')
19
19
  subject.define('dummy', entry)
20
- expect(subject.bindings.size).to eq(1)
20
+ expect(subject.size).to eq(1)
21
21
  expect(subject.bindings['dummy']).not_to be_nil
22
22
  expect(subject.bindings['dummy']).to eq(entry)
23
23
  end
24
+
25
+ it 'should know whether it is empty' do
26
+ # Case 1: no entry
27
+ expect(subject.empty?).to be_truthy
28
+
29
+ # Case 2: existing entry
30
+ entry = double('dummy')
31
+ subject.define('dummy', entry)
32
+ expect(subject.empty?).to be_falsey
33
+
34
+ # Case 3: entry defined in outer environment
35
+ nested = Environment.new(subject)
36
+ expect(nested.empty?).to be_falsey
37
+ end
38
+
39
+ it 'should retrieve entries' do
40
+ # Case 1: non-existing entry
41
+ expect(subject.fetch('dummy')).to be_nil
42
+
43
+ # Case 2: existing entry
44
+ entry = double('dummy')
45
+ subject.define('dummy', entry)
46
+ expect(subject.fetch('dummy')).to eq(entry)
47
+
48
+ # Case 3: entry defined in outer environment
49
+ nested = Environment.new(subject)
50
+ expect(nested.fetch('dummy')).to eq(entry)
51
+ end
52
+
53
+ it 'should know the total number of bindings' do
54
+ # Case 1: non-existing entry
55
+ expect(subject.size).to be_zero
56
+
57
+ # Case 2: existing entry
58
+ entry = double('dummy')
59
+ subject.define('dummy', entry)
60
+ expect(subject.size).to eq(1)
61
+
62
+ # Case 3: entry defined in outer environment
63
+ nested = Environment.new(subject)
64
+ expect(nested.size).to eq(1)
65
+ end
24
66
  end # context
25
-
67
+
26
68
  end # describe
27
69
  end # module
@@ -77,13 +77,13 @@ module Skeem
77
77
  end
78
78
  end
79
79
  end # context
80
-
81
- context 'Built-in primitives' do
80
+
81
+ context 'Built-in primitives' do
82
82
  it 'should implement variable definition' do
83
83
  result = subject.run('(define x 28)')
84
84
  expect(result).to be_kind_of(SkmDefinition)
85
85
  end
86
-
86
+
87
87
  it 'should implement variable reference' do
88
88
  source = <<-SKEEM
89
89
  ; Example from R7RS section 4.1.1
@@ -103,9 +103,9 @@ SKEEM
103
103
  checks.each do |(skeem_expr, expectation)|
104
104
  result = subject.run(skeem_expr)
105
105
  expect(result.value).to eq(expectation)
106
- end
106
+ end
107
107
  end
108
-
108
+
109
109
  it 'should implement the complete conditional form' do
110
110
  checks = [
111
111
  ['(if (> 3 2) "yes" "no")', 'yes'],
@@ -122,8 +122,49 @@ SKEEM
122
122
  (+ 3 2))
123
123
  SKEEM
124
124
  result = subject.run(source)
125
- expect(result.value).to eq(1)
125
+ expect(result.value).to eq(1)
126
+ end
127
+
128
+ it 'should implement the lambda function with one arg' do
129
+ source = <<-SKEEM
130
+ ; Simplified 'abs' function implementation
131
+ (define abs (lambda(x)
132
+ (if (< x 0) (- x) x)))
133
+ SKEEM
134
+ subject.run(source)
135
+ result = subject.run('(abs -3)')
136
+ expect(result.value).to eq(3)
137
+ result = subject.run('(abs 0)')
138
+ expect(result.value).to eq(0)
139
+ result = subject.run('(abs 3)')
140
+ expect(result.value).to eq(3)
126
141
  end
142
+
143
+ it 'should implement the lambda function with two args' do
144
+ source = <<-SKEEM
145
+ ; Simplified 'min' function implementation
146
+ (define min (lambda(x y)
147
+ (if (< x y) x y)))
148
+ SKEEM
149
+ subject.run(source)
150
+ result = subject.run('(min 1 2)')
151
+ expect(result.value).to eq(1)
152
+ result = subject.run('(min 2 1)')
153
+ expect(result.value).to eq(1)
154
+ result = subject.run('(min 2 2)')
155
+ expect(result.value).to eq(2)
156
+ end
157
+
158
+ # it 'should implement recursive functions' do
159
+ # source = <<-SKEEM
160
+ # ; Example from R7RS section 4.1.5
161
+ # (define fact (lambda (n)
162
+ # (if (<= n 1) 1 (* n (fact (- n 1))))))
163
+ # (fact 10)
164
+ # SKEEM
165
+ # subject.run(source)
166
+ # expect(result.value).to eq(3628800)
167
+ # end
127
168
  end # context
128
169
 
129
170
  context 'Built-in primitive procedures' do
@@ -234,7 +275,7 @@ SKEEM
234
275
  expect(result.value).to eq(expectation)
235
276
  end
236
277
  end
237
-
278
+
238
279
  it 'should implement the greater or equal than operator' do
239
280
  checks = [
240
281
  ['(>= 3 2)', true],
@@ -248,7 +289,7 @@ SKEEM
248
289
  result = subject.run(skeem_expr)
249
290
  expect(result.value).to eq(expectation)
250
291
  end
251
- end
292
+ end
252
293
 
253
294
  it 'should implement the number? predicate' do
254
295
  checks = [
@@ -6,7 +6,7 @@ module Skeem
6
6
  describe Runtime do
7
7
  let(:some_env) { Environment.new }
8
8
  subject { Runtime.new(some_env) }
9
-
9
+
10
10
  context 'Initialization:' do
11
11
  it 'should be initialized with an environment' do
12
12
  expect { Runtime.new(Environment.new) }.not_to raise_error
@@ -21,15 +21,29 @@ module Skeem
21
21
  it 'should add entries to the environment' do
22
22
  entry = double('dummy')
23
23
  subject.define('dummy', entry)
24
- expect(subject.environment.bindings.size).to eq(1)
24
+ expect(subject.environment.size).to eq(1)
25
25
  end
26
-
26
+
27
27
  it 'should know the keys in the environment' do
28
28
  expect(subject.include?('dummy')).to be_falsey
29
29
  entry = double('dummy')
30
- subject.define('dummy', entry)
30
+ subject.define('dummy', entry)
31
31
  expect(subject.include?('dummy')).to be_truthy
32
32
  end
33
+
34
+ it 'should add nested environment' do
35
+ env_before = subject.environment
36
+ subject.nest
37
+ expect(subject.environment).not_to eq(env_before)
38
+ expect(subject.environment.outer).to eq(env_before)
39
+ end
40
+
41
+ it 'should remove nested environment' do
42
+ subject.nest
43
+ outer_before = subject.environment.outer
44
+ subject.unnest
45
+ expect(subject.environment).to eq(outer_before)
46
+ end
33
47
  end # context
34
48
  end # describe
35
49
  end # module
@@ -0,0 +1,411 @@
1
+ require 'ostruct'
2
+ require_relative '../spec_helper' # Use the RSpec framework
3
+ require_relative '../../lib/skeem/s_expr_nodes' # Load the classes under test
4
+
5
+ module Skeem
6
+ describe SkmElement do
7
+ let(:pos) {double('fake-position') }
8
+ subject { SkmElement.new(pos) }
9
+
10
+ context 'Initialization:' do
11
+ it 'should be initialized with a position' do
12
+ expect { SkmElement.new(pos) }.not_to raise_error
13
+ end
14
+
15
+ it 'should know its position' do
16
+ expect(subject.position).to eq(pos)
17
+ end
18
+
19
+ # Default (overridable) behavior of SkmElement
20
+ it 'should react by default to predicates' do
21
+ expect(subject).not_to be_boolean
22
+ expect(subject).not_to be_number
23
+ expect(subject).not_to be_real
24
+ expect(subject).not_to be_integer
25
+ expect(subject).not_to be_string
26
+ expect(subject).not_to be_symbol
27
+ end
28
+ end # context
29
+ end # describe
30
+
31
+ describe SkmTerminal do
32
+ let(:pos) { double('fake-position') }
33
+ let(:dummy_symbol) { double('fake-symbol') }
34
+ let(:sample_value) { 'sample-value' }
35
+ let(:dummy_token) do
36
+ obj = OpenStruct.new
37
+ obj.terminal = dummy_symbol
38
+ obj.lexeme = sample_value
39
+ obj
40
+ end
41
+ subject { SkmTerminal.new(dummy_token, pos) }
42
+
43
+ context 'Initialization:' do
44
+ it 'should be initialized with a token and a position' do
45
+ expect { SkmTerminal.new(dummy_token, pos) }.not_to raise_error
46
+ end
47
+
48
+ it 'should know its token' do
49
+ expect(subject.token).to eq(dummy_token)
50
+ end
51
+
52
+ it 'should know its value' do
53
+ expect(subject.value).to eq(sample_value)
54
+ end
55
+
56
+ it "should know the token's symbol" do
57
+ expect(subject.symbol).to eq(dummy_symbol)
58
+ end
59
+ end
60
+
61
+ context 'Provided services:' do
62
+ it 'should return its text representation' do
63
+ expect(subject.inspect).to eq("<Skeem::SkmTerminal: sample-value>")
64
+ end
65
+ end # context
66
+ end # describe
67
+
68
+ describe SkmBoolean do
69
+ let(:pos) { double('fake-position') }
70
+ let(:dummy_symbol) { double('BOOLEAN') }
71
+ let(:sample_value) { false }
72
+ let(:dummy_token) do
73
+ obj = OpenStruct.new
74
+ obj.terminal = dummy_symbol
75
+ obj.lexeme = sample_value
76
+ obj
77
+ end
78
+ subject { SkmBoolean.new(dummy_token, pos) }
79
+
80
+ context 'Initialization:' do
81
+ it 'should be initialized with a token and a position' do
82
+ expect { SkmBoolean.new(dummy_token, pos) }.not_to raise_error
83
+ end
84
+
85
+ it 'should react positively to boolean? predicate' do
86
+ expect(subject).to be_boolean
87
+ end
88
+ end
89
+
90
+ context 'Provided services:' do
91
+ it 'should return its text representation' do
92
+ expect(subject.inspect).to eq('<Skeem::SkmBoolean: false>')
93
+ end
94
+ end # context
95
+ end # describe
96
+
97
+ describe SkmNumber do
98
+ let(:pos) { double('fake-position') }
99
+ let(:dummy_symbol) { double('dummy') }
100
+ let(:sample_value) { 0.5100 }
101
+ let(:dummy_token) do
102
+ obj = OpenStruct.new
103
+ obj.terminal = dummy_symbol
104
+ obj.lexeme = sample_value
105
+ obj
106
+ end
107
+ subject { SkmNumber.new(dummy_token, pos) }
108
+
109
+ context 'Initialization:' do
110
+ it 'should be initialized with a token and a position' do
111
+ expect { SkmNumber.new(dummy_token, pos) }.not_to raise_error
112
+ end
113
+ end
114
+
115
+ context 'Provided services:' do
116
+ it 'should react positively to number? predicate' do
117
+ expect(subject).to be_number
118
+ end
119
+
120
+ it 'should return its text representation' do
121
+ expect(subject.inspect).to eq('<Skeem::SkmNumber: 0.51>')
122
+ end
123
+ end # context
124
+ end # describe
125
+
126
+ describe SkmReal do
127
+ let(:pos) { double('fake-position') }
128
+ let(:dummy_symbol) { double('dummy') }
129
+ let(:sample_value) { 0.5100 }
130
+ let(:dummy_token) do
131
+ obj = OpenStruct.new
132
+ obj.terminal = dummy_symbol
133
+ obj.lexeme = sample_value
134
+ obj
135
+ end
136
+ subject { SkmReal.new(dummy_token, pos) }
137
+
138
+ context 'Provided services:' do
139
+ it 'should react positively to number? predicate' do
140
+ expect(subject).to be_number
141
+ end
142
+
143
+ it 'should react positively to real? predicate' do
144
+ expect(subject).to be_real
145
+ end
146
+ end
147
+ end # describe
148
+
149
+ describe SkmInteger do
150
+ let(:pos) { double('fake-position') }
151
+ let(:dummy_symbol) { double('dummy') }
152
+ let(:sample_value) { 3 }
153
+ let(:dummy_token) do
154
+ obj = OpenStruct.new
155
+ obj.terminal = dummy_symbol
156
+ obj.lexeme = sample_value
157
+ obj
158
+ end
159
+ subject { SkmInteger.new(dummy_token, pos) }
160
+
161
+ context 'Provided services:' do
162
+ it 'should react positively to number? predicate' do
163
+ expect(subject).to be_number
164
+ end
165
+
166
+ it 'should react positively to real? predicate' do
167
+ expect(subject).to be_real
168
+ end
169
+
170
+ it 'should react positively to integer? predicate' do
171
+ expect(subject).to be_real
172
+ end
173
+ end
174
+ end # describe
175
+
176
+ describe SkmString do
177
+ let(:pos) { double('fake-position') }
178
+ let(:dummy_symbol) { double('dummy') }
179
+ let(:sample_value) { 'Hello' }
180
+ let(:dummy_token) do
181
+ obj = OpenStruct.new
182
+ obj.terminal = dummy_symbol
183
+ obj.lexeme = sample_value
184
+ obj
185
+ end
186
+ subject { SkmString.new(dummy_token, pos) }
187
+
188
+ context 'Provided services:' do
189
+ it 'should react positively to string? predicate' do
190
+ expect(subject).to be_string
191
+ end
192
+
193
+ it 'should return its text representation' do
194
+ expect(subject.inspect).to eq('<Skeem::SkmString: Hello>')
195
+ end
196
+ end
197
+ end # describe
198
+
199
+ describe SkmIdentifier do
200
+ let(:pos) { double('fake-position') }
201
+ let(:dummy_symbol) { double('dummy') }
202
+ let(:sample_value) { 'this-is-it!' }
203
+ let(:dummy_token) do
204
+ obj = OpenStruct.new
205
+ obj.terminal = dummy_symbol
206
+ obj.lexeme = sample_value
207
+ obj
208
+ end
209
+ subject { SkmIdentifier.new(dummy_token, pos) }
210
+
211
+ context 'Provided services:' do
212
+ it 'should react positively to symbol? predicate' do
213
+ expect(subject).to be_symbol
214
+ end
215
+
216
+ it 'should return its text representation' do
217
+ expect(subject.inspect).to eq('<Skeem::SkmIdentifier: this-is-it!>')
218
+ end
219
+ end # context
220
+ end # describe
221
+
222
+ describe SkmList do
223
+ let(:sample_members) { [1, 2, 3] }
224
+
225
+ subject { SkmList.new(sample_members) }
226
+
227
+ context 'Initialization:' do
228
+ it 'should be initialized with its members' do
229
+ expect{ SkmList.new(sample_members) }.not_to raise_error
230
+ end
231
+
232
+ it 'should know its members' do
233
+ expect(subject.members).to eq(sample_members)
234
+ end
235
+ end # context
236
+
237
+ context 'Provided services:' do
238
+ it 'should retrieve its first member' do
239
+ expect(subject.first).to eq(1)
240
+ expect(subject.head).to eq(1)
241
+ end
242
+
243
+ it 'should retrieve its tail members' do
244
+ expect(subject.tail.inspect).to eq('<Skeem::SkmList: 2, 3>')
245
+ expect(subject.rest.inspect).to eq('<Skeem::SkmList: 2, 3>')
246
+ end
247
+
248
+ it 'should return its text representation' do
249
+ expect(subject.inspect).to eq('<Skeem::SkmList: 1, 2, 3>')
250
+ end
251
+ end # context
252
+ end # describe
253
+
254
+
255
+ describe SkmDefinition do
256
+ let(:pos) { double('fake-position') }
257
+ let(:sample_symbol) { SkmIdentifier.create('this-is-it!') }
258
+ let(:sample_expr) { SkmInteger.create(10) }
259
+
260
+ subject { SkmDefinition.new(pos, sample_symbol, sample_expr) }
261
+
262
+ context 'Initialization:' do
263
+ it 'should be initialized with a symbol and an expression' do
264
+ expect{ SkmDefinition.new(pos, sample_symbol, sample_expr) }.not_to raise_error
265
+ end
266
+
267
+ it 'should know its variable' do
268
+ expect(subject.variable).to eq(sample_symbol)
269
+ end
270
+
271
+ it 'should know its expression' do
272
+ expect(subject.expression).to eq(sample_expr)
273
+ end
274
+ end # context
275
+
276
+ context 'Provided services:' do
277
+ it 'should return its text representation' do
278
+ txt1 = '<Skeem::SkmDefinition: <Skeem::SkmIdentifier: this-is-it!>,'
279
+ txt2 = ' <Skeem::SkmInteger: 10>>'
280
+ expect(subject.inspect).to eq(txt1 + txt2)
281
+ end
282
+ end # context
283
+ end # describe
284
+
285
+ describe SkmVariableReference do
286
+ let(:pos) { double('fake-position') }
287
+ let(:sample_symbol) { SkmIdentifier.create('this-is-it!') }
288
+
289
+ subject { SkmVariableReference.new(pos, sample_symbol) }
290
+
291
+ context 'Initialization:' do
292
+ it 'should be initialized with a position and a symbol' do
293
+ expect{ SkmVariableReference.new(pos, sample_symbol) }.not_to raise_error
294
+ end
295
+
296
+ it 'should know its variable' do
297
+ expect(subject.variable).to eq(sample_symbol)
298
+ end
299
+ end # context
300
+
301
+ context 'Provided services:' do
302
+ it 'should return its text representation' do
303
+ txt1 = '<Skeem::SkmVariableReference: <Skeem::SkmIdentifier: this-is-it!>>'
304
+ expect(subject.inspect).to eq(txt1)
305
+ end
306
+ end # context
307
+ end # describe
308
+
309
+ describe ProcedureCall do
310
+ let(:pos) { double('fake-position') }
311
+ let(:operator) { SkmIdentifier.create('+') }
312
+ let(:operands) { [1, 2, 3] }
313
+
314
+ subject { ProcedureCall.new(pos, operator, operands) }
315
+
316
+ context 'Initialization:' do
317
+ it 'should be initialized with an operator symbol and its operands' do
318
+ expect{ ProcedureCall.new(pos, operator, operands) }.not_to raise_error
319
+ end
320
+
321
+ it 'should know its operator' do
322
+ expect(subject.operator).to eq(operator)
323
+ end
324
+
325
+ it 'should know its operands' do
326
+ expect(subject.operands.inspect).to eq('<Skeem::SkmList: 1, 2, 3>')
327
+ end
328
+ end # context
329
+
330
+ context 'Provided services:' do
331
+ it 'should return its text representation' do
332
+ txt1 = '<Skeem::ProcedureCall: <Skeem::SkmIdentifier: +>, '
333
+ txt2 = '<Skeem::SkmList: 1, 2, 3>>'
334
+ expect(subject.inspect).to eq(txt1 + txt2)
335
+ end
336
+ end # context
337
+ end # describe
338
+
339
+ describe SkmCondition do
340
+ let(:pos) { double('fake-position') }
341
+ let(:s_test) { double('fake-test') }
342
+ let(:s_consequent) { double('fake-consequent') }
343
+ let(:s_alt) { double('fake-alternate') }
344
+
345
+ subject { SkmCondition.new(pos, s_test, s_consequent, s_alt) }
346
+
347
+ context 'Initialization:' do
348
+ it 'should be initialized with a pos and 3 expressions' do
349
+ expect{ SkmCondition.new(pos, s_test, s_consequent, s_alt) }.not_to raise_error
350
+ end
351
+
352
+ it 'should know its test' do
353
+ expect(subject.test).to eq(s_test)
354
+ end
355
+
356
+ it 'should know its consequent' do
357
+ expect(subject.consequent).to eq(s_consequent)
358
+ end
359
+
360
+ it 'should know its alternate' do
361
+ expect(subject.alternate).to eq(s_alt)
362
+ end
363
+ end # context
364
+
365
+ context 'Provided services:' do
366
+ it 'should return its text representation' do
367
+ txt1 = '<Skeem::SkmCondition: @test #<Double "fake-test">, '
368
+ txt2 = '@consequent #<Double "fake-consequent">, '
369
+ txt3 = '@alternate #<Double "fake-alternate">>'
370
+ expect(subject.inspect).to eq(txt1 + txt2 + txt3)
371
+ end
372
+ end # context
373
+ end # describe
374
+
375
+ describe SkmLambda do
376
+ let(:pos) { double('fake-position') }
377
+ let(:s_formals) { double('fake-formals') }
378
+ let(:s_defs) { double('fake-definitions') }
379
+ let(:s_sequence) { double('fake-sequence') }
380
+ let(:s_body) do { defs: s_defs, sequence: s_sequence } end
381
+
382
+ subject { SkmLambda.new(pos, s_formals, s_body) }
383
+
384
+ context 'Initialization:' do
385
+ it 'should be initialized with a pos and 3 expressions' do
386
+ expect{ SkmLambda.new(pos, s_formals, s_body) }.not_to raise_error
387
+ end
388
+
389
+ it 'should know its formals' do
390
+ expect(subject.formals).to eq(s_formals)
391
+ end
392
+
393
+ it 'should know its definitions' do
394
+ expect(subject.definitions).to eq(s_defs)
395
+ end
396
+
397
+ it 'should know its sequence' do
398
+ expect(subject.sequence).to eq(s_sequence)
399
+ end
400
+ end # context
401
+
402
+ context 'Provided services:' do
403
+ it 'should return its text representation' do
404
+ txt1 = '<Skeem::SkmLambda: @formals #<Double "fake-formals">, '
405
+ txt2 = '@definitions #<Double "fake-definitions">, '
406
+ txt3 = '@sequence #<Double "fake-sequence">>'
407
+ expect(subject.inspect).to eq(txt1 + txt2 + txt3)
408
+ end
409
+ end # context
410
+ end # describe
411
+ end # module
@@ -134,7 +134,11 @@ module Skeem
134
134
  examples.each do |input|
135
135
  subject.reinitialize(input)
136
136
  token = subject.tokens.first
137
- expect(token.terminal).to eq('IDENTIFIER')
137
+ if token.lexeme == 'lambda'
138
+ expect(token.terminal).to eq('LAMBDA')
139
+ else
140
+ expect(token.terminal).to eq('IDENTIFIER')
141
+ end
138
142
  expect(token.lexeme).to eq(input)
139
143
  end
140
144
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skeem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-18 00:00:00.000000000 Z
11
+ date: 2018-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -104,6 +104,7 @@ files:
104
104
  - spec/skeem/parser_spec.rb
105
105
  - spec/skeem/primitive/primitive_builder_spec.rb
106
106
  - spec/skeem/runtime_spec.rb
107
+ - spec/skeem/s_expr_nodes_spec.rb
107
108
  - spec/skeem/tokenizer_spec.rb
108
109
  - spec/skeem_spec.rb
109
110
  - spec/spec_helper.rb
@@ -139,5 +140,6 @@ test_files:
139
140
  - spec/skeem/parser_spec.rb
140
141
  - spec/skeem/primitive/primitive_builder_spec.rb
141
142
  - spec/skeem/runtime_spec.rb
143
+ - spec/skeem/s_expr_nodes_spec.rb
142
144
  - spec/skeem/tokenizer_spec.rb
143
145
  - spec/skeem_spec.rb