skeem 0.1.03 → 0.2.00

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.
@@ -0,0 +1,103 @@
1
+ require_relative 'skm_element'
2
+
3
+ module Skeem
4
+
5
+ # An identifier that is not a syntactic keyword can be used as a variable.
6
+ # A variable may give a name to value that is bound (i.e. associated)
7
+ # to that variable.
8
+ class SkmBinding < SkmElement
9
+ # @return [SkmIdentifier] The identifier that is bound the value.
10
+ attr_reader(:variable)
11
+
12
+ # @return [SkmElement] The Skeem object that is associated with the variable.
13
+ attr_reader(:value)
14
+
15
+ # Constructor
16
+ # @param anIdentifier [SkmIdentifier] The variable name
17
+ # @param aValue [SkmElement] The value to bind to the variable.
18
+ def initialize(anIdentifier, aValue)
19
+ @variable = anIdentifier
20
+ @value = aValue
21
+ end
22
+
23
+ def evaluate(aRuntime)
24
+ name = variable.evaluate(aRuntime)
25
+
26
+ if value.kind_of?(SkmVariableReference)
27
+ other_name = value.variable.evaluate(aRuntime)
28
+ if name.value != other_name.value
29
+ entry = aRuntime.fetch(other_name)
30
+ result = value.evaluate(aRuntime)
31
+ if entry.callable?
32
+ @value = entry
33
+ end
34
+ else
35
+ # same name in definiens
36
+ raise StandardError
37
+ end
38
+ else
39
+ result = value.evaluate(aRuntime)
40
+ end
41
+
42
+ =begin
43
+ aRuntime.add_binding(var_key, self)
44
+ case expression
45
+ when SkmLambda
46
+ result = expression.evaluate(aRuntime)
47
+
48
+ when SkmVariableReference
49
+ other_key = expression.variable.evaluate(aRuntime)
50
+ if var_key.value != other_key.value
51
+ entry = aRuntime.fetch(other_key)
52
+ result = expression.evaluate(aRuntime)
53
+ if entry.kind_of?(Primitive::PrimitiveProcedure)
54
+ @expression = entry
55
+ elsif entry.kind_of?(SkmDefinition)
56
+ if entry.expression.kind_of?(SkmLambda)
57
+ @expression = entry.expression
58
+ end
59
+ end
60
+ else
61
+ # INFINITE LOOP DANGER: definition of 'x' has a reference to 'x'!
62
+ # Way out: the lookup for the reference should start from outer
63
+ # environment.
64
+ env = aRuntime.pop
65
+ @expression = expression.evaluate(aRuntime)
66
+ aRuntime.push(env)
67
+ result = expression
68
+ end
69
+ else
70
+ result = self
71
+ end
72
+ =end
73
+ binding_action(aRuntime, name, result)
74
+ result
75
+ end
76
+
77
+ protected
78
+
79
+ def binding_action(aRuntime, anIdentifier, anExpression)
80
+ aRuntime.add_binding(anIdentifier, anExpression)
81
+ end
82
+
83
+ def inspect_specific
84
+ result = variable.inspect
85
+ if value
86
+ result << ', ' << value.inspect
87
+ end
88
+
89
+ result
90
+ end
91
+
92
+ end # class
93
+
94
+
95
+ class SkmUpdateBinding < SkmBinding
96
+
97
+ protected
98
+
99
+ def binding_action(aRuntime, anIdentifier, anExpression)
100
+ aRuntime.update_binding(anIdentifier, anExpression)
101
+ end
102
+ end # class
103
+ end # module
@@ -4,6 +4,10 @@ module Skeem
4
4
  def initialize(aPosition)
5
5
  self.position = aPosition
6
6
  end
7
+
8
+ def callable?
9
+ false
10
+ end
7
11
 
8
12
  def number?
9
13
  false
@@ -36,7 +40,7 @@ module Skeem
36
40
  def null?
37
41
  false
38
42
  end
39
-
43
+
40
44
  def pair?
41
45
  false
42
46
  end
@@ -44,16 +48,16 @@ module Skeem
44
48
  def vector?
45
49
  false
46
50
  end
47
-
51
+
48
52
  def eqv?(other)
49
53
  equal?(other)
50
54
  end
51
-
55
+
52
56
  def skm_equal?(_other)
53
57
  msg = "Missing implementation of method #{self.class.name}##{__method__}"
54
58
  raise NotImplementedError, msg
55
59
  end
56
-
60
+
57
61
  # @return [TrueClass, FalseClass] true if quoted element is identical to itself
58
62
  def verbatim?
59
63
  false
@@ -62,10 +66,10 @@ module Skeem
62
66
  def evaluate(_runtime)
63
67
  raise NotImplementedError, "Missing implementation of #{self.class.name}"
64
68
  end
