klam 0.0.1
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 +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
|