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
@@ -0,0 +1,23 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Lists
|
4
|
+
EMPTY_LIST = nil
|
5
|
+
|
6
|
+
def cons(head, tail)
|
7
|
+
::Klam::Cons.new(head, tail)
|
8
|
+
end
|
9
|
+
|
10
|
+
def hd(l)
|
11
|
+
l.hd
|
12
|
+
end
|
13
|
+
|
14
|
+
def tl(l)
|
15
|
+
l.tl
|
16
|
+
end
|
17
|
+
|
18
|
+
def cons?(l)
|
19
|
+
l.instance_of?(::Klam::Cons)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Streams
|
4
|
+
def read_byte(stream)
|
5
|
+
if stream.eof?
|
6
|
+
-1
|
7
|
+
else
|
8
|
+
stream.readbyte
|
9
|
+
end
|
10
|
+
end
|
11
|
+
alias_method :"read-byte", :read_byte
|
12
|
+
remove_method :read_byte
|
13
|
+
|
14
|
+
def write_byte(byte, stream)
|
15
|
+
stream.putc byte
|
16
|
+
byte
|
17
|
+
end
|
18
|
+
alias_method :"write-byte", :write_byte
|
19
|
+
remove_method :write_byte
|
20
|
+
|
21
|
+
def open(name, direction)
|
22
|
+
::File.open(::File.expand_path(name, value(:'*home-directory*')),
|
23
|
+
direction == :out ? 'w' : 'r')
|
24
|
+
end
|
25
|
+
|
26
|
+
def close(stream)
|
27
|
+
stream.close
|
28
|
+
:NIL
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Strings
|
4
|
+
def pos(str, n)
|
5
|
+
if n < 0 || n >= str.length
|
6
|
+
::Kernel.raise ::Klam::Error, "index out of bounds: #{n}"
|
7
|
+
end
|
8
|
+
str[n]
|
9
|
+
end
|
10
|
+
|
11
|
+
def tlstr(str)
|
12
|
+
if str.empty?
|
13
|
+
::Kernel.raise ::Klam::Error, 'attempted to take tail of empty string'
|
14
|
+
end
|
15
|
+
str[1..-1]
|
16
|
+
end
|
17
|
+
|
18
|
+
def cn(s1, s2)
|
19
|
+
s1 + s2
|
20
|
+
end
|
21
|
+
|
22
|
+
def str(x)
|
23
|
+
case x
|
24
|
+
when String
|
25
|
+
'"' + x + '"'
|
26
|
+
when Symbol
|
27
|
+
x.to_s
|
28
|
+
when Numeric
|
29
|
+
x.to_s
|
30
|
+
when TrueClass, FalseClass
|
31
|
+
x.to_s
|
32
|
+
when Proc
|
33
|
+
x.to_s
|
34
|
+
when IO
|
35
|
+
x.to_s
|
36
|
+
else
|
37
|
+
::Kernel.raise ::Klam::Error, "str applied to non-atomic type: #{x.class}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def string?(x)
|
42
|
+
x.kind_of?(String)
|
43
|
+
end
|
44
|
+
|
45
|
+
def n_to_string(n)
|
46
|
+
'' << n
|
47
|
+
end
|
48
|
+
alias_method :"n->string", :n_to_string
|
49
|
+
remove_method :n_to_string
|
50
|
+
|
51
|
+
def string_to_n(str)
|
52
|
+
str.ord
|
53
|
+
end
|
54
|
+
alias_method :"string->n", :string_to_n
|
55
|
+
remove_method :string_to_n
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Time
|
4
|
+
def get_time(type)
|
5
|
+
case type
|
6
|
+
when :real, :run
|
7
|
+
::Time.now.to_f
|
8
|
+
when :unix
|
9
|
+
::Time.now.to_i
|
10
|
+
else
|
11
|
+
::Kernel.raise ::Klam::Error, "invalid time parameter: #{type}"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
alias_method :"get-time", :get_time
|
16
|
+
remove_method :get_time
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Klam
|
2
|
+
module Primitives
|
3
|
+
module Vectors
|
4
|
+
def absvector(n)
|
5
|
+
::Array.new(n)
|
6
|
+
end
|
7
|
+
|
8
|
+
def absvec_store(vec, n, val)
|
9
|
+
vec[n] = val
|
10
|
+
vec
|
11
|
+
end
|
12
|
+
alias_method :"address->", :absvec_store
|
13
|
+
remove_method :absvec_store
|
14
|
+
|
15
|
+
def absvec_read(vec, n)
|
16
|
+
vec[n]
|
17
|
+
end
|
18
|
+
alias_method :"<-address", :absvec_read
|
19
|
+
remove_method :absvec_read
|
20
|
+
|
21
|
+
def absvector?(v)
|
22
|
+
v.instance_of?(::Array)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/klam/reader.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Klam
|
2
|
+
class Reader
|
3
|
+
include Klam::Converters::List
|
4
|
+
|
5
|
+
def initialize(stream)
|
6
|
+
@lexer = Klam::Lexer.new(stream)
|
7
|
+
end
|
8
|
+
|
9
|
+
def next
|
10
|
+
token = @lexer.next
|
11
|
+
unless token.nil?
|
12
|
+
if token.kind_of? Klam::Lexer::OpenParen
|
13
|
+
read_list
|
14
|
+
else
|
15
|
+
token
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def read_list
|
23
|
+
items = []
|
24
|
+
stack = [items]
|
25
|
+
|
26
|
+
until stack.empty? do
|
27
|
+
token = @lexer.next
|
28
|
+
raise Klam::SyntaxError, 'Unterminated list' if token.nil?
|
29
|
+
case token
|
30
|
+
when Klam::Lexer::OpenParen
|
31
|
+
items = []
|
32
|
+
stack.push items
|
33
|
+
when Klam::Lexer::CloseParen
|
34
|
+
array = stack.pop
|
35
|
+
unless stack.empty?
|
36
|
+
items = stack.last
|
37
|
+
items << array
|
38
|
+
end
|
39
|
+
else
|
40
|
+
items << token
|
41
|
+
end
|
42
|
+
end
|
43
|
+
arrayToList(array)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Klam
|
2
|
+
module Template
|
3
|
+
def render_string(template, *args)
|
4
|
+
args = join_array_arguments(args)
|
5
|
+
segments = segment_string(template)
|
6
|
+
segments.map do |segment|
|
7
|
+
if segment =~ /^\$(\d+)$/
|
8
|
+
args[$1.to_i - 1]
|
9
|
+
else
|
10
|
+
segment
|
11
|
+
end
|
12
|
+
end.join
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def join_array_arguments(args)
|
18
|
+
args.map do |arg|
|
19
|
+
if arg.kind_of?(Array)
|
20
|
+
arg.join(',')
|
21
|
+
else
|
22
|
+
arg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
def segment_string(str)
|
27
|
+
segments = []
|
28
|
+
pre, placeholder, str = str.partition(/\$\d+/)
|
29
|
+
until placeholder.empty? && str.empty?
|
30
|
+
segments << pre
|
31
|
+
segments << placeholder
|
32
|
+
pre, placeholder, str = str.partition(/\$\d+/)
|
33
|
+
end
|
34
|
+
segments << pre
|
35
|
+
segments
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Klam
|
2
|
+
class Variable
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
name
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.kind_of?(Variable) && name == other.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash
|
18
|
+
name.hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/klam/version.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Application', :type => :functional do
|
4
|
+
describe 'of functions' do
|
5
|
+
describe 'when all arguments are provided' do
|
6
|
+
it 'returns the result of applying the function to its arguments' do
|
7
|
+
eval_kl('(defun add (A B) (+ A B))')
|
8
|
+
expect_kl('(add 2 3)').to eq(5)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'when not all arguments are provided' do
|
13
|
+
before(:each) do
|
14
|
+
eval_kl('(defun add3 (A B C) (+ (+ A B) C))')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns a function' do
|
18
|
+
expect_kl('(add3 1)').to be_kind_of(Proc)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'evaluates once all of the arguments are provided' do
|
22
|
+
expect_kl('((add3 1) 2 3)').to eq(6)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows further partial application' do
|
26
|
+
expect_kl('(((add3 1) 2) 3)').to eq(6)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'uncurrying' do
|
31
|
+
before(:each) do
|
32
|
+
eval_kl('(defun add3 (A) (lambda B (lambda C (+ (+ A B) C))))')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'provides the equivalent behavior to defining a polyadic function' do
|
36
|
+
expect_kl('(add3 1 2 3)').to eq(6)
|
37
|
+
expect_kl('((add3 1 2) 3)').to eq(6)
|
38
|
+
expect_kl('((add3 1) 2 3)').to eq(6)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'when the function takes no arguments' do
|
43
|
+
it 'returns the result of evaluating the function\'s body' do
|
44
|
+
eval_kl('(defun thirty-seven () 37)')
|
45
|
+
expect_kl('(thirty-seven)').to eq(37)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'of abstractions' do
|
51
|
+
describe 'when applied directly' do
|
52
|
+
it 'returns the result of applying the abstraction to its argument' do
|
53
|
+
expect_kl('((lambda X (+ X 3)) 2)').to eq(5)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'uncurries additional arguments' do
|
57
|
+
expect_kl('((lambda A (lambda B (lambda C (+ (+ A B) C)))) 1 2 3)')
|
58
|
+
.to eq(6)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'when returned from another function' do
|
63
|
+
it 'returns the result of applying the returned abstraction to its arg' do
|
64
|
+
eval_kl('(defun make-adder (X) (lambda Y (+ X Y)))')
|
65
|
+
expect_kl('((make-adder 3) 2)').to eq(5)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "of variables" do
|
71
|
+
describe "when bound to functions" do
|
72
|
+
describe 'when applied directly' do
|
73
|
+
it 'returns the result of applying the abstraction to its argument' do
|
74
|
+
expect_kl('(let F (lambda X (+ X 3)) (F 2))').to eq(5)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'uncurries additional arguments' do
|
78
|
+
expect_kl('(let F (lambda A (lambda B (lambda C (+ (+ A B) C)))) (F 1 2 3))')
|
79
|
+
.to eq(6)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "when bound to symbols" do
|
85
|
+
it 'invokes the function bound to the symobl in the global environment' do
|
86
|
+
expect_kl('(let F + (F 1 2))').to eq(3)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'supports partial evaluation' do
|
90
|
+
expect_kl('(let F + ((F 1) 2))').to eq(3)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Atoms', :type => :functional do
|
4
|
+
describe 'numbers' do
|
5
|
+
it 'integers evaluate to themselves' do
|
6
|
+
expect_kl('37').to eq(read_kl('37'))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'reals evaluate to themselves' do
|
10
|
+
expect_kl('37.42').to eq(read_kl('37.42'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'symbols' do
|
15
|
+
it 'evaluate to themselves' do
|
16
|
+
expect_kl('sym').to eq(read_kl('sym'))
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'supports the full range of initial symbol characters' do
|
20
|
+
# See http://www.shenlanguage.org/learn-shen/shendoc.htm#The%20Syntax%20of%20Symbols
|
21
|
+
all_chars = 'abcdefghijklmnopqrstuvwxyz'
|
22
|
+
all_chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
23
|
+
all_chars += "=-*/+_?$!@~.><&%'#`;:{}"
|
24
|
+
|
25
|
+
all_chars.each_char do |c|
|
26
|
+
expect_kl(c).to eq(read_kl(c))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'strings' do
|
32
|
+
it 'evaluate to themselves' do
|
33
|
+
expect_kl('"a string"').to eq(read_kl('"a string"'))
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'supports embedded single quotes' do
|
37
|
+
expect_kl('"\'quote\'"').to eq(read_kl('"\'quote\'"'))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'booleans' do
|
42
|
+
it 'evaluates true to itself' do
|
43
|
+
expect_kl('true').to eq(read_kl('true'))
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'evaluates false to itself' do
|
47
|
+
expect_kl('false').to eq(read_kl('false'))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'empty list' do
|
52
|
+
it 'evaluates to itself' do
|
53
|
+
expect_kl('()').to be(Klam::Primitives::Lists::EMPTY_LIST)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|