65
-
69
+
66
70
  def quasiquote(_runtime)
67
71
  raise NotImplementedError, "Missing implementation of #{self.class.name}"
68
- end
72
+ end
69
73
 
70
74
  # Abstract method.
71
75
  # Part of the 'visitee' role in Visitor design pattern.
@@ -77,14 +81,20 @@ module Skeem
77
81
  def done!
78
82
  # Do nothing
79
83
  end
80
-
84
+
81
85
  def quoted!
82
86
  # Do nothing
83
87
  end
84
-
88
+
85
89
  def unquoted!
86
90
  # Do nothing
87
- end
91
+ end
92
+
93
+ # Notification that this procedure is bound to a variable
94
+ # @param [Skemm::SkmFrame]
95
+ def bound!(_frame)
96
+ # Do nothing
97
+ end
88
98
 
89
99
  def inspect
90
100
  result = inspect_prefix
@@ -105,7 +115,7 @@ module Skeem
105
115
  end
106
116
 
107
117
  def inspect_specific
108
- raise NotImplementedError
118
+ raise NotImplementedError, "Missing #{self.class.to_s + '#' + 'inspect_specific'}"
109
119
  end
110
120
  end # struct
111
121
  end # module
@@ -0,0 +1,137 @@
1
+ require_relative 'skm_simple_datum'
2
+
3
+ module Skeem
4
+ # A frame is a set of bindings of the form { String => SkmElement }.
5
+ # It associate to each identifier (as String) a Skeem object.
6
+ class SkmFrame
7
+ # @return [SkmFrame, nil] Link to parent frame (if any).
8
+ attr_reader :parent
9
+
10
+ # @return [Hash{String => SkmElement}] Map of variable names => values.
11
+ attr_reader(:bindings)
12
+
13
+ # Constructor
14
+ # @param parentFrame[SkmFrame] Parent frame of this one.
15
+ def initialize(parentFrame = nil)
16
+ @bindings = {}
17
+ @parent = parentFrame
18
+ end
19
+
20
+ # Add a binding to this frame.
21
+ # There is no check that the variable name is already in use.
22
+ # @param anIdentifier [String, SkmIdentifier] The variable name
23
+ # @param anExpression [SkmElement] The Skeem expression to bind
24
+ def add_binding(anIdentifier, anExpression)
25
+ bind(valid_identifier(anIdentifier), anExpression)
26
+ end
27
+
28
+ # Update the value of an existing variable.
29
+ # @param anIdentifier [String, SkmIdentifier] The variable name.
30
+ # @param anExpression [SkmElement] The Skeem expression to bind
31
+ def update_binding(anIdentifier, anExpression)
32
+ variable = valid_identifier(anIdentifier)
33
+ if bindings.include?(variable)
34
+ bind(variable, anExpression)
35
+ elsif parent
36
+ parent.update_binding(variable, anExpression)
37
+ end
38
+ end
39
+
40
+ # Retrieve the value of an existing variable.
41
+ # @param anIdentifier [String, SkmIdentifier] The variable name.
42
+ # @return [SkmElement]
43
+ def fetch(anIdentifier)
44
+ variable = valid_identifier(anIdentifier)
45
+ found = bindings[variable]
46
+ if found.nil? && parent
47
+ found = parent.fetch(variable)
48
+ end
49
+
50
+ found
51
+ end
52
+
53
+ # Tell the value of an existing variable.
54
+ # @param anIdentifier [String, SkmIdentifier] The variable name.
55
+ # @return [Boolean]
56
+ def include?(anIdentifier)
57
+ variable = valid_identifier(anIdentifier)
58
+ my_result = bindings.include?(variable)
59
+ if my_result == false && parent
60
+ my_result = parent.include?(variable)
61
+ end
62
+
63
+ my_result
64
+ end
65
+
66
+ # @return [Boolean]
67
+ def empty?
68
+ my_result = bindings.empty?
69
+ if my_result && parent
70
+ my_result = parent.empty?
71
+ end
72
+
73
+ my_result
74
+ end
75
+
76
+ # @return [Integer]
77
+ def size
78
+ my_result = bindings.size
79
+ my_result += parent.size if parent
80
+
81
+ my_result
82
+ end
83
+
84
+ # The number of parents this frame has.
85
+ # @return [Integer] The nesting levels
86
+ def depth
87
+ count = 0
88
+
89
+ curr_frame = self
90
+ while curr_frame.parent
91
+ count += 1
92
+ curr_frame = curr_frame.parent
93
+ end
94
+
95
+ count
96
+ end
97
+
98
+ # @return [String]
99
+ def inspect
100
+ result = ''
101
+ if parent
102
+ result << parent.inspect
103
+ else
104
+ return "\n"
105
+ end
106
+ result << "\n----\n"
107
+ bindings.each_pair do |key, expr|
108
+ result << "#{key.inspect} => #{expr.inspect}\n"
109
+ end
110
+
111
+ result
112
+ end
113
+
114
+ private
115
+
116
+ def valid_identifier(anIdentifier)
117
+ case anIdentifier
118
+ when String
119
+ anIdentifier
120
+ when SkmIdentifier
121
+ anIdentifier.value
122
+ else
123
+ klass = anIdentifier.class.to_s
124
+ err_msg = "Invalid identifier: #{anIdentifier} has type #{klass}."
125
+ raise StandardError, err_msg
126
+ end
127
+ end
128
+
129
+ def bind(anIdentifier, anExpression)
130
+ @bindings[anIdentifier] = anExpression
131
+
132
+ # Notify the value that it is bound to a variable from this frame.
133
+ anExpression.bound!(self)
134
+ end
135
+
136
+ end # class
137
+ end # module
@@ -133,8 +133,8 @@ module Skeem
133
133
  begin
