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