skeem 0.1.03 → 0.2.00

Sign up to get free protection for your applications and to get access to all the features.
@@ -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