134
134
  result = clone_evaluate(aRuntime)
135
135
  rescue NoMethodError => exc
136
- $stderr.puts self.inspect
137
- $stderr.puts self.to_a.inspect
136
+ $stderr.puts 'SkmPair#evaluate: ' + self.inspect
137
+ $stderr.puts 'SkmPair as Array: ' + self.to_a.inspect
138
138
  raise exc
139
139
  end
140
140
  end
@@ -0,0 +1,31 @@
1
+ require_relative 'runtime'
2
+
3
+ module Skeem
4
+ class SkmProcedureExec
5
+ attr_reader :frame
6
+ attr_reader :definition
7
+
8
+ def initialize(aLambda)
9
+ @definition = aLambda
10
+ @frame = SkmFrame.new(definition.environment)
11
+ # $stderr.puts "New SkmProcedureExec"
12
+ # $stderr.puts " frame = #{frame.object_id.to_s(16)}"
13
+ # $stderr.puts " Lambda = #{aLambda.object_id.to_s(16)}"
14
+ end
15
+
16
+ # @param theActuals [Array<SkmElement>]
17
+ def run!(aRuntime, theActuals)
18
+ runtime = aRuntime
19
+ runtime.push(frame)
20
+ definition.bind_locals(runtime, theActuals)
21
+ # $stderr.puts "Locals"
22
+ # $stderr.puts frame.bindings.keys.join(', ')
23
+ definition.evaluate_defs(runtime)
24
+ result = definition.evaluate_sequence(runtime)
25
+ runtime.pop
26
+ # $stderr.puts "Lambda result: #{result.object_id.to_s(16)}" if result.kind_of?(SkmLambda)
27
+
28
+ result
29
+ end
30
+ end # class
31
+ end # module
@@ -98,4 +98,18 @@
98
98
  ; (car ls)
99
99
  ; (list-ref (cdr ls) (- n 1)))))
100
100
 
101
- (define symbol=? string=?)
101
+ (define symbol=? string=?)
102
+
103
+ ;; Test the equivalence (with eqv? predicate) between an expected value and
104
+ ;; an expression
105
+ ;; (test-eqv expected test-expr)
106
+ (define test-eqv
107
+ (lambda (x y)
108
+ (test-assert (equal? x y))))
109
+
110
+ ;; Test the equality (with equal? predicate) between an expected value and
111
+ ;; an expression
112
+ ;; (test-equal expected test-expr)
113
+ (define test-equal
114
+ (lambda (x y)
115
+ (test-assert (equal? x y))))
@@ -29,7 +29,7 @@ module Skeem
29
29
  '#(' => 'VECTOR_BEGIN'
30
30
  }.freeze
31
31
 
32
- # Here are all the SRL keywords (in uppercase)
32
+ # Here are all the Scheme keywords (in uppercase)
33
33
  @@keywords = %w[
34
34
  BEGIN
35
35
  DEFINE
@@ -37,6 +37,7 @@ module Skeem
37
37
  LAMBDA
38
38
  QUASIQUOTE
39
39
  QUOTE
40
+ SET!
40
41
  UNQUOTE
41
42
  UNQUOTE-SPLICING
42
43
  ].map { |x| [x, x] } .to_h
@@ -1,3 +1,3 @@
1
1
  module Skeem
2
- VERSION = '0.1.03'.freeze
2
+ VERSION = '0.2.00'.freeze
3
3
  end
@@ -8,8 +8,12 @@ module Skeem
8
8
  include DatumDSL
9
9
 
10
10
  context 'Initialization:' do
11
- it 'should be initialized without argument' do
12
- expect { Interpreter.new() }.not_to raise_error
11
+ it 'could be initialized without an argument' do
12
+ expect { Interpreter.new }.not_to raise_error
13
+ end
14
+
15
+ it 'could be initialized with a block argument' do
16
+ expect { Interpreter.new { |interp| } }.not_to raise_error
13
17
  end
