iode 0.0.1 → 0.0.2
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/README.md +78 -14
- data/lib/iode.rb +4 -1
- data/lib/iode/core.rb +53 -0
- data/lib/iode/{built_ins.rb → core/comparisons.rb} +17 -39
- data/lib/iode/core/lists.rb +35 -0
- data/lib/iode/core/math.rb +41 -0
- data/lib/iode/core/output.rb +33 -0
- data/lib/iode/interpreter.rb +32 -8
- data/lib/iode/scope.rb +28 -9
- data/lib/iode/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef8311e9162a113a3aff4d5741524ac2bd6eafcb
|
4
|
+
data.tar.gz: 56634c23acfedee8d05d6bc38a78392dc9df0865
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 295055612ba622c3f84503f957e996a9996f32201bf934c571c63a17c93144dad7fe7fa206abfe469c5f4dc0e34262c12f1b19a9e69c24dabe9a9c401a25f837
|
7
|
+
data.tar.gz: fc8b1824b95dbf0583917390f64b4df9614f320e4d0c972d6c8e8d81f84518a9be58d5b2e570feaaf13d5178efddbbb6edee38f0368e03a9cda27be6a6455db9
|
data/README.md
CHANGED
@@ -53,11 +53,13 @@ with Ruby.
|
|
53
53
|
``` ruby
|
54
54
|
require "iode"
|
55
55
|
|
56
|
-
|
56
|
+
result = Iode.run <<-PROG
|
57
57
|
(if ((lambda (x) x) false)
|
58
58
|
"x = true"
|
59
59
|
"x = false")
|
60
60
|
PROG
|
61
|
+
|
62
|
+
puts result
|
61
63
|
```
|
62
64
|
|
63
65
|
The above code creates a lambda function that simply acts like the identity
|
@@ -65,7 +67,7 @@ function (i.e. it returns its input). That lambda is then immediately applied
|
|
65
67
|
with the input `false`, thereby returning `false`.
|
66
68
|
|
67
69
|
The `if` form evaluates false and therefore evaluates the else part of the
|
68
|
-
`if`, returning the string
|
70
|
+
`if`, returning the string "x = false".
|
69
71
|
|
70
72
|
Here's another example showing how you can pass values from Ruby into Iode.
|
71
73
|
|
@@ -75,12 +77,12 @@ require "iode"
|
|
75
77
|
prog = Iode.run <<-PROG
|
76
78
|
(lambda (x)
|
77
79
|
(if x
|
78
|
-
42
|
79
|
-
7))
|
80
|
+
(puts 42)
|
81
|
+
(puts 7)))
|
80
82
|
PROG
|
81
83
|
|
82
|
-
prog
|
83
|
-
prog
|
84
|
+
prog.call(false) #=> 7
|
85
|
+
prog.call(true) #=> 42
|
84
86
|
```
|
85
87
|
|
86
88
|
This works because internally, Iode lambdas as represented as Ruby Procs.
|
@@ -93,18 +95,80 @@ require "iode"
|
|
93
95
|
|
94
96
|
prog = Iode.run <<-PROG
|
95
97
|
(lambda (f)
|
96
|
-
(
|
97
|
-
42
|
98
|
-
7))
|
98
|
+
(f 42))
|
99
99
|
PROG
|
100
100
|
|
101
|
-
prog.call(->(){
|
102
|
-
prog.call(->(){
|
101
|
+
prog.call(->(x){ x * 2 }) #=> 84
|
102
|
+
prog.call(->(x){ x + 4 }) #=> 46
|
103
|
+
```
|
104
|
+
|
105
|
+
Of course, functions can be defined recursively too.
|
106
|
+
|
107
|
+
``` lisp
|
108
|
+
;; Recursive function example.
|
109
|
+
(def loop
|
110
|
+
(lambda (n)
|
111
|
+
(if (= n 0)
|
112
|
+
(quote done)
|
113
|
+
(progn
|
114
|
+
(puts n)
|
115
|
+
(loop (- n 1))))))
|
116
|
+
|
117
|
+
|
118
|
+
(loop 20)
|
119
|
+
```
|
120
|
+
|
121
|
+
The above code will print 20 through 1 to the screen and finally return the
|
122
|
+
Symbol `:done` to Ruby (quoted Iode Symbols are also Ruby Symbols). Note that
|
123
|
+
I haven't yet done tail call elimination.
|
124
|
+
|
125
|
+
Similarly, closures can be returned from functions.
|
126
|
+
|
127
|
+
``` lisp
|
128
|
+
;; Provides partial application of a function
|
129
|
+
(def curry
|
130
|
+
(lambda (fn a)
|
131
|
+
(lambda (b) (fn a b))))
|
132
|
+
|
133
|
+
((curry + 2) 3) ; 5
|
134
|
+
```
|
135
|
+
|
136
|
+
## Development
|
137
|
+
|
138
|
+
Iode (in this Ruby incarnation) is literally a few hours old at the time I
|
139
|
+
write this. Much is still not yet developed. However, you may poke around in
|
140
|
+
the internals and find some interesting this. A string of source code takes
|
141
|
+
this path to being executed as code.
|
142
|
+
|
143
|
+
String(source) -> Reader(data) -> Interpreter(data) -> Core(data) -> Result
|
144
|
+
|
145
|
+
The source string is parsed by the Reader into native lisp data (using Ruby
|
146
|
+
data types, like Array and Symbol). The data representation is then given to
|
147
|
+
the Interpreter's eval method, which is a simple recursive algorithm mapping
|
148
|
+
the elements in the data to executable types (e.g. `[:lambda, [], 42]` becomes
|
149
|
+
a Proc). Variables are held in the Scope class, which is able to chain Scopes
|
150
|
+
together to create lexical closures. Core functions are registered as mixins
|
151
|
+
in the Core module.
|
152
|
+
|
153
|
+
If you want to add a native Ruby function to be applied like an Iode function,
|
154
|
+
put it in a Module and register it into `Iode::Core`:
|
155
|
+
|
156
|
+
``` ruby
|
157
|
+
require "iode"
|
158
|
+
|
159
|
+
module MyFunctions
|
160
|
+
def example(a, b)
|
161
|
+
a + b
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
Iode::Core.register MyFunctions
|
166
|
+
|
167
|
+
Iode.run('(example 7 5)') #=> 12
|
103
168
|
```
|
104
169
|
|
105
|
-
I
|
106
|
-
|
107
|
-
though all the wiring is there, I just need to define a core library.
|
170
|
+
Once I have namespacing done, you'll be able to write actual Iode code in
|
171
|
+
separate files and have them loaded under a namespace.
|
108
172
|
|
109
173
|
## Copyright & Licensing
|
110
174
|
|
data/lib/iode.rb
CHANGED
@@ -15,8 +15,11 @@
|
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
17
|
require "iode/version"
|
18
|
+
require "iode/core/comparisons"
|
19
|
+
require "iode/core/lists"
|
20
|
+
require "iode/core/math"
|
21
|
+
require "iode/core/output"
|
18
22
|
require "iode/scope"
|
19
|
-
require "iode/built_ins"
|
20
23
|
require "iode/interpreter"
|
21
24
|
require "iode/reader"
|
22
25
|
|
data/lib/iode/core.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# iode: core.rb
|
2
|
+
#
|
3
|
+
# Copyright 2014 Chris Corbyn <chris@w3style.co.uk>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Iode
|
18
|
+
# Iode Core language definitions.
|
19
|
+
#
|
20
|
+
# This is simply a module that exposes all its instance methods as functions.
|
21
|
+
#
|
22
|
+
# The actual functions are mixed in by other modules.
|
23
|
+
module Core
|
24
|
+
class << self
|
25
|
+
# Register a new library of functions into the global definitions.
|
26
|
+
#
|
27
|
+
# @param [Module] base
|
28
|
+
# the module to register
|
29
|
+
def register(base)
|
30
|
+
include(base).tap{@definitions = nil}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get a singleton instance of all the core definitions.
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
# core functions and variables
|
37
|
+
def definitions
|
38
|
+
@definitions ||=
|
39
|
+
Hash[
|
40
|
+
names.zip(
|
41
|
+
names.map(&method(:instance_method)).map{|m| m.bind(self)}
|
42
|
+
)
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def names
|
49
|
+
instance_methods
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# iode:
|
1
|
+
# iode: core/comparisons.rb
|
2
2
|
#
|
3
3
|
# Copyright 2014 Chris Corbyn <chris@w3style.co.uk>
|
4
4
|
#
|
@@ -14,48 +14,26 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
-
|
18
|
-
module BuiltIns
|
19
|
-
def car(list)
|
20
|
-
v, *_ = list
|
21
|
-
v
|
22
|
-
end
|
17
|
+
require "iode/core"
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
car(cdr(list))
|
31
|
-
end
|
32
|
-
|
33
|
-
def cddr(list)
|
34
|
-
cdr(cdr(list))
|
35
|
-
end
|
36
|
-
|
37
|
-
def caddr(list)
|
38
|
-
car(cddr(list))
|
39
|
-
end
|
40
|
-
|
41
|
-
def cdddr(list)
|
42
|
-
cdr(cddr(list))
|
43
|
-
end
|
44
|
-
|
45
|
-
def cadddr(list)
|
46
|
-
car(cdddr(list))
|
47
|
-
end
|
19
|
+
module Iode
|
20
|
+
module Core
|
21
|
+
module Comparisons
|
22
|
+
def >(a, b)
|
23
|
+
a > b
|
24
|
+
end
|
48
25
|
|
49
|
-
|
50
|
-
|
51
|
-
|
26
|
+
def <(a, b)
|
27
|
+
a < b
|
28
|
+
end
|
52
29
|
|
53
|
-
|
54
|
-
|
55
|
-
|
30
|
+
def ==(a, b)
|
31
|
+
a == b
|
32
|
+
end
|
56
33
|
|
57
|
-
|
58
|
-
cdr(cddddr(list))
|
34
|
+
alias_method :"=", :==
|
59
35
|
end
|
60
36
|
end
|
61
37
|
end
|
38
|
+
|
39
|
+
Iode::Core.register Iode::Core::Comparisons
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# iode: core/lists.rb
|
2
|
+
#
|
3
|
+
# Copyright 2014 Chris Corbyn <chris@w3style.co.uk>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require "iode/core"
|
18
|
+
|
19
|
+
module Iode
|
20
|
+
module Core
|
21
|
+
module Lists
|
22
|
+
def car(list)
|
23
|
+
v, *_ = list
|
24
|
+
v
|
25
|
+
end
|
26
|
+
|
27
|
+
def cdr(list)
|
28
|
+
_, *v = list
|
29
|
+
v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Iode::Core.register Iode::Core::Lists
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# iode: core/math.rb
|
2
|
+
#
|
3
|
+
# Copyright 2014 Chris Corbyn <chris@w3style.co.uk>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require "iode/core"
|
18
|
+
|
19
|
+
module Iode
|
20
|
+
module Core
|
21
|
+
module Math
|
22
|
+
def *(*args)
|
23
|
+
args.reduce(:*)
|
24
|
+
end
|
25
|
+
|
26
|
+
def /(*args)
|
27
|
+
args.reduce(:/)
|
28
|
+
end
|
29
|
+
|
30
|
+
def +(*args)
|
31
|
+
args.reduce(:+)
|
32
|
+
end
|
33
|
+
|
34
|
+
def -(*args)
|
35
|
+
args.reduce(:-)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Iode::Core.register Iode::Core::Math
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# iode: core/output.rb
|
2
|
+
#
|
3
|
+
# Copyright 2014 Chris Corbyn <chris@w3style.co.uk>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require "iode/core"
|
18
|
+
|
19
|
+
module Iode
|
20
|
+
module Core
|
21
|
+
module Output
|
22
|
+
def puts(*args, &block)
|
23
|
+
Kernel.puts(*args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def p(*args, &block)
|
27
|
+
Kernel.p(*args, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Iode::Core.register Iode::Core::Output
|
data/lib/iode/interpreter.rb
CHANGED
@@ -17,8 +17,6 @@
|
|
17
17
|
module Iode
|
18
18
|
# Iode interpreter, providing the central #eval function.
|
19
19
|
class Interpreter
|
20
|
-
include BuiltIns # FIXME: Remove this coupling!
|
21
|
-
|
22
20
|
# Create a new Interpreter with a given Scope.
|
23
21
|
#
|
24
22
|
# @param [Scope] scope
|
@@ -27,6 +25,30 @@ module Iode
|
|
27
25
|
@env = scope
|
28
26
|
end
|
29
27
|
|
28
|
+
# Get the head (car) of a list.
|
29
|
+
#
|
30
|
+
# @param [Array] list
|
31
|
+
# the list to return the car from
|
32
|
+
#
|
33
|
+
# @return [Object]
|
34
|
+
# the first element in list
|
35
|
+
def car(list)
|
36
|
+
v, *_ = list
|
37
|
+
v
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the tail (cdr) of a list.
|
41
|
+
#
|
42
|
+
# @param [Array] list
|
43
|
+
# the list to return the cdr from
|
44
|
+
#
|
45
|
+
# @return [Array]
|
46
|
+
# all but the head of the list
|
47
|
+
def cdr(list)
|
48
|
+
_, *v = list
|
49
|
+
v
|
50
|
+
end
|
51
|
+
|
30
52
|
# Create an explicit progn block.
|
31
53
|
#
|
32
54
|
# A progn encapsulates a list of S-Expressions to be evaluated in sequence.
|
@@ -93,19 +115,21 @@ module Iode
|
|
93
115
|
when nil
|
94
116
|
nil
|
95
117
|
when :quote
|
96
|
-
|
118
|
+
car(cdr(sexp))
|
97
119
|
when :if
|
98
|
-
if eval(
|
99
|
-
eval(
|
120
|
+
if eval(car(cdr(sexp)))
|
121
|
+
eval(car(cdr(cdr(sexp))))
|
100
122
|
else
|
101
|
-
eval(
|
123
|
+
eval(car(cdr(cdr(cdr(sexp)))))
|
102
124
|
end
|
103
125
|
when :progn
|
104
126
|
progn(*cdr(sexp))
|
105
127
|
when :set!
|
106
|
-
@env[
|
128
|
+
@env[car(cdr(sexp))] = eval(car(cdr(cdr(sexp))))
|
129
|
+
when :def
|
130
|
+
@env.define(car(cdr(sexp)), eval(car(cdr(cdr(sexp)))))
|
107
131
|
when :lambda
|
108
|
-
lambda(
|
132
|
+
lambda(car(cdr(sexp)), *cdr(cdr(sexp)))
|
109
133
|
else
|
110
134
|
apply(eval(car(sexp)), cdr(sexp).map(&method(:eval)))
|
111
135
|
end
|
data/lib/iode/scope.rb
CHANGED
@@ -26,12 +26,26 @@ module Iode
|
|
26
26
|
#
|
27
27
|
# @param [Scope] parent
|
28
28
|
# the parent scope, if any
|
29
|
-
def initialize(values =
|
30
|
-
@values = values
|
29
|
+
def initialize(values = nil, parent = nil)
|
30
|
+
@values = values || Core.definitions
|
31
31
|
@parent = parent
|
32
32
|
end
|
33
33
|
|
34
|
-
#
|
34
|
+
# Define a new variable.
|
35
|
+
#
|
36
|
+
# @param [Symbol] k
|
37
|
+
# the name of the variable to define
|
38
|
+
#
|
39
|
+
# @param [Object] v
|
40
|
+
# the value to set
|
41
|
+
#
|
42
|
+
# @return [Object]
|
43
|
+
# the newly defined value
|
44
|
+
def define(k, v)
|
45
|
+
@values[k] = v
|
46
|
+
end
|
47
|
+
|
48
|
+
# Reference a variable in this Scope or any parent Scopes.
|
35
49
|
#
|
36
50
|
# Raises a RuntimeError if the variable does not exist.
|
37
51
|
#
|
@@ -46,13 +60,13 @@ module Iode
|
|
46
60
|
elsif @parent
|
47
61
|
@parent[k]
|
48
62
|
else
|
49
|
-
raise "
|
63
|
+
raise "Reference to undefined variable `#{k}`"
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
53
|
-
# Re-assign a variable in this
|
67
|
+
# Re-assign a variable in this Scope, or any parent Scope.
|
54
68
|
#
|
55
|
-
#
|
69
|
+
# Raises a RuntimeError if the variable does not exist.
|
56
70
|
#
|
57
71
|
# @param [Symbol] k
|
58
72
|
# the variable name to assign
|
@@ -63,13 +77,18 @@ module Iode
|
|
63
77
|
# @return [Object]
|
64
78
|
# the assigned object
|
65
79
|
def []=(k, v)
|
66
|
-
@values
|
80
|
+
if @values.key?(k)
|
81
|
+
@values[k] = v
|
82
|
+
elsif @parent
|
83
|
+
@parent[k] = v
|
84
|
+
else
|
85
|
+
raise "Reference to undefined variable `#{k}`"
|
86
|
+
end
|
67
87
|
end
|
68
88
|
|
69
89
|
# Create a new Scope with this Scope as its parent.
|
70
90
|
#
|
71
|
-
# The new Scope will have access to all variables in this Scope
|
72
|
-
# but assignments will only mask variables, not replace them.
|
91
|
+
# The new Scope will have access to all variables in this Scope.
|
73
92
|
#
|
74
93
|
# @param [Hash] values
|
75
94
|
# variables to exist in the new Scope
|
data/lib/iode/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Corbyn
|
@@ -72,7 +72,11 @@ files:
|
|
72
72
|
- bin/iode-rb
|
73
73
|
- iode.gemspec
|
74
74
|
- lib/iode.rb
|
75
|
-
- lib/iode/
|
75
|
+
- lib/iode/core.rb
|
76
|
+
- lib/iode/core/comparisons.rb
|
77
|
+
- lib/iode/core/lists.rb
|
78
|
+
- lib/iode/core/math.rb
|
79
|
+
- lib/iode/core/output.rb
|
76
80
|
- lib/iode/interpreter.rb
|
77
81
|
- lib/iode/reader.rb
|
78
82
|
- lib/iode/scope.rb
|