campa 0.1.0
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 +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +23 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/campa.gemspec +34 -0
- data/campa/core.cmp +59 -0
- data/campa/example.cmp +2 -0
- data/campa/test.cmp +2 -0
- data/exe/campa +7 -0
- data/lib/campa.rb +18 -0
- data/lib/campa/cli.rb +66 -0
- data/lib/campa/context.rb +42 -0
- data/lib/campa/core/load.rb +25 -0
- data/lib/campa/core/print.rb +20 -0
- data/lib/campa/core/print_ln.rb +17 -0
- data/lib/campa/core/test.rb +52 -0
- data/lib/campa/core/test_report.rb +59 -0
- data/lib/campa/error/arity.rb +11 -0
- data/lib/campa/error/illegal_argument.rb +9 -0
- data/lib/campa/error/invalid_number.rb +9 -0
- data/lib/campa/error/missing_delimiter.rb +9 -0
- data/lib/campa/error/not_a_function.rb +9 -0
- data/lib/campa/error/not_found.rb +9 -0
- data/lib/campa/error/parameters.rb +11 -0
- data/lib/campa/error/reserved.rb +11 -0
- data/lib/campa/error/resolution.rb +9 -0
- data/lib/campa/evaler.rb +106 -0
- data/lib/campa/execution_error.rb +3 -0
- data/lib/campa/lambda.rb +45 -0
- data/lib/campa/language.rb +33 -0
- data/lib/campa/lisp/atom.rb +14 -0
- data/lib/campa/lisp/cadr.rb +41 -0
- data/lib/campa/lisp/car.rb +22 -0
- data/lib/campa/lisp/cdr.rb +22 -0
- data/lib/campa/lisp/cond.rb +50 -0
- data/lib/campa/lisp/cons.rb +23 -0
- data/lib/campa/lisp/core.rb +35 -0
- data/lib/campa/lisp/defun.rb +36 -0
- data/lib/campa/lisp/eq.rb +9 -0
- data/lib/campa/lisp/label.rb +29 -0
- data/lib/campa/lisp/lambda_fn.rb +33 -0
- data/lib/campa/lisp/list_fn.rb +9 -0
- data/lib/campa/lisp/quote.rb +13 -0
- data/lib/campa/list.rb +83 -0
- data/lib/campa/node.rb +17 -0
- data/lib/campa/printer.rb +70 -0
- data/lib/campa/reader.rb +198 -0
- data/lib/campa/repl.rb +75 -0
- data/lib/campa/symbol.rb +23 -0
- data/lib/campa/version.rb +5 -0
- metadata +119 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Campa
|
2
|
+
class Language < Lisp::Core
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
load_core_funs
|
6
|
+
load_core_files
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
FUNS = {
|
12
|
+
"tests-run": Core::Test.new,
|
13
|
+
"tests-report": Core::TestReport.new,
|
14
|
+
|
15
|
+
"print": Core::Print.new,
|
16
|
+
"println": Core::PrintLn.new,
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
FILES = [
|
20
|
+
"../campa/core.cmp",
|
21
|
+
"../campa/test.cmp",
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
def load_core_funs
|
25
|
+
FUNS.each { |label, fun| self[Symbol.new(label.to_s)] = fun }
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_core_files
|
29
|
+
loader = Campa::Core::Load.new
|
30
|
+
FILES.each { |f| loader.call(Campa.root.join(f), env: self) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Cadr
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
@car = Car.new
|
7
|
+
@cdr = Cdr.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def macro?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(operation, list)
|
15
|
+
return nil if list.nil? || list == List::EMPTY
|
16
|
+
raise illegal_argument(list) if !list.is_a?(List)
|
17
|
+
|
18
|
+
cut_list(
|
19
|
+
list,
|
20
|
+
operation
|
21
|
+
.label[1..-2]
|
22
|
+
.reverse
|
23
|
+
.split("")
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def illegal_argument(list)
|
30
|
+
Error::IllegalArgument.new(@printer.call(list), "list")
|
31
|
+
end
|
32
|
+
|
33
|
+
def cut_list(list, invocation_sequence)
|
34
|
+
invocation_sequence.reduce(list) do |l, oper|
|
35
|
+
to_call = oper == "a" ? @car : @cdr
|
36
|
+
to_call.call(l)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Car
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(list)
|
9
|
+
return nil if list.nil? || list == List::EMPTY
|
10
|
+
raise illegal_argument(list) if !list.is_a?(List)
|
11
|
+
|
12
|
+
list.head
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def illegal_argument(list)
|
18
|
+
Error::IllegalArgument.new(@printer.call(list), "list")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Cdr
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(list)
|
9
|
+
return nil if list.nil? || list == List::EMPTY
|
10
|
+
raise illegal_argument(list) if !list.is_a?(List)
|
11
|
+
|
12
|
+
list.tail
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def illegal_argument(list)
|
18
|
+
Error::IllegalArgument.new(@printer.call(list), "list")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Cond
|
4
|
+
FALSEY = [false, nil].freeze
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@printer = Printer.new
|
8
|
+
@evaler = Evaler.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def macro?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(*conditions, env:)
|
16
|
+
found = conditions.find do |cond|
|
17
|
+
raise illegal_argument(cond) if !cond.is_a?(List)
|
18
|
+
|
19
|
+
!FALSEY.include? evaler.call(cond.head, env)
|
20
|
+
end
|
21
|
+
|
22
|
+
eval_result(found, env)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :evaler, :printer
|
28
|
+
|
29
|
+
def illegal_argument(thing)
|
30
|
+
Error::IllegalArgument.new(printer.call(thing), "list")
|
31
|
+
end
|
32
|
+
|
33
|
+
def eval_result(found, env)
|
34
|
+
return if found.nil?
|
35
|
+
|
36
|
+
# For when condition is a "truethy" value
|
37
|
+
#
|
38
|
+
# (cond
|
39
|
+
# ((eq 1 2) 'no)
|
40
|
+
# (true 'yes)
|
41
|
+
# )
|
42
|
+
# => 'yes
|
43
|
+
to_eval = found.tail == List::EMPTY ? [found.head] : found.tail.to_a
|
44
|
+
to_eval.reduce(nil) do |_, expr|
|
45
|
+
evaler.call(expr, env)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Cons
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(new_head, list)
|
9
|
+
raise illegal_argument(list) if !list.is_a?(List)
|
10
|
+
|
11
|
+
list.push(new_head)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :printer
|
17
|
+
|
18
|
+
def illegal_argument(list)
|
19
|
+
Error::IllegalArgument.new(printer.call(list), "list")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Core < Context
|
4
|
+
CORE_FUNCS_MAP = {
|
5
|
+
"quote" => Quote,
|
6
|
+
"atom" => Atom,
|
7
|
+
"eq" => Eq,
|
8
|
+
"car" => Car,
|
9
|
+
"cdr" => Cdr,
|
10
|
+
"cons" => Cons,
|
11
|
+
"cond" => Cond,
|
12
|
+
"lambda" => LambdaFn,
|
13
|
+
"label" => Label,
|
14
|
+
"defun" => Defun,
|
15
|
+
|
16
|
+
"_cadr" => Cadr,
|
17
|
+
"list" => ListFn,
|
18
|
+
|
19
|
+
"load" => Campa::Core::Load,
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
super Hash[
|
24
|
+
CORE_FUNCS_MAP.map { |label, handler| [sym(label), handler.new] }
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def sym(label)
|
31
|
+
Campa::Symbol.new(label)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Defun
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
@label_fn = Label.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def macro?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(label, params, *body, env:)
|
14
|
+
raise label_error(label) if !label.is_a?(Symbol)
|
15
|
+
|
16
|
+
label_fn.call(
|
17
|
+
label,
|
18
|
+
invoke_lambda(params, body),
|
19
|
+
env: env
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :printer, :label_fn
|
26
|
+
|
27
|
+
def label_error(given)
|
28
|
+
Error::IllegalArgument.new(printer.call(given), "symbol")
|
29
|
+
end
|
30
|
+
|
31
|
+
def invoke_lambda(params, body)
|
32
|
+
List.new(SYMBOL_LAMBDA, params, *body)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class Label
|
4
|
+
def initialize
|
5
|
+
@evaler = Evaler.new
|
6
|
+
@printer = Printer.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def macro?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(label, expression, env:)
|
14
|
+
result = evaler.call(expression, env)
|
15
|
+
raise Error::Reserved, printer.call(label) if reserved?(label, result)
|
16
|
+
|
17
|
+
env[label] = result
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :evaler, :printer
|
23
|
+
|
24
|
+
def reserved?(symbol, result)
|
25
|
+
symbol.label.match?(CR_REGEX) && result.is_a?(Lambda)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Campa
|
2
|
+
module Lisp
|
3
|
+
class LambdaFn
|
4
|
+
def initialize
|
5
|
+
@printer = Printer.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def macro?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(params, *body, env:)
|
13
|
+
raise parameters_error(printer.call(params)) if !params.respond_to?(:find)
|
14
|
+
|
15
|
+
validate_params(params)
|
16
|
+
Lambda.new(params, body, env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :printer
|
22
|
+
|
23
|
+
def parameters_error(param, expected = "list of symbols")
|
24
|
+
Error::Parameters.new(param, expected)
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_params(params)
|
28
|
+
invalid_param = params.find { |el| !el.is_a?(Symbol) }
|
29
|
+
raise parameters_error(invalid_param, "symbol") if !invalid_param.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/campa/list.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Campa
|
2
|
+
class List
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
EMPTY = new
|
6
|
+
|
7
|
+
def initialize(*elements)
|
8
|
+
@first = nil
|
9
|
+
@head = nil
|
10
|
+
@tail = EMPTY
|
11
|
+
|
12
|
+
with elements
|
13
|
+
end
|
14
|
+
|
15
|
+
def push(element)
|
16
|
+
self.class.new.tap do |l|
|
17
|
+
l.first = Node.new(value: element, next_node: first)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def head
|
22
|
+
return nil if self == EMPTY
|
23
|
+
|
24
|
+
first.value
|
25
|
+
end
|
26
|
+
|
27
|
+
def tail
|
28
|
+
return EMPTY if first.nil? || first.next_node.nil?
|
29
|
+
|
30
|
+
self.class.new.tap { |l| l.first = @first.next_node }
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
return if self == EMPTY
|
35
|
+
|
36
|
+
block.call(head)
|
37
|
+
tail.each(&block) if tail != EMPTY
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
return false if !other.is_a?(List)
|
42
|
+
|
43
|
+
node = first
|
44
|
+
other_node = other.first
|
45
|
+
|
46
|
+
loop do
|
47
|
+
# If both node and other_node are nil
|
48
|
+
# we managed to walk the whole list.
|
49
|
+
# Since there was no early return (with false)
|
50
|
+
# this means all nodes are equal.
|
51
|
+
return node.nil? if other_node.nil?
|
52
|
+
return false if node != other_node
|
53
|
+
|
54
|
+
node = node.next_node
|
55
|
+
other_node = other_node.next_node
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
@printer ||= Printer.new
|
61
|
+
@printer.call(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
attr_accessor :first
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def with(elements)
|
71
|
+
return if elements.empty?
|
72
|
+
|
73
|
+
elements.reduce(nil) do |previous, current|
|
74
|
+
new_node = Node.new(value: current)
|
75
|
+
if previous.nil?
|
76
|
+
self.first = new_node
|
77
|
+
else
|
78
|
+
previous.next_node = new_node
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|