skeem 0.0.6 → 0.0.7
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +5 -2
- data/lib/skeem/environment.rb +19 -0
- data/lib/skeem/grammar.rb +7 -0
- data/lib/skeem/interpreter.rb +8 -1
- data/lib/skeem/primitive/primitive_builder.rb +38 -0
- data/lib/skeem/primitive_func.rb +18 -0
- data/lib/skeem/runtime.rb +30 -0
- data/lib/skeem/s_expr_builder.rb +20 -5
- data/lib/skeem/s_expr_nodes.rb +111 -40
- data/lib/skeem/tokenizer.rb +7 -1
- data/lib/skeem/version.rb +1 -1
- data/spec/skeem/environment_spec.rb +27 -0
- data/spec/skeem/interpreter_spec.rb +41 -26
- data/spec/skeem/parser_spec.rb +5 -5
- data/spec/skeem/primitive/primitive_builder_spec.rb +15 -0
- data/spec/skeem/runtime_spec.rb +35 -0
- data/spec/skeem/tokenizer_spec.rb +32 -4
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9907100633d80e63603f094700423139f320600
|
4
|
+
data.tar.gz: 5d4b89ed16eb0c029d98ce02c88eed4cc459404d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edd237e0152cbac2cf89bd0724436db40865fddddec40eb4cd3de595909c8a1c09e424f0903903a3cbacf2e3c6fc63fb98770523acda5ffa2099a382f8eccd41
|
7
|
+
data.tar.gz: 3d9605b4577d2701c18cb675b4ea37907b8d7c990c3a9139b6227931b2b68ddf0be8617b6980bf03e640821a958e5d3419b423c95bda128dc6db2837dda4a21e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## [0.0.7] - 2018-09-12
|
2
|
+
Proof of concept of a primitive function: '+' operator.
|
3
|
+
Demo works but code needs some polishing and testing.
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Class `Environment`. Holds a mapping between symbol names and their associated value.
|
7
|
+
- Class `PrimitiveBuilder`. Builder class that seeds the default environment with primitive functions (now, limited to '+')
|
8
|
+
- Class `PrimitiveFunc` Internal representation of primitive functions.
|
9
|
+
- Class `Runtime`. Holds all context data of the Skeem interpreter.
|
10
|
+
- Methods `SExprBuilder#reduce_proc_call`, `SExprBuilder#reduce_multiple_operands`, SExprBuilder#reduce_last_operand
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- Class `Tokenize` Added support for Scheme semi-colon comments.
|
14
|
+
- File `grammar.rb` Added syntax rules for procedure calling.
|
15
|
+
- File `s_expr_nodes.rb` Class and code refactoring.
|
16
|
+
- File `README.md` Changed demo snippet with example plus operator.
|
17
|
+
|
18
|
+
|
1
19
|
## [0.0.6] - 2018-09-01
|
2
20
|
Initial (minimalistic) interpreter implementation.
|
3
21
|
### Added
|
data/README.md
CHANGED
@@ -31,9 +31,12 @@ At this stage, the gem consists of a bare-bones interpreter.
|
|
31
31
|
require 'skeem'
|
32
32
|
|
33
33
|
schemer = Skeem::Interpreter.new
|
34
|
-
scheme_code
|
34
|
+
scheme_code =<<-SKEEM
|
35
|
+
; Let's try the addition operator
|
36
|
+
(+ 3 4 5)
|
37
|
+
SKEEM
|
35
38
|
result = schemer.run(scheme_code)
|
36
|
-
puts result.value # =>
|
39
|
+
puts result.value # => 12
|
37
40
|
```
|
38
41
|
|
39
42
|
Roadmap:
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
class Environment
|
5
|
+
extend Forwardable
|
6
|
+
def_delegator :@bindings, :empty?
|
7
|
+
|
8
|
+
attr_reader(:bindings)
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@bindings = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def define(anIdentifier, anExpression)
|
15
|
+
raise StandardError, anIdentifier unless anIdentifier.kind_of?(String)
|
16
|
+
@bindings[anIdentifier] = anExpression
|
17
|
+
end
|
18
|
+
end # class
|
19
|
+
end # module
|
data/lib/skeem/grammar.rb
CHANGED
@@ -30,10 +30,17 @@ module Skeem
|
|
30
30
|
rule 'definition' => 'LPAREN DEFINE IDENTIFIER expression RPAREN'
|
31
31
|
rule 'expression' => 'IDENTIFIER'
|
32
32
|
rule 'expression' => 'literal'
|
33
|
+
rule 'expression' => 'procedure_call'
|
33
34
|
rule 'literal' => 'self-evaluating'
|
34
35
|
rule 'self-evaluating' => 'BOOLEAN'
|
35
36
|
rule 'self-evaluating' => 'number'
|
36
37
|
rule 'self-evaluating' => 'STRING_LIT'
|
38
|
+
rule 'procedure_call' => 'LPAREN operator RPAREN'
|
39
|
+
rule('procedure_call' => 'LPAREN operator operand_plus RPAREN').as 'proc_call_args'
|
40
|
+
rule('operand_plus' => 'operand_plus operand').as 'multiple_operands'
|
41
|
+
rule('operand_plus' => 'operand').as 'last_operand'
|
42
|
+
rule 'operator' => 'expression'
|
43
|
+
rule 'operand' => 'expression'
|
37
44
|
rule 'number' => 'INTEGER'
|
38
45
|
rule 'number' => 'REAL'
|
39
46
|
end
|
data/lib/skeem/interpreter.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
require_relative 'parser'
|
2
|
+
require_relative 'environment'
|
3
|
+
require_relative 'runtime'
|
4
|
+
require_relative './primitive/primitive_builder'
|
2
5
|
|
3
6
|
module Skeem
|
4
7
|
class Interpreter
|
8
|
+
include Primitive::PrimitiveBuilder
|
5
9
|
attr_reader(:parser)
|
10
|
+
attr_reader(:runtime)
|
6
11
|
|
7
12
|
def initialize()
|
13
|
+
@runtime = Runtime.new(Environment.new)
|
14
|
+
add_primitives(runtime)
|
8
15
|
end
|
9
16
|
|
10
17
|
def run(source)
|
11
18
|
@parser ||= Parser.new
|
12
19
|
@ptree = parser.parse(source)
|
13
|
-
return @ptree.root.
|
20
|
+
return @ptree.root.evaluate(runtime)
|
14
21
|
end
|
15
22
|
end # class
|
16
23
|
end # module
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../primitive_func'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
module Primitive
|
5
|
+
module PrimitiveBuilder
|
6
|
+
def add_primitives(aRuntime)
|
7
|
+
add_arithmetic(aRuntime)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def add_arithmetic(aRuntime)
|
13
|
+
def_func(aRuntime, create_plus)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_plus()
|
17
|
+
plus_code = ->(runtime, arglist) do
|
18
|
+
operands = arglist.to_eval_enum(runtime)
|
19
|
+
raw_result = operands.reduce(0) do |interim, elem|
|
20
|
+
interim += elem.value
|
21
|
+
end
|
22
|
+
SExprInteger.create(raw_result)
|
23
|
+
end
|
24
|
+
|
25
|
+
['+', plus_code]
|
26
|
+
end
|
27
|
+
|
28
|
+
def def_func(aRuntime, aPair)
|
29
|
+
func = PrimitiveFunc.new(aPair.first, aPair.last)
|
30
|
+
define(aRuntime, func.identifier, func)
|
31
|
+
end
|
32
|
+
|
33
|
+
def define(aRuntime, aKey, anEntry)
|
34
|
+
aRuntime.define(aKey, anEntry)
|
35
|
+
end
|
36
|
+
end # module
|
37
|
+
end # module
|
38
|
+
end # module
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 's_expr_nodes'
|
2
|
+
|
3
|
+
module Skeem
|
4
|
+
class PrimitiveFunc
|
5
|
+
attr_reader(:identifier)
|
6
|
+
attr_reader(:code)
|
7
|
+
|
8
|
+
def initialize(anId, aLambda)
|
9
|
+
@identifier = anId.kind_of?(String) ? SExprIdentifier.create(anId) : anId
|
10
|
+
@code = aLambda
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(aRuntime, aProcedureCall)
|
14
|
+
args = aProcedureCall.operands
|
15
|
+
return @code.call(aRuntime, args)
|
16
|
+
end
|
17
|
+
end # class
|
18
|
+
end # module
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Skeem
|
2
|
+
class Runtime
|
3
|
+
attr_reader(:environment)
|
4
|
+
|
5
|
+
def initialize(anEnvironment)
|
6
|
+
@environment = anEnvironment
|
7
|
+
end
|
8
|
+
|
9
|
+
def include?(anIdentifier)
|
10
|
+
environment.bindings.include?(normalize_key(anIdentifier))
|
11
|
+
end
|
12
|
+
|
13
|
+
def define(aKey, anEntry)
|
14
|
+
environment.define(normalize_key(aKey), anEntry)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def normalize_key(aKey)
|
20
|
+
result = case aKey
|
21
|
+
when String
|
22
|
+
aKey
|
23
|
+
else
|
24
|
+
aKey.evaluate(self).value
|
25
|
+
end
|
26
|
+
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end # class
|
30
|
+
end # module
|
data/lib/skeem/s_expr_builder.rb
CHANGED
@@ -11,11 +11,11 @@ module Skeem
|
|
11
11
|
# nodes) and using a step by step approach.
|
12
12
|
class SExprBuilder < Rley::ParseRep::ASTBaseBuilder
|
13
13
|
Terminal2NodeClass = {
|
14
|
-
'BOOLEAN' =>
|
15
|
-
'IDENTIFIER' =>
|
16
|
-
'INTEGER' =>
|
17
|
-
'REAL' =>
|
18
|
-
'STRING_LIT' =>
|
14
|
+
'BOOLEAN' => SExprBoolean,
|
15
|
+
'IDENTIFIER' => SExprIdentifier,
|
16
|
+
'INTEGER' => SExprInteger,
|
17
|
+
'REAL' => SExprReal,
|
18
|
+
'STRING_LIT' => SExprString
|
19
19
|
}.freeze
|
20
20
|
|
21
21
|
# Create a new AST builder instance.
|
@@ -34,6 +34,21 @@ module Skeem
|
|
34
34
|
def terminal2node
|
35
35
|
Terminal2NodeClass
|
36
36
|
end
|
37
|
+
|
38
|
+
# rule('proc_call_args' => 'LPAREN operator operand_plus RPAREN')
|
39
|
+
def reduce_proc_call_args(_production, aRange, _tokens, theChildren)
|
40
|
+
ProcedureCall.new(aRange, theChildren[1], theChildren[2])
|
41
|
+
end
|
42
|
+
|
43
|
+
# rule('operand_plus' => 'operand_plus operand').as 'multiple_operands'
|
44
|
+
def reduce_multiple_operands(_production, _range, _tokens, theChildren)
|
45
|
+
theChildren[0] << theChildren[1]
|
46
|
+
end
|
47
|
+
|
48
|
+
# rule('operand_plus' => 'operand').as 'last_operand'
|
49
|
+
def reduce_last_operand(_production, _range, _tokens, theChildren)
|
50
|
+
[theChildren.last]
|
51
|
+
end
|
37
52
|
end # class
|
38
53
|
end # module
|
39
54
|
# End of file
|
data/lib/skeem/s_expr_nodes.rb
CHANGED
@@ -1,29 +1,62 @@
|
|
1
1
|
# Classes that implement nodes of Abstract Syntax Trees (AST) representing
|
2
2
|
# Skeem parse results.
|
3
3
|
|
4
|
+
require 'forwardable'
|
5
|
+
|
4
6
|
module Skeem
|
7
|
+
# Abstract class. Generalization of any S-expr element.
|
8
|
+
SExprElement = Struct.new(:position) do
|
9
|
+
def initialize(aPosition)
|
10
|
+
self.position = aPosition
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(_runtime)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def done!()
|
18
|
+
# Do nothing
|
19
|
+
end
|
20
|
+
|
21
|
+
# Abstract method.
|
22
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
23
|
+
# @param _visitor[ParseTreeVisitor] the visitor
|
24
|
+
def accept(_visitor)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
end # struct
|
28
|
+
|
5
29
|
# Abstract class. Root of class hierarchy needed for Interpreter
|
6
30
|
# design pattern
|
7
|
-
|
31
|
+
class SExprTerminal < SExprElement
|
32
|
+
attr_reader :token
|
33
|
+
attr_reader :value
|
34
|
+
|
8
35
|
def initialize(aToken, aPosition)
|
9
|
-
|
10
|
-
|
36
|
+
super(aPosition)
|
37
|
+
@token = aToken
|
11
38
|
init_value(aToken.lexeme)
|
12
39
|
end
|
13
40
|
|
41
|
+
def self.create(aValue)
|
42
|
+
lightweight = self.allocate
|
43
|
+
lightweight.init_value(aValue)
|
44
|
+
return lightweight
|
45
|
+
end
|
46
|
+
|
14
47
|
# This method can be overriden
|
15
48
|
def init_value(aValue)
|
16
|
-
|
49
|
+
@value = aValue
|
17
50
|
end
|
18
51
|
|
19
52
|
def symbol()
|
20
53
|
token.terminal
|
21
54
|
end
|
22
55
|
|
23
|
-
def
|
56
|
+
def evaluate(_runtime)
|
24
57
|
return self
|
25
58
|
end
|
26
|
-
|
59
|
+
|
27
60
|
def done!()
|
28
61
|
# Do nothing
|
29
62
|
end
|
@@ -33,44 +66,65 @@ module Skeem
|
|
33
66
|
def accept(aVisitor)
|
34
67
|
aVisitor.visit_terminal(self)
|
35
68
|
end
|
36
|
-
end
|
69
|
+
end # class
|
70
|
+
|
71
|
+
class SExprBoolean < SExprTerminal
|
72
|
+
end # class
|
37
73
|
|
38
|
-
class
|
74
|
+
class SExprNumber < SExprTerminal
|
39
75
|
end # class
|
40
|
-
|
41
|
-
class
|
42
|
-
end # class
|
43
|
-
|
44
|
-
class SExprRealNode < SExprNumberNode
|
76
|
+
|
77
|
+
class SExprReal < SExprTerminal
|
45
78
|
end # class
|
46
|
-
|
47
|
-
class
|
79
|
+
|
80
|
+
class SExprInteger < SExprReal
|
48
81
|
end # class
|
49
|
-
|
50
|
-
class
|
82
|
+
|
83
|
+
class SExprString < SExprTerminal
|
51
84
|
# Override
|
52
85
|
def init_value(aValue)
|
53
|
-
|
54
|
-
end
|
86
|
+
super(aValue.dup)
|
87
|
+
end
|
55
88
|
end # class
|
56
|
-
|
57
|
-
class
|
89
|
+
|
90
|
+
class SExprIdentifier < SExprTerminal
|
58
91
|
# Override
|
59
92
|
def init_value(aValue)
|
60
|
-
|
61
|
-
end
|
93
|
+
super(aValue.dup)
|
94
|
+
end
|
62
95
|
end # class
|
63
|
-
|
64
|
-
=begin
|
65
|
-
class SExprCompositeNode
|
66
|
-
attr_accessor(:children)
|
67
|
-
attr_accessor(:symbol)
|
68
|
-
attr_accessor(:position)
|
69
96
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
97
|
+
class SExprReserved < SExprIdentifier
|
98
|
+
end # class
|
99
|
+
|
100
|
+
|
101
|
+
class SExprList < SExprElement
|
102
|
+
attr_accessor(:members)
|
103
|
+
extend Forwardable
|
104
|
+
|
105
|
+
def_delegator :@members, :first, :empty?
|
106
|
+
|
107
|
+
def initialize()
|
108
|
+
super(nil)
|
109
|
+
@members = []
|
110
|
+
end
|
111
|
+
|
112
|
+
def rest()
|
113
|
+
members.slice(1..-1)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Factory method.
|
117
|
+
# Construct an Enumerator that will return iteratively the result
|
118
|
+
# of 'evaluate' method of each members of self.
|
119
|
+
def to_eval_enum(aRuntime)
|
120
|
+
elements = self.members
|
121
|
+
|
122
|
+
new_enum = Enumerator.new do |result|
|
123
|
+
context = aRuntime
|
124
|
+
elements.each { |elem| result << elem.evaluate(context) }
|
125
|
+
end
|
126
|
+
|
127
|
+
new_enum
|
74
128
|
end
|
75
129
|
|
76
130
|
# Part of the 'visitee' role in Visitor design pattern.
|
@@ -78,21 +132,38 @@ module Skeem
|
|
78
132
|
def accept(aVisitor)
|
79
133
|
aVisitor.visit_nonterminal(self)
|
80
134
|
end
|
81
|
-
|
135
|
+
|
82
136
|
def done!()
|
83
137
|
# Do nothing
|
84
138
|
end
|
85
139
|
|
86
|
-
alias
|
140
|
+
alias children members
|
141
|
+
alias subnodes members
|
142
|
+
alias head first
|
143
|
+
alias tail rest
|
87
144
|
end # class
|
88
145
|
|
89
|
-
class
|
90
|
-
|
91
|
-
|
146
|
+
class ProcedureCall < SExprElement
|
147
|
+
attr_reader :operator
|
148
|
+
attr_reader :operands
|
149
|
+
|
150
|
+
def initialize(aPosition, anOperator, theOperands)
|
151
|
+
super(aPosition)
|
152
|
+
@operator = anOperator
|
153
|
+
@operands = SExprList.new
|
154
|
+
@operands.instance_variable_set(:@members, theOperands)
|
155
|
+
end
|
156
|
+
|
157
|
+
def evaluate(aRuntime)
|
158
|
+
procedure_key = operator.evaluate(aRuntime)
|
159
|
+
err = StandardError
|
160
|
+
err_msg = "Unknown function #{procedure_key}"
|
161
|
+
raise err, err_msg unless aRuntime.include?(procedure_key.value)
|
162
|
+
procedure = aRuntime.environment.bindings[procedure_key.value]
|
163
|
+
result = procedure.call(aRuntime, self)
|
92
164
|
end
|
93
165
|
|
94
|
-
alias
|
166
|
+
alias children operands
|
95
167
|
end # class
|
96
|
-
=end
|
97
168
|
end # module
|
98
169
|
# End of file
|
data/lib/skeem/tokenizer.rb
CHANGED
@@ -185,6 +185,7 @@ module Skeem
|
|
185
185
|
|
186
186
|
loop do
|
187
187
|
ws_found = false
|
188
|
+
cmt_found = false
|
188
189
|
found = scanner.skip(/[ \t\f]+/)
|
189
190
|
ws_found = true if found
|
190
191
|
found = scanner.skip(/(?:\r\n)|\r|\n/)
|
@@ -193,7 +194,12 @@ module Skeem
|
|
193
194
|
@lineno += 1
|
194
195
|
@line_start = scanner.pos
|
195
196
|
end
|
196
|
-
|
197
|
+
next_ch = scanner.peek(1)
|
198
|
+
if next_ch == ';'
|
199
|
+
cmt_found = true
|
200
|
+
scanner.skip(/;[^\r\n]*(?:(?:\r\n)|\r|\n)?/)
|
201
|
+
end
|
202
|
+
break unless ws_found or cmt_found
|
197
203
|
end
|
198
204
|
|
199
205
|
curr_pos = scanner.pos
|
data/lib/skeem/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../spec_helper' # Use the RSpec framework
|
2
|
+
require_relative '../../lib/skeem/environment' # Load the class under test
|
3
|
+
|
4
|
+
module Skeem
|
5
|
+
describe Environment do
|
6
|
+
context 'Initialization:' do
|
7
|
+
it 'should be initialized without argument' do
|
8
|
+
expect { Environment.new() }.not_to raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should have no bindings' do
|
12
|
+
expect(subject.bindings).to be_empty
|
13
|
+
end
|
14
|
+
end # context
|
15
|
+
|
16
|
+
context 'Provided services:' do
|
17
|
+
it 'should add entries' do
|
18
|
+
entry = double('dummy')
|
19
|
+
subject.define('dummy', entry)
|
20
|
+
expect(subject.bindings.size).to eq(1)
|
21
|
+
expect(subject.bindings['dummy']).not_to be_nil
|
22
|
+
expect(subject.bindings['dummy']).to eq(entry)
|
23
|
+
end
|
24
|
+
end # context
|
25
|
+
|
26
|
+
end # describe
|
27
|
+
end # module
|
@@ -8,11 +8,19 @@ module Skeem
|
|
8
8
|
expect { Interpreter.new() }.not_to raise_error
|
9
9
|
end
|
10
10
|
|
11
|
-
it 'should not have
|
11
|
+
it 'should not have a parser' do
|
12
12
|
expect(subject.parser).to be_nil
|
13
13
|
end
|
14
|
+
|
15
|
+
it 'should have a runtime object' do
|
16
|
+
expect(subject.runtime).to be_kind_of(Runtime)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should come with built-in functions' do
|
20
|
+
expect(subject.runtime.environment).not_to be_empty
|
21
|
+
end
|
14
22
|
end # context
|
15
|
-
|
23
|
+
|
16
24
|
context 'Interpreting self-evaluating expressions' do
|
17
25
|
it 'should evaluate isolated booleans' do
|
18
26
|
samples = [
|
@@ -23,38 +31,37 @@ module Skeem
|
|
23
31
|
]
|
24
32
|
samples.each do |source, predicted|
|
25
33
|
result = subject.run(source)
|
26
|
-
expect(result).to be_kind_of(
|
34
|
+
expect(result).to be_kind_of(SExprBoolean)
|
27
35
|
expect(result.value).to eq(predicted)
|
28
36
|
end
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
it 'should evaluate isolated integers' do
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should evaluate isolated integers' do
|
33
40
|
samples = [
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
['0', 0],
|
42
|
+
['3', 3],
|
43
|
+
['-3', -3],
|
44
|
+
['+12345', 12345],
|
45
|
+
['-12345', -12345]
|
46
|
+
]
|
40
47
|
samples.each do |source, predicted|
|
41
48
|
result = subject.run(source)
|
42
|
-
expect(result).to be_kind_of(
|
49
|
+
expect(result).to be_kind_of(SExprInteger)
|
43
50
|
expect(result.value).to eq(predicted)
|
44
51
|
end
|
45
52
|
end
|
46
|
-
|
53
|
+
|
47
54
|
it 'should evaluate isolated real numbers' do
|
48
55
|
samples = [
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
56
|
+
['0.0', 0.0],
|
57
|
+
['3.14', 3.14],
|
58
|
+
['-3.14', -3.14],
|
59
|
+
['+123e+45', 123e+45],
|
60
|
+
['-123e-45', -123e-45]
|
61
|
+
]
|
55
62
|
samples.each do |source, predicted|
|
56
63
|
result = subject.run(source)
|
57
|
-
expect(result).to be_kind_of(
|
64
|
+
expect(result).to be_kind_of(SExprReal)
|
58
65
|
expect(result.value).to eq(predicted)
|
59
66
|
end
|
60
67
|
end
|
@@ -65,20 +72,28 @@ module Skeem
|
|
65
72
|
]
|
66
73
|
samples.each do |source, predicted|
|
67
74
|
result = subject.run(source)
|
68
|
-
expect(result).to be_kind_of(
|
75
|
+
expect(result).to be_kind_of(SExprString)
|
69
76
|
expect(result.value).to eq(predicted)
|
70
77
|
end
|
71
78
|
end
|
72
79
|
|
73
80
|
it 'should evaluate isolated identifiers' do
|
74
81
|
samples = [
|
75
|
-
|
76
|
-
|
82
|
+
['the-word-recursion-has-many-meanings',
|
83
|
+
'the-word-recursion-has-many-meanings']
|
84
|
+
]
|
77
85
|
samples.each do |source, predicted|
|
78
86
|
result = subject.run(source)
|
79
|
-
expect(result).to be_kind_of(
|
87
|
+
expect(result).to be_kind_of(SExprIdentifier)
|
80
88
|
expect(result.value).to eq(predicted)
|
81
89
|
end
|
82
90
|
end
|
91
|
+
|
92
|
+
it 'should support procedure calls' do
|
93
|
+
result = subject.run('(+ 3 4)')
|
94
|
+
expect(result).to be_kind_of(SExprInteger)
|
95
|
+
expect(result.value).to eq(7)
|
96
|
+
end
|
97
|
+
end # context
|
83
98
|
end # describe
|
84
99
|
end # module
|
data/spec/skeem/parser_spec.rb
CHANGED
@@ -23,7 +23,7 @@ module Skeem
|
|
23
23
|
]
|
24
24
|
samples.each do |source, predicted|
|
25
25
|
ptree = subject.parse(source)
|
26
|
-
expect(ptree.root).to be_kind_of(
|
26
|
+
expect(ptree.root).to be_kind_of(SExprBoolean)
|
27
27
|
expect(ptree.root.value).to eq(predicted)
|
28
28
|
end
|
29
29
|
end
|
@@ -38,7 +38,7 @@ module Skeem
|
|
38
38
|
]
|
39
39
|
samples.each do |source, predicted|
|
40
40
|
ptree = subject.parse(source)
|
41
|
-
expect(ptree.root).to be_kind_of(
|
41
|
+
expect(ptree.root).to be_kind_of(SExprInteger)
|
42
42
|
expect(ptree.root.value).to eq(predicted)
|
43
43
|
end
|
44
44
|
end
|
@@ -53,7 +53,7 @@ module Skeem
|
|
53
53
|
]
|
54
54
|
samples.each do |source, predicted|
|
55
55
|
ptree = subject.parse(source)
|
56
|
-
expect(ptree.root).to be_kind_of(
|
56
|
+
expect(ptree.root).to be_kind_of(SExprReal)
|
57
57
|
expect(ptree.root.value).to eq(predicted)
|
58
58
|
end
|
59
59
|
end
|
@@ -64,7 +64,7 @@ module Skeem
|
|
64
64
|
]
|
65
65
|
samples.each do |source, predicted|
|
66
66
|
ptree = subject.parse(source)
|
67
|
-
expect(ptree.root).to be_kind_of(
|
67
|
+
expect(ptree.root).to be_kind_of(SExprString)
|
68
68
|
expect(ptree.root.value).to eq(predicted)
|
69
69
|
end
|
70
70
|
end
|
@@ -75,7 +75,7 @@ module Skeem
|
|
75
75
|
]
|
76
76
|
samples.each do |source, predicted|
|
77
77
|
ptree = subject.parse(source)
|
78
|
-
expect(ptree.root).to be_kind_of(
|
78
|
+
expect(ptree.root).to be_kind_of(SExprIdentifier)
|
79
79
|
expect(ptree.root.value).to eq(predicted)
|
80
80
|
end
|
81
81
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative '../../spec_helper' # Use the RSpec framework
|
2
|
+
|
3
|
+
# Load the class under test
|
4
|
+
require_relative '../../../lib/skeem/primitive/primitive_builder'
|
5
|
+
|
6
|
+
module Skeem
|
7
|
+
describe Primitive::PrimitiveBuilder do
|
8
|
+
|
9
|
+
context 'Initialization:' do
|
10
|
+
it 'should be initialized without argument' do
|
11
|
+
expect { Interpreter.new() }.not_to raise_error
|
12
|
+
end
|
13
|
+
end # context
|
14
|
+
end # describe
|
15
|
+
end # module
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../spec_helper' # Use the RSpec framework
|
2
|
+
require_relative '../../lib/skeem/environment' # Load the class under test
|
3
|
+
require_relative '../../lib/skeem/runtime' # Load the class under test
|
4
|
+
|
5
|
+
module Skeem
|
6
|
+
describe Runtime do
|
7
|
+
let(:some_env) { Environment.new }
|
8
|
+
subject { Runtime.new(some_env) }
|
9
|
+
|
10
|
+
context 'Initialization:' do
|
11
|
+
it 'should be initialized with an environment' do
|
12
|
+
expect { Runtime.new(Environment.new) }.not_to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should know the environment' do
|
16
|
+
expect(subject.environment).to eq(some_env)
|
17
|
+
end
|
18
|
+
end # context
|
19
|
+
|
20
|
+
context 'Provided services:' do
|
21
|
+
it 'should add entries to the environment' do
|
22
|
+
entry = double('dummy')
|
23
|
+
subject.define('dummy', entry)
|
24
|
+
expect(subject.environment.bindings.size).to eq(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should know the keys in the environment' do
|
28
|
+
expect(subject.include?('dummy')).to be_falsey
|
29
|
+
entry = double('dummy')
|
30
|
+
subject.define('dummy', entry)
|
31
|
+
expect(subject.include?('dummy')).to be_truthy
|
32
|
+
end
|
33
|
+
end # context
|
34
|
+
end # describe
|
35
|
+
end # module
|
@@ -101,7 +101,7 @@ module Skeem
|
|
101
101
|
it 'should tokenize strings' do
|
102
102
|
examples = [
|
103
103
|
# Some examples taken from R7RS document
|
104
|
-
'"Hello world
|
104
|
+
'"Hello, world"',
|
105
105
|
'"The word \"recursion\" has many meanings."'
|
106
106
|
]
|
107
107
|
|
@@ -114,15 +114,12 @@ module Skeem
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end # context
|
117
|
-
|
118
|
-
|
119
117
|
# For later:
|
120
118
|
# "Another example:\ntwo lines of text"
|
121
119
|
# "Here's text \
|
122
120
|
# containing just one line"
|
123
121
|
# "\x03B1; is named GREEK SMALL LETTER ALPHA."
|
124
122
|
|
125
|
-
|
126
123
|
context 'Identifier recognition:' do
|
127
124
|
it 'should tokenize identifier' do
|
128
125
|
examples = [
|
@@ -142,6 +139,37 @@ module Skeem
|
|
142
139
|
end
|
143
140
|
end
|
144
141
|
end # context
|
142
|
+
|
143
|
+
context 'Semi-colon comments:' do
|
144
|
+
it 'should skip heading comments' do
|
145
|
+
input = "; Starting comment\n \"Some text\""
|
146
|
+
subject.reinitialize(input)
|
147
|
+
token = subject.tokens.first
|
148
|
+
expect(token.terminal).to eq('STRING_LIT')
|
149
|
+
expect(token.lexeme).to eq('Some text')
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should skip trailing comments' do
|
153
|
+
input = "\"Some text\"; Trailing comment"
|
154
|
+
subject.reinitialize(input)
|
155
|
+
token = subject.tokens.first
|
156
|
+
expect(token.terminal).to eq('STRING_LIT')
|
157
|
+
expect(token.lexeme).to eq('Some text')
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should skip embedded comments' do
|
161
|
+
input = "\"First text\"; Middle comment\n\"Second text\""
|
162
|
+
subject.reinitialize(input)
|
163
|
+
tokens = subject.tokens
|
164
|
+
expect(tokens.size).to eq(2)
|
165
|
+
token = tokens[0]
|
166
|
+
expect(token.terminal).to eq('STRING_LIT')
|
167
|
+
expect(token.lexeme).to eq('First text')
|
168
|
+
token = tokens[1]
|
169
|
+
expect(token.terminal).to eq('STRING_LIT')
|
170
|
+
expect(token.lexeme).to eq('Second text')
|
171
|
+
end
|
172
|
+
end
|
145
173
|
=begin
|
146
174
|
context 'Scanning Scheme sample code' do
|
147
175
|
it 'should read examples from lis.py page' do
|
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.
|
4
|
+
version: 0.0.7
|
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-
|
11
|
+
date: 2018-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -85,17 +85,24 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- appveyor.yml
|
87
87
|
- lib/skeem.rb
|
88
|
+
- lib/skeem/environment.rb
|
88
89
|
- lib/skeem/grammar.rb
|
89
90
|
- lib/skeem/interpreter.rb
|
90
91
|
- lib/skeem/parser.rb
|
92
|
+
- lib/skeem/primitive/primitive_builder.rb
|
93
|
+
- lib/skeem/primitive_func.rb
|
94
|
+
- lib/skeem/runtime.rb
|
91
95
|
- lib/skeem/s_expr_builder.rb
|
92
96
|
- lib/skeem/s_expr_nodes.rb
|
93
97
|
- lib/skeem/stoken.rb
|
94
98
|
- lib/skeem/tokenizer.rb
|
95
99
|
- lib/skeem/version.rb
|
96
100
|
- skeem.gemspec
|
101
|
+
- spec/skeem/environment_spec.rb
|
97
102
|
- spec/skeem/interpreter_spec.rb
|
98
103
|
- spec/skeem/parser_spec.rb
|
104
|
+
- spec/skeem/primitive/primitive_builder_spec.rb
|
105
|
+
- spec/skeem/runtime_spec.rb
|
99
106
|
- spec/skeem/tokenizer_spec.rb
|
100
107
|
- spec/skeem_spec.rb
|
101
108
|
- spec/spec_helper.rb
|
@@ -126,7 +133,10 @@ specification_version: 4
|
|
126
133
|
summary: Skeem is an interpreter of a subset of the Scheme programming language. Scheme
|
127
134
|
is a descendent of the Lisp language.
|
128
135
|
test_files:
|
136
|
+
- spec/skeem/environment_spec.rb
|
129
137
|
- spec/skeem/interpreter_spec.rb
|
130
138
|
- spec/skeem/parser_spec.rb
|
139
|
+
- spec/skeem/primitive/primitive_builder_spec.rb
|
140
|
+
- spec/skeem/runtime_spec.rb
|
131
141
|
- spec/skeem/tokenizer_spec.rb
|
132
142
|
- spec/skeem_spec.rb
|