14
18
 
15
19
  it 'should have a parser' do
@@ -26,8 +30,9 @@ module Skeem
26
30
 
27
31
  it 'should implement base bindings' do
28
32
  expect(subject.fetch('number?')).to be_kind_of(Primitive::PrimitiveProcedure)
29
- expect(subject.fetch('abs')).to be_kind_of(SkmDefinition)
30
- expect(subject.fetch('abs').expression).to be_kind_of(SkmLambda)
33
+ expect(subject.fetch('abs')).to be_kind_of(SkmLambda)
34
+ expect(subject.fetch('abs').formals.arity).to eq(1)
35
+ expect(subject.fetch('abs').formals.formals[0]).to eq('x')
31
36
  end
32
37
  end # context
33
38
 
@@ -107,7 +112,7 @@ module Skeem
107
112
  context 'Built-in primitives' do
108
113
  it 'should implement variable definition' do
109
114
  result = subject.run('(define x 28)')
110
- expect(result).to be_kind_of(SkmDefinition)
115
+ expect(subject.fetch('x')).to eq(28)
111
116
  end
112
117
 
113
118
  it 'should implement variable reference' do
@@ -125,7 +130,7 @@ SKEEM
125
130
  it 'should implement the simple conditional form' do
126
131
  checks = [
127
132
  ['(if (> 3 2) "yes")', 'yes'],
128
- ['(if (> 2 3) "yes")', :UNDEFINED]
133
+ ['(if (> 2 3) "yes")', SkmUndefined.instance]
129
134
  ]
130
135
  checks.each do |(skeem_expr, expectation)|
131
136
  result = subject.run(skeem_expr)
@@ -213,7 +218,7 @@ SKEEM
213
218
  (if (< x 0) (- x) x)))
214
219
  SKEEM
215
220
  subject.run(source)
216
- procedure = subject.fetch('abs').expression
221
+ procedure = subject.fetch('abs')
217
222
  expect(procedure.arity).to eq(1)
218
223
  result = subject.run('(abs -3)')
219
224
  expect(result).to eq(3)
@@ -231,7 +236,7 @@ SKEEM
231
236
  (if (< x y) x y)))
232
237
  SKEEM
233
238
  subject.run(source)
234
- procedure = subject.fetch('min').expression
239
+ procedure = subject.fetch('min')
235
240
  expect(procedure.arity).to eq(2)
236
241
  result = subject.run('(min 1 2)')
237
242
  expect(result).to eq(1)
@@ -631,7 +636,7 @@ SKEEM
631
636
  expect(result.to_a).to eq(expectation)
632
637
  end
633
638
  end
634
-
639
+
635
640
  it 'should implement the cddr procedure' do
636
641
  checks = [
637
642
  ["(cddr '(2 1))", []]
@@ -640,7 +645,7 @@ SKEEM
640
645
  result = subject.run(skeem_expr)
641
646
  expect(result.to_a).to eq(expectation)
642
647
  end
643
- end
648
+ end
644
649
 
645
650
  it 'should implement the symbol=? procedure' do
646
651
  checks = [
@@ -656,19 +661,27 @@ SKEEM
656
661
  end # context
657
662
 
658
663
  context 'More advanced tests' do
659
- it 'should implement second-order functions' do
664
+ it 'should implement lambda that calls second-order functions' do
660
665
  source = <<-SKEEM
666
+ (define twice
667
+ (lambda (x)
668
+ (* 2 x)))
661
669
  (define compose
662
670
  (lambda (f g)
663
671
  (lambda (x)
664
672
  (f (g x)))))
665
- ((compose list square) 5)
673
+ (define repeat
674
+ (lambda (f)
675
+ (compose f f)))
676
+ ((repeat twice) 5)
666
677
  SKEEM
667
678
  result = subject.run(source)
668
- expect(result.last.car).to eq(25)
679
+ expect(result.last).to eq(20)
669
680
  end
670
681
 
671
- it 'should implement lambda that calls second-order functions' do
682
+ #=begin
683
+ it 'under construction' do
684
+ # Fails
672
685
  source = <<-SKEEM
673
686
  (define twice
674
687
  (lambda (x)
@@ -680,11 +693,12 @@ SKEEM
680
693
  (define repeat
681
694
  (lambda (f)
682
695
  (compose f f)))
683
- ((repeat twice) 5)
696
+ ((repeat (repeat twice)) 5)
684
697
  SKEEM
685
698
  result = subject.run(source)
686
- expect(result.last).to eq(20)
699
+ expect(result.last).to eq(80)
687
700
  end
701
+ #=end
688
702
  end # context
689
703
  end # describe
690
704
  end # module