skeem 0.0.13 → 0.0.14

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