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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88c450a99b55f2e4e32ba2889a229da5efc6f37c
4
- data.tar.gz: 00e32521320732d46ce067b49dcb53c6594cd7b0
3
+ metadata.gz: ef8311e9162a113a3aff4d5741524ac2bd6eafcb
4
+ data.tar.gz: 56634c23acfedee8d05d6bc38a78392dc9df0865
5
5
  SHA512:
6
- metadata.gz: 8fc46489cacd07abc6d46c574040f3643aef9f99e7ff5176a9bf47b8c08c8d23cc3ffe954917c0fe81709987b088779d41be2ea360829c009c5768124c2cc7ed
7
- data.tar.gz: 20b0ead4cbbaf356c768acb45965a423907ee2acee8a7a57a82404c1f5cc9722e9b83c78dda32643e08b0131175b2a503af2822505b13053d8c09d5fb4e53661
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
- puts Iode.run <<-PROG
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 `"x = false"`.
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[false] #=> 7
83
- prog[true] #=> 42
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
- (if (f)
97
- 42
98
- 7))
98
+ (f 42))
99
99
  PROG
100
100
 
101
- prog.call(->(){ false }) #=> 7
102
- prog.call(->(){ true }) #=> 42
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'd show a more complex example if the language were more than 3 hours old!
106
- There are not even any mathematic operators or persistent definitions yet,
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
 
@@ -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
 
@@ -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: built_ins.rb
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
- module Iode
18
- module BuiltIns
19
- def car(list)
20
- v, *_ = list
21
- v
22
- end
17
+ require "iode/core"
23
18
 
24
- def cdr(list)
25
- _, *v = list
26
- v
27
- end
28
-
29
- def cadr(list)
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
- def cddddr(list)
50
- cdr(cdddr(list))
51
- end
26
+ def <(a, b)
27
+ a < b
28
+ end
52
29
 
53
- def caddddr(list)
54
- car(cdddr(list))
55
- end
30
+ def ==(a, b)
31
+ a == b
32
+ end
56
33
 
57
- def cdddddr(list)
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
@@ -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
- cadr(sexp)
118
+ car(cdr(sexp))
97
119
  when :if
98
- if eval(cadr(sexp))
99
- eval(caddr(sexp))
120
+ if eval(car(cdr(sexp)))
121
+ eval(car(cdr(cdr(sexp))))
100
122
  else
101
- eval(cadddr(sexp))
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[cadr(sexp)] = eval(caddr(sexp))
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(cadr(sexp), *cddr(sexp))
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
@@ -26,12 +26,26 @@ module Iode
26
26
  #
27
27
  # @param [Scope] parent
28
28
  # the parent scope, if any
29
- def initialize(values = {}, parent = nil)
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
- # Reference a variable in this scope.
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 "Undefined variable `#{k}`"
63
+ raise "Reference to undefined variable `#{k}`"
50
64
  end
51
65
  end
52
66
 
53
- # Re-assign a variable in this scope.
67
+ # Re-assign a variable in this Scope, or any parent Scope.
54
68
  #
55
- # If the variable does not exist, it will be initialized.
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[k] = v
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
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module Iode
18
- VERSION = "0.0.1"
18
+ VERSION = "0.0.2"
19
19
  end
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.1
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/built_ins.rb
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