iode 0.0.1 → 0.0.2

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