klam 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +0 -0
- data/.travis.yml +8 -0
- data/Gemfile +1 -0
- data/LICENSE.txt +19 -0
- data/PROGRESS.asciidoc +105 -0
- data/README.asciidoc +11 -0
- data/Rakefile +5 -0
- data/klam.gemspec +28 -0
- data/lib/klam.rb +16 -0
- data/lib/klam/compilation_stages.rb +4 -0
- data/lib/klam/compilation_stages/convert_freezes_to_lambdas.rb +17 -0
- data/lib/klam/compilation_stages/convert_lexical_variables.rb +79 -0
- data/lib/klam/compilation_stages/convert_partial_applications_to_lambdas.rb +35 -0
- data/lib/klam/compilation_stages/convert_self_tail_calls_to_loops.rb +147 -0
- data/lib/klam/compilation_stages/curry_abstraction_applications.rb +33 -0
- data/lib/klam/compilation_stages/emit_ruby.rb +232 -0
- data/lib/klam/compilation_stages/kl_to_internal_representation.rb +21 -0
- data/lib/klam/compilation_stages/make_abstractions_monadic.rb +26 -0
- data/lib/klam/compilation_stages/make_abstractions_variadic.rb +23 -0
- data/lib/klam/compilation_stages/simplify_boolean_operations.rb +74 -0
- data/lib/klam/compilation_stages/strip_type_declarations.rb +24 -0
- data/lib/klam/compiler.rb +63 -0
- data/lib/klam/cons.rb +18 -0
- data/lib/klam/converters.rb +4 -0
- data/lib/klam/converters/.list.rb.swp +0 -0
- data/lib/klam/converters/list.rb +29 -0
- data/lib/klam/environment.rb +61 -0
- data/lib/klam/error.rb +4 -0
- data/lib/klam/lexer.rb +185 -0
- data/lib/klam/primitives.rb +4 -0
- data/lib/klam/primitives/arithmetic.rb +49 -0
- data/lib/klam/primitives/assignments.rb +13 -0
- data/lib/klam/primitives/boolean_operations.rb +22 -0
- data/lib/klam/primitives/error_handling.rb +19 -0
- data/lib/klam/primitives/generic_functions.rb +19 -0
- data/lib/klam/primitives/lists.rb +23 -0
- data/lib/klam/primitives/streams.rb +32 -0
- data/lib/klam/primitives/strings.rb +58 -0
- data/lib/klam/primitives/symbols.rb +16 -0
- data/lib/klam/primitives/time.rb +19 -0
- data/lib/klam/primitives/vectors.rb +26 -0
- data/lib/klam/reader.rb +46 -0
- data/lib/klam/template.rb +38 -0
- data/lib/klam/variable.rb +21 -0
- data/lib/klam/variable_generator.rb +12 -0
- data/lib/klam/version.rb +3 -0
- data/spec/functional/application_spec.rb +94 -0
- data/spec/functional/atoms_spec.rb +56 -0
- data/spec/functional/extensions/do_spec.rb +22 -0
- data/spec/functional/primitives/assignments_spec.rb +38 -0
- data/spec/functional/primitives/boolean_operations_spec.rb +133 -0
- data/spec/functional/primitives/error_handling_spec.rb +22 -0
- data/spec/functional/primitives/generic_functions_spec.rb +82 -0
- data/spec/functional/tail_call_optimization_spec.rb +71 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/unit/klam/compilation_stages/convert_lexical_variables_spec.rb +58 -0
- data/spec/unit/klam/compilation_stages/convert_self_tail_calls_to_loops_spec.rb +33 -0
- data/spec/unit/klam/compilation_stages/curry_abstraction_applications_spec.rb +19 -0
- data/spec/unit/klam/compilation_stages/make_abstractions_variadic_spec.rb +12 -0
- data/spec/unit/klam/converters/list_spec.rb +57 -0
- data/spec/unit/klam/lexer_spec.rb +149 -0
- data/spec/unit/klam/primitives/arithmetic_spec.rb +153 -0
- data/spec/unit/klam/primitives/boolean_operations_spec.rb +39 -0
- data/spec/unit/klam/primitives/error_handling_spec.rb +19 -0
- data/spec/unit/klam/primitives/lists_spec.rb +49 -0
- data/spec/unit/klam/primitives/strings_spec.rb +53 -0
- data/spec/unit/klam/primitives/symbols_spec.rb +19 -0
- data/spec/unit/klam/primitives/time_spec.rb +16 -0
- data/spec/unit/klam/primitives/vectors_spec.rb +55 -0
- data/spec/unit/klam/reader_spec.rb +47 -0
- data/spec/unit/klam/template_spec.rb +28 -0
- data/spec/unit/klam/variable_spec.rb +22 -0
- data/spec/unit/klam/version_spec.rb +7 -0
- metadata +225 -0
data/lib/klam/cons.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Klam
|
2
|
+
class Cons
|
3
|
+
attr_reader :hd, :tl
|
4
|
+
|
5
|
+
def initialize(hd, tl)
|
6
|
+
@hd = hd
|
7
|
+
@tl = tl
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.instance_of?(Cons) && other.hd == @hd && other.tl == @tl
|
12
|
+
end
|
13
|
+
|
14
|
+
def hash
|
15
|
+
[@hd, @tl].hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
Binary file
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Klam
|
2
|
+
module Converters
|
3
|
+
# Utility methods to convert between Ruby Arrays and Kl Lists
|
4
|
+
module List
|
5
|
+
include Klam::Primitives::Lists
|
6
|
+
|
7
|
+
def arrayToList(array)
|
8
|
+
list = EMPTY_LIST
|
9
|
+
(array.length - 1).downto(0) do |index|
|
10
|
+
item = array[index]
|
11
|
+
item = arrayToList(item) if item.kind_of?(Array)
|
12
|
+
list = cons(item, list)
|
13
|
+
end
|
14
|
+
list
|
15
|
+
end
|
16
|
+
|
17
|
+
def listToArray(list)
|
18
|
+
array = []
|
19
|
+
while list != EMPTY_LIST
|
20
|
+
item = hd(list)
|
21
|
+
item = listToArray(item) if cons?(item) || item == EMPTY_LIST
|
22
|
+
array << item
|
23
|
+
list = tl(list)
|
24
|
+
end
|
25
|
+
array
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Klam
|
2
|
+
class Environment < BasicObject
|
3
|
+
include ::Klam::Primitives::BooleanOperations
|
4
|
+
include ::Klam::Primitives::Symbols
|
5
|
+
include ::Klam::Primitives::Strings
|
6
|
+
include ::Klam::Primitives::Assignments
|
7
|
+
include ::Klam::Primitives::ErrorHandling
|
8
|
+
include ::Klam::Primitives::Lists
|
9
|
+
include ::Klam::Primitives::GenericFunctions
|
10
|
+
include ::Klam::Primitives::Vectors
|
11
|
+
include ::Klam::Primitives::Streams
|
12
|
+
include ::Klam::Primitives::Time
|
13
|
+
include ::Klam::Primitives::Arithmetic
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# The global assignments namespace. Errors are thrown here in the
|
17
|
+
# missing element handler rather than in the value primitive in order
|
18
|
+
# to facilitate inlining later.
|
19
|
+
@assignments = ::Hash.new do |_, name|
|
20
|
+
::Kernel.raise ::Klam::Error, "The variable #{name} is unbound."
|
21
|
+
end
|
22
|
+
|
23
|
+
@arities = ::Hash.new { |h, k| h[k] = __arity(k) }
|
24
|
+
@curried_methods = ::Hash.new { |h, k| h[k] = __method(k).to_proc.curry }
|
25
|
+
@loop_cache = {}
|
26
|
+
|
27
|
+
# Grab a handle to this object's eigenclass for use later when the
|
28
|
+
# compiled code needs to reference it. It is used, e.g., when renaming
|
29
|
+
# methods.
|
30
|
+
@eigenclass = class << self; self; end
|
31
|
+
|
32
|
+
# The open primitive depends on having *home-directory* assigned.
|
33
|
+
set(:"*home-directory*", ::Dir.pwd)
|
34
|
+
end
|
35
|
+
|
36
|
+
def __apply(rator, *rands)
|
37
|
+
if rator.kind_of?(::Symbol)
|
38
|
+
@curried_methods[rator].call(*rands)
|
39
|
+
else
|
40
|
+
rator.call(*rands)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def __method(sym)
|
45
|
+
@eigenclass.instance_method(sym).bind(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def __arity(sym)
|
49
|
+
@eigenclass.instance_method(sym).arity
|
50
|
+
rescue ::NameError
|
51
|
+
-1
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def rename_method(old_name, new_name)
|
56
|
+
alias_method(new_name, old_name)
|
57
|
+
remove_method(old_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/klam/error.rb
ADDED
data/lib/klam/lexer.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Klam
|
4
|
+
class Lexer
|
5
|
+
SYMBOL_CHARS = /[-=*\/+_?$!\@~><&%'#`;:{}a-zA-Z0-9.]/
|
6
|
+
|
7
|
+
# Syntax tokens
|
8
|
+
class OpenParen
|
9
|
+
include Singleton
|
10
|
+
end
|
11
|
+
class CloseParen
|
12
|
+
include Singleton
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(stream)
|
16
|
+
@stream = stream
|
17
|
+
@buffer = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def eof?
|
21
|
+
@buffer.empty? && @stream.eof?
|
22
|
+
end
|
23
|
+
|
24
|
+
def getc
|
25
|
+
if @buffer.empty?
|
26
|
+
@stream.getc
|
27
|
+
else
|
28
|
+
@buffer.pop
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def ungetc(c)
|
33
|
+
@buffer.push(c)
|
34
|
+
end
|
35
|
+
|
36
|
+
def next
|
37
|
+
drain_whitespace
|
38
|
+
unless eof?
|
39
|
+
c = getc
|
40
|
+
case c
|
41
|
+
when '('
|
42
|
+
OpenParen.instance
|
43
|
+
when ')'
|
44
|
+
CloseParen.instance
|
45
|
+
when '"'
|
46
|
+
consume_string
|
47
|
+
when SYMBOL_CHARS
|
48
|
+
ungetc(c)
|
49
|
+
consume_number_or_symbol
|
50
|
+
else
|
51
|
+
raise Klam::SyntaxError, "illegal character: #{c}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def drain_whitespace
|
58
|
+
until eof?
|
59
|
+
c = getc
|
60
|
+
if c =~ /\S/
|
61
|
+
ungetc(c)
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def consume_string
|
68
|
+
chars = []
|
69
|
+
loop do
|
70
|
+
raise Klam::SyntaxError, "unterminated string" if eof?
|
71
|
+
c = getc
|
72
|
+
break if c == '"'
|
73
|
+
chars << c
|
74
|
+
end
|
75
|
+
chars.join
|
76
|
+
end
|
77
|
+
|
78
|
+
def consume_number
|
79
|
+
# Shen allows multiple leading plusses and minuses. The plusses
|
80
|
+
# are ignored and an even number of minuses cancel each other.
|
81
|
+
# Thus '------+-7' is read as 7.
|
82
|
+
#
|
83
|
+
# The Shen reader parses "7." as the integer 7 and the symbol '.'
|
84
|
+
decimal_seen = false
|
85
|
+
negative = false
|
86
|
+
past_sign = false
|
87
|
+
chars = []
|
88
|
+
loop do
|
89
|
+
break if eof?
|
90
|
+
c = getc
|
91
|
+
if c =~ /\d/
|
92
|
+
past_sign = true
|
93
|
+
chars << c
|
94
|
+
elsif c == '.' && !decimal_seen
|
95
|
+
past_sign = true
|
96
|
+
decimal_seen = true
|
97
|
+
chars << c
|
98
|
+
elsif c == '+' && !past_sign
|
99
|
+
# ignore
|
100
|
+
elsif c == '-' && !past_sign
|
101
|
+
negative = !negative
|
102
|
+
else
|
103
|
+
ungetc c
|
104
|
+
break
|
105
|
+
end
|
106
|
+
end
|
107
|
+
chars.unshift('-') if negative
|
108
|
+
if chars.last == '.'
|
109
|
+
# A trailing decimal point is treated as part of the next
|
110
|
+
# token. Forget we saw it.
|
111
|
+
ungetc(chars.pop)
|
112
|
+
decimal_seen = false
|
113
|
+
end
|
114
|
+
str = chars.join
|
115
|
+
decimal_seen ? str.to_f : str.to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def consume_symbol
|
119
|
+
chars = []
|
120
|
+
loop do
|
121
|
+
break if eof?
|
122
|
+
c = getc
|
123
|
+
unless c =~ SYMBOL_CHARS
|
124
|
+
ungetc c
|
125
|
+
break
|
126
|
+
end
|
127
|
+
chars << c
|
128
|
+
end
|
129
|
+
str = chars.join
|
130
|
+
|
131
|
+
case str
|
132
|
+
when 'true'
|
133
|
+
true
|
134
|
+
when 'false'
|
135
|
+
false
|
136
|
+
else
|
137
|
+
str.to_sym
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def consume_number_or_symbol
|
142
|
+
# First drain optional leading signs
|
143
|
+
# Then drain optional decimal point
|
144
|
+
# If there is another character and it is a digit, then it
|
145
|
+
# is a number. Otherwise it is a symbol.
|
146
|
+
chars = []
|
147
|
+
loop do
|
148
|
+
break if eof?
|
149
|
+
c = getc
|
150
|
+
unless c =~ /[-+]/
|
151
|
+
ungetc c
|
152
|
+
break
|
153
|
+
end
|
154
|
+
chars << c
|
155
|
+
end
|
156
|
+
if eof?
|
157
|
+
chars.reverse.each {|x| ungetc x}
|
158
|
+
return consume_symbol
|
159
|
+
end
|
160
|
+
|
161
|
+
c = getc
|
162
|
+
chars << c
|
163
|
+
if c == '.'
|
164
|
+
if eof?
|
165
|
+
chars.reverse.each {|x| ungetc x}
|
166
|
+
return consume_symbol
|
167
|
+
end
|
168
|
+
c = getc
|
169
|
+
chars << c
|
170
|
+
chars.reverse.each {|x| ungetc x}
|
171
|
+
if c =~ /\d/
|
172
|
+
return consume_number
|
173
|
+
else
|
174
|
+
return consume_symbol
|
175
|
+
end
|
176
|
+
elsif c =~ /\d/
|
177
|
+
chars.reverse.each {|x| ungetc x}
|
178
|
+
return consume_number
|
179
|
+
else
|
180
|
+
chars.reverse.each {|x| ungetc x}
|
181
|
+
return consume_symbol
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Arithmetic
|
4
|
+
def +(a, b)
|
5
|
+
a + b
|
6
|
+
end
|
7
|
+
|
8
|
+
def -(a, b)
|
9
|
+
a - b
|
10
|
+
end
|
11
|
+
|
12
|
+
def *(a, b)
|
13
|
+
a * b
|
14
|
+
end
|
15
|
+
|
16
|
+
def /(a, b)
|
17
|
+
# Kl does not make a distinction between integers and reals. Dividing
|
18
|
+
# the integer 3 by the interger 2 must yield 1.5 rather than 1. We'd
|
19
|
+
# like to keep things in integers as much as possible, so we coerce a
|
20
|
+
# to a float only if integer division is not possible.
|
21
|
+
if a.kind_of?(Fixnum) && b.kind_of?(Fixnum) && a.remainder(b) != 0
|
22
|
+
a = a.to_f
|
23
|
+
end
|
24
|
+
|
25
|
+
a / b
|
26
|
+
end
|
27
|
+
|
28
|
+
def <(a, b)
|
29
|
+
a < b
|
30
|
+
end
|
31
|
+
|
32
|
+
def >(a, b)
|
33
|
+
a > b
|
34
|
+
end
|
35
|
+
|
36
|
+
def <=(a, b)
|
37
|
+
a <= b
|
38
|
+
end
|
39
|
+
|
40
|
+
def >=(a, b)
|
41
|
+
a >= b
|
42
|
+
end
|
43
|
+
|
44
|
+
def number?(a)
|
45
|
+
a.kind_of?(Numeric)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
# All of the boolean operations are special forms, but if, and, and or
|
4
|
+
# are also available as normal functions to facilitate partial application.
|
5
|
+
# When partially applied, they are no longer short circuiting.
|
6
|
+
module BooleanOperations
|
7
|
+
def _if(a, b, c)
|
8
|
+
a ? b : c
|
9
|
+
end
|
10
|
+
alias_method :'if', :_if
|
11
|
+
remove_method :_if
|
12
|
+
|
13
|
+
def and(a, b)
|
14
|
+
a && b
|
15
|
+
end
|
16
|
+
|
17
|
+
def or(a, b)
|
18
|
+
a || b
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module ErrorHandling
|
4
|
+
def simple_error(msg)
|
5
|
+
::Kernel.raise ::Klam::Error, msg
|
6
|
+
end
|
7
|
+
alias_method :"simple-error", :simple_error
|
8
|
+
remove_method :simple_error
|
9
|
+
|
10
|
+
# trap-error is a special form and implemented in the compiler
|
11
|
+
|
12
|
+
def error_to_string(err)
|
13
|
+
err.message
|
14
|
+
end
|
15
|
+
alias_method :"error-to-string", :error_to_string
|
16
|
+
remove_method :error_to_string
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module GenericFunctions
|
4
|
+
def equal(a, b)
|
5
|
+
a == b
|
6
|
+
end
|
7
|
+
alias_method :"=", :equal
|
8
|
+
remove_method :equal
|
9
|
+
|
10
|
+
def eval_kl(form)
|
11
|
+
compiler = Klam::Compiler.new(self)
|
12
|
+
code = compiler.compile(form)
|
13
|
+
instance_eval code
|
14
|
+
end
|
15
|
+
alias_method :"eval-kl", :eval_kl
|
16
|
+
remove_method :eval_kl
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|