apricot 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.
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README.md +90 -0
- data/Rakefile +9 -0
- data/apricot.gemspec +22 -0
- data/bin/apricot +58 -0
- data/examples/bot.apr +23 -0
- data/examples/cinch-bot.apr +12 -0
- data/examples/hanoi.apr +10 -0
- data/examples/hello.apr +1 -0
- data/examples/plot.apr +28 -0
- data/examples/quine.apr +1 -0
- data/kernel/core.apr +928 -0
- data/lib/apricot/ast/identifier.rb +111 -0
- data/lib/apricot/ast/list.rb +99 -0
- data/lib/apricot/ast/literals.rb +240 -0
- data/lib/apricot/ast/node.rb +45 -0
- data/lib/apricot/ast/scopes.rb +147 -0
- data/lib/apricot/ast/toplevel.rb +66 -0
- data/lib/apricot/ast/variables.rb +64 -0
- data/lib/apricot/ast.rb +3 -0
- data/lib/apricot/compiler.rb +55 -0
- data/lib/apricot/cons.rb +27 -0
- data/lib/apricot/errors.rb +38 -0
- data/lib/apricot/generator.rb +15 -0
- data/lib/apricot/identifier.rb +91 -0
- data/lib/apricot/list.rb +96 -0
- data/lib/apricot/macroexpand.rb +47 -0
- data/lib/apricot/misc.rb +11 -0
- data/lib/apricot/namespace.rb +59 -0
- data/lib/apricot/parser.rb +541 -0
- data/lib/apricot/printers.rb +12 -0
- data/lib/apricot/repl.rb +254 -0
- data/lib/apricot/ruby_ext.rb +254 -0
- data/lib/apricot/seq.rb +44 -0
- data/lib/apricot/special_forms.rb +735 -0
- data/lib/apricot/stages.rb +60 -0
- data/lib/apricot/version.rb +3 -0
- data/lib/apricot.rb +30 -0
- data/spec/compiler_spec.rb +499 -0
- data/spec/identifier_spec.rb +58 -0
- data/spec/list_spec.rb +96 -0
- data/spec/parser_spec.rb +312 -0
- data/spec/spec_helper.rb +10 -0
- metadata +188 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module Apricot
|
2
|
+
module AST
|
3
|
+
class LocalReference
|
4
|
+
attr_reader :slot, :depth
|
5
|
+
|
6
|
+
def initialize(slot, depth = 0)
|
7
|
+
@slot = slot
|
8
|
+
@depth = depth
|
9
|
+
end
|
10
|
+
|
11
|
+
def bytecode(g)
|
12
|
+
if @depth == 0
|
13
|
+
g.push_local @slot
|
14
|
+
else
|
15
|
+
g.push_local_depth @depth, @slot
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class NamespaceReference
|
21
|
+
def initialize(name, ns = nil)
|
22
|
+
@name = name
|
23
|
+
@ns = ns || Apricot.current_namespace
|
24
|
+
end
|
25
|
+
|
26
|
+
def bytecode(g)
|
27
|
+
if @ns.is_a?(Namespace) && !@ns.vars.include?(@name)
|
28
|
+
g.compile_error "Unable to resolve name #{@name} in namespace #{@ns}"
|
29
|
+
end
|
30
|
+
|
31
|
+
ns_id = Apricot::Identifier.intern(@ns.name)
|
32
|
+
g.push_const ns_id.const_names.first
|
33
|
+
ns_id.const_names.drop(1).each {|n| g.find_const(n) }
|
34
|
+
|
35
|
+
g.push_literal @name
|
36
|
+
|
37
|
+
if @ns.is_a? Namespace
|
38
|
+
g.send :get_var, 1
|
39
|
+
else # @ns is a regular Ruby module
|
40
|
+
g.send :method, 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def meta
|
45
|
+
@ns.is_a?(Namespace) && @ns.vars[@name] && @ns.vars[@name].apricot_meta
|
46
|
+
end
|
47
|
+
|
48
|
+
def fn?
|
49
|
+
@ns.is_a?(Namespace) && @ns.fns.include?(@name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def method?
|
53
|
+
!@ns.is_a?(Namespace) && @ns.respond_to?(@name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# For the 'self' identifier. Just like Ruby's 'self'.
|
58
|
+
class SelfReference
|
59
|
+
def bytecode(g)
|
60
|
+
g.push_self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/apricot/ast.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Apricot
|
2
|
+
class Compiler < Rubinius::Compiler
|
3
|
+
def self.compile(file, output = nil, debug = false)
|
4
|
+
compiler = new :apricot_file, :compiled_file
|
5
|
+
|
6
|
+
compiler.parser.input file
|
7
|
+
compiler.packager.print(BytecodePrinter) if debug
|
8
|
+
compiler.writer.name = output || Rubinius::Compiler.compiled_name(file)
|
9
|
+
|
10
|
+
prepare_compiled_code compiler.run
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.compile_string(code, file = nil, line = 1, debug = false)
|
14
|
+
compiler = new :apricot_string, :compiled_method
|
15
|
+
|
16
|
+
compiler.parser.input(code, file || "(none)", line)
|
17
|
+
compiler.packager.print(BytecodePrinter) if debug
|
18
|
+
|
19
|
+
prepare_compiled_code compiler.run
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.compile_node(node, file = "(none)", line = 1, debug = false)
|
23
|
+
compiler = new :apricot_bytecode, :compiled_method
|
24
|
+
|
25
|
+
compiler.generator.input AST::TopLevel.new([node], file, line, false)
|
26
|
+
compiler.packager.print(BytecodePrinter) if debug
|
27
|
+
|
28
|
+
prepare_compiled_code compiler.run
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compile_form(node, file = "(none)", line = 1, debug = false)
|
32
|
+
compile_node(AST::Node.from_value(node, line), file, line, debug)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.prepare_compiled_code(cc)
|
36
|
+
cc.scope = Rubinius::ConstantScope.new(Object)
|
37
|
+
cc
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.eval(code, file = "(eval)", line = 1, debug = false)
|
41
|
+
cc = compile_string(code, file, line, debug)
|
42
|
+
Rubinius.run_script cc
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.eval_form(form, file = "(eval)", line = 1, debug = false)
|
46
|
+
cc = compile_form(form, file, line, debug)
|
47
|
+
Rubinius.run_script cc
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.eval_node(node, file = "(eval)", line = 1, debug = false)
|
51
|
+
cc = compile_node(node, file, line, debug)
|
52
|
+
Rubinius.run_script cc
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/apricot/cons.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Apricot
|
2
|
+
class Cons
|
3
|
+
include Seq
|
4
|
+
|
5
|
+
def initialize(head, tail)
|
6
|
+
@head = head
|
7
|
+
@tail = tail.to_seq
|
8
|
+
end
|
9
|
+
|
10
|
+
def first
|
11
|
+
@head
|
12
|
+
end
|
13
|
+
|
14
|
+
def next
|
15
|
+
if @tail
|
16
|
+
@tail
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
yield first
|
24
|
+
@tail.each {|x| yield x }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Apricot
|
2
|
+
class SyntaxError < StandardError
|
3
|
+
attr_reader :filename, :line, :msg
|
4
|
+
|
5
|
+
def initialize(filename, line, msg, incomplete = false)
|
6
|
+
@filename = filename
|
7
|
+
@line = line
|
8
|
+
@msg = msg
|
9
|
+
@incomplete = incomplete
|
10
|
+
end
|
11
|
+
|
12
|
+
def incomplete?
|
13
|
+
@incomplete
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{@filename}:#{@line}: #{@msg}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class CompileError < StandardError
|
22
|
+
attr_reader :filename, :line, :msg
|
23
|
+
|
24
|
+
def initialize(filename, line, msg)
|
25
|
+
@filename = filename
|
26
|
+
@line = line
|
27
|
+
@msg = msg
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"#{@filename}:#{@line}: #{@msg}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.compile_error(file, line, msg)
|
36
|
+
raise CompileError.new(file, line, msg)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Apricot
|
2
|
+
class Identifier
|
3
|
+
attr_reader :name, :ns, :unqualified_name
|
4
|
+
|
5
|
+
@table = {}
|
6
|
+
|
7
|
+
def self.intern(name)
|
8
|
+
name = name.to_sym
|
9
|
+
@table[name] ||= new(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
private_class_method :new
|
13
|
+
|
14
|
+
def initialize(name)
|
15
|
+
@name = name
|
16
|
+
|
17
|
+
if @name =~ /\A(?:[A-Z]\w*::)*[A-Z]\w*\z/
|
18
|
+
@constant = true
|
19
|
+
@const_names = @name.to_s.split('::').map(&:to_sym)
|
20
|
+
elsif @name =~ /\A(.+?)\/(.+)\z/
|
21
|
+
@qualified = true
|
22
|
+
ns_id = Identifier.intern($1)
|
23
|
+
raise 'namespace in identifier must be a constant' unless ns_id.constant?
|
24
|
+
|
25
|
+
@ns = ns_id.const_names.reduce(Object) do |mod, name|
|
26
|
+
mod.const_get(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
@unqualified_name = $2.to_sym
|
30
|
+
else
|
31
|
+
@ns = Apricot.current_namespace
|
32
|
+
@unqualified_name = name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def qualified?
|
37
|
+
@qualified
|
38
|
+
end
|
39
|
+
|
40
|
+
def unqualified_name
|
41
|
+
@unqualified_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def constant?
|
45
|
+
@constant
|
46
|
+
end
|
47
|
+
|
48
|
+
def const_names
|
49
|
+
raise "#{@name} is not a constant" unless constant?
|
50
|
+
@const_names
|
51
|
+
end
|
52
|
+
|
53
|
+
# Copying Identifiers is not allowed.
|
54
|
+
def initialize_copy(other)
|
55
|
+
raise TypeError, "copy of #{self.class} is not allowed"
|
56
|
+
end
|
57
|
+
|
58
|
+
private :initialize_copy
|
59
|
+
|
60
|
+
alias_method :==, :equal?
|
61
|
+
alias_method :eql?, :equal?
|
62
|
+
|
63
|
+
def hash
|
64
|
+
@name.hash
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
case @name
|
69
|
+
when :true, :false, :nil, /\A(?:\+|-)?\d/
|
70
|
+
# Use arbitrary identifier syntax for identifiers that would otherwise
|
71
|
+
# be parsed as keywords or numbers
|
72
|
+
str = @name.to_s.gsub(/(\\.)|\|/) { $1 || '\|' }
|
73
|
+
"#|#{str}|"
|
74
|
+
when /\A#{Apricot::Parser::IDENTIFIER}+\z/
|
75
|
+
@name.to_s
|
76
|
+
else
|
77
|
+
str = @name.to_s.inspect[1..-2]
|
78
|
+
str.gsub!(/(\\.)|\|/) { $1 || '\|' }
|
79
|
+
"#|#{str}|"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
@name.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_sym
|
88
|
+
@name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/apricot/list.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Apricot
|
2
|
+
# A linked list implementation representing (a b c) syntax in Apricot
|
3
|
+
class List
|
4
|
+
include Seq
|
5
|
+
|
6
|
+
def self.[](*args)
|
7
|
+
list = EmptyList
|
8
|
+
args.reverse_each do |arg|
|
9
|
+
list = list.cons(arg)
|
10
|
+
end
|
11
|
+
list
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :head, :tail
|
15
|
+
|
16
|
+
def initialize(head, tail)
|
17
|
+
@head = head
|
18
|
+
@tail = tail
|
19
|
+
end
|
20
|
+
|
21
|
+
def cons(x)
|
22
|
+
List.new(x, self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def each
|
26
|
+
list = self
|
27
|
+
until list.empty?
|
28
|
+
yield list.head
|
29
|
+
list = list.tail
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
return true if self.equal? other
|
35
|
+
return false unless other.is_a? List
|
36
|
+
|
37
|
+
list = self
|
38
|
+
|
39
|
+
until list.empty?
|
40
|
+
return false if other.empty? || list.head != other.head
|
41
|
+
|
42
|
+
list = list.tail
|
43
|
+
other = other.tail
|
44
|
+
end
|
45
|
+
|
46
|
+
other.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :eql?, :==
|
50
|
+
|
51
|
+
def hash
|
52
|
+
hashes = map {|x| x.hash }
|
53
|
+
hashes.reduce(hashes.size) {|acc,hash| acc ^ hash }
|
54
|
+
end
|
55
|
+
|
56
|
+
def empty?
|
57
|
+
!@tail
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_copy(other)
|
61
|
+
super
|
62
|
+
@tail = other.tail.dup if other.tail && !other.tail.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
private :initialize_copy
|
66
|
+
|
67
|
+
def to_list
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def first
|
72
|
+
empty? ? nil : @head
|
73
|
+
end
|
74
|
+
|
75
|
+
def next
|
76
|
+
@tail.empty? ? nil : @tail
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_seq
|
80
|
+
empty? ? nil : self
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect
|
84
|
+
return '()' if empty?
|
85
|
+
|
86
|
+
str = '('
|
87
|
+
each {|x| str << x.apricot_inspect << ' ' }
|
88
|
+
str.chop!
|
89
|
+
str << ')'
|
90
|
+
end
|
91
|
+
|
92
|
+
alias_method :to_s, :inspect
|
93
|
+
|
94
|
+
EmptyList = new(nil, nil)
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Apricot
|
2
|
+
def self.macroexpand(form)
|
3
|
+
ex = macroexpand_1(form)
|
4
|
+
ex.equal?(form) ? ex : macroexpand(ex)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.macroexpand_1(form)
|
8
|
+
return form unless form.is_a? List
|
9
|
+
|
10
|
+
callee = form.first
|
11
|
+
return form unless callee.is_a?(Identifier) && !callee.constant?
|
12
|
+
|
13
|
+
name = callee.name
|
14
|
+
name_s = name.to_s
|
15
|
+
args = form.tail
|
16
|
+
|
17
|
+
# Handle the (.method receiver args*) send expression form
|
18
|
+
if name.length > 1 && name_s != '..' && name_s.start_with?('.')
|
19
|
+
raise ArgumentError, "Too few arguments to send expression, expecting (.method receiver ...)" if args.empty?
|
20
|
+
|
21
|
+
dot = Identifier.intern(:'.')
|
22
|
+
method = Identifier.intern(name_s[1..-1])
|
23
|
+
return List[dot, args.first, method, *args.tail]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Handle the (Class. args*) shorthand new form
|
27
|
+
if name.length > 1 && name_s != '..' && name_s.end_with?('.')
|
28
|
+
dot = Identifier.intern(:'.')
|
29
|
+
klass = Identifier.intern(name_s[0..-2])
|
30
|
+
new = Identifier.intern(:new)
|
31
|
+
return List[dot, klass, new, *args]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Handle defined macros
|
35
|
+
if callee.ns.is_a?(Namespace) && callee.ns.vars.include?(callee.unqualified_name)
|
36
|
+
potential_macro = callee.ns.get_var(callee.unqualified_name)
|
37
|
+
meta = potential_macro.apricot_meta
|
38
|
+
|
39
|
+
if meta && meta[:macro]
|
40
|
+
return potential_macro.call(*args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Default case
|
45
|
+
form
|
46
|
+
end
|
47
|
+
end
|
data/lib/apricot/misc.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# This file contains some things that are used in a bunch of places and don't
|
2
|
+
# fit anywhere in particular.
|
3
|
+
|
4
|
+
module Apricot
|
5
|
+
# TODO: Should this counter be thread-local?
|
6
|
+
@gensym = 0
|
7
|
+
|
8
|
+
def self.gensym(prefix = 'g')
|
9
|
+
:"#{prefix}__#{@gensym += 1}"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Apricot
|
2
|
+
class Namespace < Module
|
3
|
+
def self.find_or_create(constant)
|
4
|
+
ns = constant.const_names.reduce(Object) do |mod, name|
|
5
|
+
if mod.const_defined? name
|
6
|
+
next_mod = mod.const_get name
|
7
|
+
raise TypeError, "#{mod}::#{name} (#{next_mod}) is not a Module" unless next_mod.is_a? Module
|
8
|
+
next_mod
|
9
|
+
else
|
10
|
+
mod.const_set(name, Namespace.new)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
raise TypeError, "#{constant.name} is not a Namespace" unless ns.is_a? Namespace
|
15
|
+
|
16
|
+
ns
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :vars, :fns
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@vars = {}
|
23
|
+
@fns = Set[]
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_var(name, val)
|
27
|
+
@vars[name] = val
|
28
|
+
|
29
|
+
val = val.to_proc if val.is_a? Method
|
30
|
+
|
31
|
+
if val.is_a?(Proc) && (@fns.include?(name) || !self.respond_to?(name))
|
32
|
+
@fns.add name
|
33
|
+
define_singleton_method(name, val)
|
34
|
+
elsif @fns.include?(name)
|
35
|
+
@fns.delete name
|
36
|
+
singleton_class.send(:undef_method, name)
|
37
|
+
end
|
38
|
+
|
39
|
+
val
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_var(name)
|
43
|
+
# raise may be a function defined on the namespace so we need to
|
44
|
+
# explicitly call the Ruby raise method.
|
45
|
+
Kernel.raise NameError, "Undefined variable '#{name}' on #{self}" unless @vars.include? name
|
46
|
+
@vars[name]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def current_namespace
|
52
|
+
Core.get_var(:"*ns*")
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_namespace=(ns)
|
56
|
+
Core.set_var(:"*ns*", ns)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|