apricot 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile.lock +229 -11
- data/README.md +46 -29
- data/Rakefile +1 -1
- data/apricot.gemspec +7 -3
- data/benchmarks/factorial.rb +51 -0
- data/benchmarks/interpolate.rb +20 -0
- data/bin/apricot +5 -23
- data/examples/bot.apr +1 -4
- data/examples/cinch-bot.apr +3 -3
- data/examples/sinatra.apr +9 -0
- data/kernel/core.apr +124 -75
- data/kernel/repl.apr +37 -0
- data/lib/apricot.rb +7 -26
- data/lib/apricot/boot.rb +24 -0
- data/lib/apricot/code_loader.rb +108 -0
- data/lib/apricot/compiler.rb +265 -32
- data/lib/apricot/generator.rb +10 -3
- data/lib/apricot/identifier.rb +25 -10
- data/lib/apricot/list.rb +28 -41
- data/lib/apricot/macroexpand.rb +14 -8
- data/lib/apricot/misc.rb +2 -1
- data/lib/apricot/namespace.rb +20 -3
- data/lib/apricot/{parser.rb → reader.rb} +221 -194
- data/lib/apricot/repl.rb +67 -24
- data/lib/apricot/ruby_ext.rb +27 -16
- data/lib/apricot/scopes.rb +159 -0
- data/lib/apricot/seq.rb +43 -1
- data/lib/apricot/special_forms.rb +16 -695
- data/lib/apricot/special_forms/def.rb +32 -0
- data/lib/apricot/special_forms/do.rb +23 -0
- data/lib/apricot/special_forms/dot.rb +112 -0
- data/lib/apricot/special_forms/fn.rb +342 -0
- data/lib/apricot/special_forms/if.rb +31 -0
- data/lib/apricot/special_forms/let.rb +8 -0
- data/lib/apricot/special_forms/loop.rb +10 -0
- data/lib/apricot/special_forms/quote.rb +9 -0
- data/lib/apricot/special_forms/recur.rb +26 -0
- data/lib/apricot/special_forms/try.rb +146 -0
- data/lib/apricot/variables.rb +65 -0
- data/lib/apricot/version.rb +1 -1
- data/spec/compiler_spec.rb +53 -450
- data/spec/fn_spec.rb +206 -0
- data/spec/list_spec.rb +1 -1
- data/spec/reader_spec.rb +349 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/special_forms_spec.rb +203 -0
- metadata +99 -133
- data/lib/apricot/ast.rb +0 -3
- data/lib/apricot/ast/identifier.rb +0 -111
- data/lib/apricot/ast/list.rb +0 -99
- data/lib/apricot/ast/literals.rb +0 -240
- data/lib/apricot/ast/node.rb +0 -45
- data/lib/apricot/ast/scopes.rb +0 -147
- data/lib/apricot/ast/toplevel.rb +0 -66
- data/lib/apricot/ast/variables.rb +0 -64
- data/lib/apricot/printers.rb +0 -12
- data/lib/apricot/stages.rb +0 -60
- data/spec/parser_spec.rb +0 -312
data/lib/apricot/ast/node.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
class Node
|
4
|
-
attr_reader :line
|
5
|
-
|
6
|
-
def initialize(line)
|
7
|
-
@line = line
|
8
|
-
end
|
9
|
-
|
10
|
-
def pos(g)
|
11
|
-
g.set_line(@line)
|
12
|
-
end
|
13
|
-
|
14
|
-
def ==(other)
|
15
|
-
return true if self.equal?(other)
|
16
|
-
return false unless self.class == other.class
|
17
|
-
self.node_equal?(other)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.from_value(val, line = 0)
|
21
|
-
case val
|
22
|
-
when true then Literal.new(line, :true)
|
23
|
-
when false then Literal.new(line, :false)
|
24
|
-
when nil then Literal.new(line, :nil)
|
25
|
-
when Integer then AST.new_integer(line, val)
|
26
|
-
when Symbol then SymbolLiteral.new(line, val)
|
27
|
-
when Float then FloatLiteral.new(line, val)
|
28
|
-
when String then StringLiteral.new(line, val)
|
29
|
-
when Rational then RationalLiteral.new(line, val.numerator, val.denominator)
|
30
|
-
when Regexp then RegexLiteral.new(line, val.source, val.options)
|
31
|
-
when Array then ArrayLiteral.new(line, val.map {|x| from_value x, line})
|
32
|
-
when Set then SetLiteral.new(line, val.map {|x| from_value x, line})
|
33
|
-
when Apricot::Identifier then Identifier.new(line, val.name)
|
34
|
-
when Apricot::Seq then List.new(line, val.map {|x| from_value x, line})
|
35
|
-
when Hash
|
36
|
-
elems = []
|
37
|
-
val.each_pair {|k,v| elems << from_value(k, line) << from_value(v, line) }
|
38
|
-
HashLiteral.new(line, elems)
|
39
|
-
else
|
40
|
-
raise TypeError, "No AST node for #{val} (#{val.class})"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/lib/apricot/ast/scopes.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
# This is a scope with real local variable storage, i.e. it is part of a
|
4
|
-
# block of code like a fn or the top level program. Let scopes do not have
|
5
|
-
# storage and must ask for storage from one of these.
|
6
|
-
module StorageScope
|
7
|
-
def variable_names
|
8
|
-
@variable_names ||= []
|
9
|
-
end
|
10
|
-
|
11
|
-
def store_new_local(name)
|
12
|
-
slot = next_slot
|
13
|
-
variable_names << name
|
14
|
-
slot
|
15
|
-
end
|
16
|
-
|
17
|
-
def next_slot
|
18
|
-
variable_names.size
|
19
|
-
end
|
20
|
-
|
21
|
-
def local_count
|
22
|
-
variable_names.size
|
23
|
-
end
|
24
|
-
|
25
|
-
def local_names
|
26
|
-
variable_names
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Scope
|
31
|
-
attr_reader :parent, :variables
|
32
|
-
# The loop label stores the code location where a (recur) form should
|
33
|
-
# jump to. The secondary loop label is used in the case of recur in a fn
|
34
|
-
# overload with variadic arguments. If the array passed for the variadic
|
35
|
-
# arguments in the recur is empty, it should instead jump to the
|
36
|
-
# matching non-variadic overload, if applicable.
|
37
|
-
attr_accessor :loop_label, :secondary_loop_label
|
38
|
-
|
39
|
-
def initialize(parent)
|
40
|
-
@parent = parent
|
41
|
-
@variables = {}
|
42
|
-
@loop_label = nil
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class FnScope < Scope
|
47
|
-
attr_reader :name, :self_reference
|
48
|
-
|
49
|
-
def initialize(parent, name)
|
50
|
-
super(parent)
|
51
|
-
|
52
|
-
if name
|
53
|
-
@name = name
|
54
|
-
name_slot = @parent.store_new_local(name)
|
55
|
-
@self_reference = LocalReference.new(name_slot, 1)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# An identifier or a nested scope is looking up a variable. If the
|
60
|
-
# variable is found here, return a reference to it. Otherwise look it up
|
61
|
-
# on the parent and increment its depth because it is beyond the bounds
|
62
|
-
# of the current block of code (fn).
|
63
|
-
def find_var(name, depth = 0)
|
64
|
-
return @self_reference if name == @name
|
65
|
-
|
66
|
-
@parent.find_var(name, depth + 1)
|
67
|
-
end
|
68
|
-
|
69
|
-
# A (recur) is looking for a recursion target (ie. a loop or a fn
|
70
|
-
# overload scope).
|
71
|
-
def find_recur_target
|
72
|
-
@parent.find_recur_target
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class OverloadScope < Scope
|
77
|
-
include StorageScope
|
78
|
-
|
79
|
-
attr_accessor :splat
|
80
|
-
alias_method :splat?, :splat
|
81
|
-
|
82
|
-
attr_accessor :block_arg
|
83
|
-
|
84
|
-
def initialize(parent_fn)
|
85
|
-
super(parent_fn)
|
86
|
-
end
|
87
|
-
|
88
|
-
# An identifier or a nested scope is looking up a variable. If the
|
89
|
-
# variable is found here, return a reference to it. Otherwise look it up
|
90
|
-
# on the parent (a fn). Don't increase the depth, the lookup on the fn
|
91
|
-
# will do that, and if we do it twice then the generated
|
92
|
-
# push_local_depth instructions look up too many scopes.
|
93
|
-
def find_var(name, depth = 0)
|
94
|
-
if slot = @variables[name]
|
95
|
-
LocalReference.new(slot, depth)
|
96
|
-
else
|
97
|
-
@parent.find_var(name, depth)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Create a new local on the current level.
|
102
|
-
def new_local(name)
|
103
|
-
name = name.name if name.is_a? Identifier
|
104
|
-
@variables[name] = store_new_local(name)
|
105
|
-
end
|
106
|
-
|
107
|
-
# A (recur) is looking for a recursion target. This, being a fn
|
108
|
-
# overload, is one.
|
109
|
-
def find_recur_target
|
110
|
-
self
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# The let scope doesn't have real storage for locals. It stores its locals
|
115
|
-
# on the nearest enclosing real scope, which is any separate block of code
|
116
|
-
# such as a fn, defn, defmacro or the top level of the program.
|
117
|
-
class LetScope < Scope
|
118
|
-
# An identifier or a nested scope is looking up a variable.
|
119
|
-
def find_var(name, depth = 0)
|
120
|
-
if slot = @variables[name]
|
121
|
-
LocalReference.new(slot, depth)
|
122
|
-
else
|
123
|
-
@parent.find_var(name, depth)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# Create a new local on the current level, with storage on the nearest
|
128
|
-
# enclosing real scope.
|
129
|
-
def new_local(name)
|
130
|
-
name = name.name if name.is_a? Identifier
|
131
|
-
@variables[name] = @parent.store_new_local(name)
|
132
|
-
end
|
133
|
-
|
134
|
-
# A deeper let is asking for a new local slot. Pass it along to the
|
135
|
-
# parent so it eventually reaches a real scope.
|
136
|
-
def store_new_local(name)
|
137
|
-
@parent.store_new_local(name)
|
138
|
-
end
|
139
|
-
|
140
|
-
# A (recur) is looking for a recursion target. This is one only if it is
|
141
|
-
# a (loop) form.
|
142
|
-
def find_recur_target
|
143
|
-
loop_label ? self : @parent.find_recur_target
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
data/lib/apricot/ast/toplevel.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
module AST
|
3
|
-
class TopLevel < Node
|
4
|
-
include StorageScope
|
5
|
-
|
6
|
-
attr_reader :elements, :file
|
7
|
-
|
8
|
-
def initialize(elements, file, line = 1, evaluate = false)
|
9
|
-
@elements = elements
|
10
|
-
@file = file
|
11
|
-
@line = line
|
12
|
-
@evaluate = evaluate
|
13
|
-
end
|
14
|
-
|
15
|
-
def bytecode(g)
|
16
|
-
g.name = :__top_level__
|
17
|
-
g.file = @file.to_sym
|
18
|
-
|
19
|
-
g.scopes << self
|
20
|
-
|
21
|
-
pos(g)
|
22
|
-
|
23
|
-
if @elements.empty?
|
24
|
-
g.push_nil
|
25
|
-
else
|
26
|
-
@elements.each_with_index do |e, i|
|
27
|
-
g.pop unless i == 0
|
28
|
-
e.bytecode(g)
|
29
|
-
# We evaluate top level forms as we generate the bytecode for them
|
30
|
-
# so macros defined in a file can be used immediately after the
|
31
|
-
# definition.
|
32
|
-
Apricot::Compiler.eval_node(e, @file) if @evaluate
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
g.ret
|
37
|
-
|
38
|
-
g.scopes.pop
|
39
|
-
|
40
|
-
g.local_count = local_count
|
41
|
-
g.local_names = local_names
|
42
|
-
end
|
43
|
-
|
44
|
-
# A nested scope is looking up a variable. There are no local variables
|
45
|
-
# at the top level, so look up the variable on the current namespace.
|
46
|
-
def find_var(name, depth = nil)
|
47
|
-
# Ignore depth, it has no bearing on namespace lookups.
|
48
|
-
NamespaceReference.new(name)
|
49
|
-
end
|
50
|
-
|
51
|
-
def node_equal?(other)
|
52
|
-
self.file == other.file && self.elements == other.elements
|
53
|
-
end
|
54
|
-
|
55
|
-
def [](*i)
|
56
|
-
@elements[*i]
|
57
|
-
end
|
58
|
-
|
59
|
-
# A (recur) is looking for a recursion target. Since this is the top
|
60
|
-
# level, which has no parent, the lookup has failed.
|
61
|
-
def find_recur_target
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,64 +0,0 @@
|
|
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/printers.rb
DELETED
data/lib/apricot/stages.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module Apricot
|
2
|
-
class Compiler
|
3
|
-
class Generator < Rubinius::Compiler::Stage
|
4
|
-
stage :apricot_bytecode
|
5
|
-
next_stage Rubinius::Compiler::Encoder
|
6
|
-
|
7
|
-
def initialize(compiler, last)
|
8
|
-
super
|
9
|
-
compiler.generator = self
|
10
|
-
end
|
11
|
-
|
12
|
-
def run
|
13
|
-
@output = Apricot::Generator.new
|
14
|
-
@input.bytecode @output
|
15
|
-
@output.close
|
16
|
-
run_next
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class Parser < Rubinius::Compiler::Stage
|
21
|
-
def initialize(compiler, last)
|
22
|
-
super
|
23
|
-
compiler.parser = self
|
24
|
-
end
|
25
|
-
|
26
|
-
def run
|
27
|
-
@output = parse
|
28
|
-
run_next
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class FileParser < Parser
|
33
|
-
stage :apricot_file
|
34
|
-
next_stage Generator
|
35
|
-
|
36
|
-
def input(file)
|
37
|
-
@file = file
|
38
|
-
end
|
39
|
-
|
40
|
-
def parse
|
41
|
-
Apricot::Parser.parse_file(@file)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class StringParser < Parser
|
46
|
-
stage :apricot_string
|
47
|
-
next_stage Generator
|
48
|
-
|
49
|
-
def input(code, file = "(none)", line = 1)
|
50
|
-
@input = code
|
51
|
-
@file = file
|
52
|
-
@line = line
|
53
|
-
end
|
54
|
-
|
55
|
-
def parse
|
56
|
-
Apricot::Parser.parse_string(@input, @file, @line)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/spec/parser_spec.rb
DELETED
@@ -1,312 +0,0 @@
|
|
1
|
-
describe Apricot::Parser do
|
2
|
-
def parse(s)
|
3
|
-
@ast = described_class.parse_string(s, "(spec)")
|
4
|
-
@first = @ast.elements.first
|
5
|
-
@ast.elements
|
6
|
-
end
|
7
|
-
|
8
|
-
def parse_one(s, klass)
|
9
|
-
parse(s).length.should == 1
|
10
|
-
@first.should be_a(klass)
|
11
|
-
@first
|
12
|
-
end
|
13
|
-
|
14
|
-
def expect_syntax_error(s)
|
15
|
-
expect { parse(s) }.to raise_error(Apricot::SyntaxError)
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'parses nothing' do
|
19
|
-
parse('').should be_empty
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'skips whitespace' do
|
23
|
-
parse(" \n\t,").should be_empty
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'skips comments' do
|
27
|
-
parse('; example').should be_empty
|
28
|
-
parse('#!/usr/bin/env apricot').should be_empty
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'discards commented forms' do
|
32
|
-
parse('#_form').should be_empty
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'parses identifiers' do
|
36
|
-
parse_one('example', AST::Identifier)
|
37
|
-
@first.name.should == :example
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'parses pipe identifiers' do
|
41
|
-
parse_one('#|example|', AST::Identifier).name.should == :example
|
42
|
-
parse_one('#|foo bar|', AST::Identifier).name.should == :"foo bar"
|
43
|
-
parse_one('#|foo\nbar|', AST::Identifier).name.should == :"foo\nbar"
|
44
|
-
parse_one('#|foo\|bar|', AST::Identifier).name.should == :"foo|bar"
|
45
|
-
parse_one('#|foo"bar|', AST::Identifier).name.should == :'foo"bar'
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'parses constants' do
|
49
|
-
parse_one('Example', AST::Identifier)
|
50
|
-
@first.constant?.should be_true
|
51
|
-
@first.const_names.should == [:Example]
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'parses invalid constants as identifiers' do
|
55
|
-
parse_one('Fo$o', AST::Identifier)
|
56
|
-
@first.constant?.should be_false
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'parses scoped constants' do
|
60
|
-
parse_one('Foo::Bar::Baz', AST::Identifier)
|
61
|
-
@first.constant?.should be_true
|
62
|
-
@first.const_names.should == [:Foo, :Bar, :Baz]
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'parses invalid scoped constants as identifiers' do
|
66
|
-
parse_one('Foo::', AST::Identifier)
|
67
|
-
@first.constant?.should be_false
|
68
|
-
parse_one('Foo:', AST::Identifier)
|
69
|
-
@first.constant?.should be_false
|
70
|
-
parse_one('Foo::a', AST::Identifier)
|
71
|
-
@first.constant?.should be_false
|
72
|
-
parse_one('Foo::::Bar', AST::Identifier)
|
73
|
-
@first.constant?.should be_false
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'parses true, false, nil, and self' do
|
77
|
-
parse('true false nil self').length.should == 4
|
78
|
-
@ast[0].should be_a(AST::Literal)
|
79
|
-
@ast[0].value.should == :true
|
80
|
-
@ast[1].should be_a(AST::Literal)
|
81
|
-
@ast[1].value.should == :false
|
82
|
-
@ast[2].should be_a(AST::Literal)
|
83
|
-
@ast[2].value.should == :nil
|
84
|
-
@ast[3].should be_a(AST::Identifier)
|
85
|
-
@ast[3].name.should == :self
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'parses fixnums' do
|
89
|
-
parse_one('123', AST::Literal)
|
90
|
-
@first.value.should == 123
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'parses bignums' do
|
94
|
-
parse_one('12345678901234567890', AST::BignumLiteral)
|
95
|
-
@first.value.should == 12345678901234567890
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'parses radix integers' do
|
99
|
-
parse_one('2r10', AST::Literal)
|
100
|
-
@first.value.should == 2
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'parses floats' do
|
104
|
-
parse_one('1.23', AST::FloatLiteral)
|
105
|
-
@first.value.should == 1.23
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'parses rationals' do
|
109
|
-
parse_one('12/34', AST::RationalLiteral)
|
110
|
-
@first.numerator.should == 12
|
111
|
-
@first.denominator.should == 34
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'does not parse invalid numbers' do
|
115
|
-
expect_syntax_error '12abc'
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'parses empty strings' do
|
119
|
-
parse_one('""', AST::StringLiteral)
|
120
|
-
@first.value.should == ''
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'parses strings' do
|
124
|
-
parse_one('"Hello, world!"', AST::StringLiteral)
|
125
|
-
@first.value.should == 'Hello, world!'
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'parses multiline strings' do
|
129
|
-
parse_one(%{"This is\na test"}, AST::StringLiteral)
|
130
|
-
@first.value.should == "This is\na test"
|
131
|
-
end
|
132
|
-
|
133
|
-
it 'does not parse unfinished strings' do
|
134
|
-
expect_syntax_error '"'
|
135
|
-
end
|
136
|
-
|
137
|
-
it 'parses strings with character escapes' do
|
138
|
-
parse_one('"\\a\\b\\t\\n\\v\\f\\r\\e\\"\\\\"', AST::StringLiteral)
|
139
|
-
@first.value.should == "\a\b\t\n\v\f\r\e\"\\"
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'parses strings with octal escapes' do
|
143
|
-
parse_one('"\\1\\01\\001"', AST::StringLiteral)
|
144
|
-
@first.value.should == "\001\001\001"
|
145
|
-
end
|
146
|
-
|
147
|
-
it 'parses strings with hex escapes' do
|
148
|
-
parse_one('"\\x1\\x01"', AST::StringLiteral)
|
149
|
-
@first.value.should == "\001\001"
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'does not parse strings with invalid hex escapes' do
|
153
|
-
expect_syntax_error '"\\x"'
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'stops parsing hex/octal escapes in strings at non-hex/octal digits' do
|
157
|
-
parse_one('"\xAZ\082"', AST::StringLiteral)
|
158
|
-
@first.value.should == "\x0AZ\00082"
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'parses regexes' do
|
162
|
-
parse_one('#r!!', AST::RegexLiteral).pattern.should == ''
|
163
|
-
parse_one('#r!egex!', AST::RegexLiteral).pattern.should == 'egex'
|
164
|
-
parse_one('#r(egex)', AST::RegexLiteral).pattern.should == 'egex'
|
165
|
-
parse_one('#r[egex]', AST::RegexLiteral).pattern.should == 'egex'
|
166
|
-
parse_one('#r{egex}', AST::RegexLiteral).pattern.should == 'egex'
|
167
|
-
parse_one('#r<egex>', AST::RegexLiteral).pattern.should == 'egex'
|
168
|
-
parse_one('#r!\!!', AST::RegexLiteral).pattern.should == '!'
|
169
|
-
parse_one('#r!foo\bar!', AST::RegexLiteral).pattern.should == 'foo\bar'
|
170
|
-
parse_one('#r!\\\\!', AST::RegexLiteral).pattern.should == "\\\\"
|
171
|
-
end
|
172
|
-
|
173
|
-
it 'parses regexes with trailing options' do
|
174
|
-
parse_one('#r//i', AST::RegexLiteral)
|
175
|
-
@first.options.should == Regexp::IGNORECASE
|
176
|
-
parse_one('#r/foo/x', AST::RegexLiteral)
|
177
|
-
@first.options.should == Regexp::EXTENDED
|
178
|
-
parse_one('#r//im', AST::RegexLiteral)
|
179
|
-
@first.options.should == Regexp::IGNORECASE | Regexp::MULTILINE
|
180
|
-
end
|
181
|
-
|
182
|
-
it 'does not parse regexes with unknown trailing options' do
|
183
|
-
expect_syntax_error '#r/foo/asdf'
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'parses symbols' do
|
187
|
-
parse_one(':example', AST::SymbolLiteral)
|
188
|
-
@first.value.should == :example
|
189
|
-
end
|
190
|
-
|
191
|
-
it 'parses quoted symbols' do
|
192
|
-
parse_one(':"\x01()"', AST::SymbolLiteral)
|
193
|
-
@first.value.should == :"\x01()"
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'does not parse unfinished quoted symbols' do
|
197
|
-
expect_syntax_error ':"'
|
198
|
-
end
|
199
|
-
|
200
|
-
it 'does not parse empty symbols' do
|
201
|
-
expect_syntax_error ':'
|
202
|
-
end
|
203
|
-
|
204
|
-
it 'does parse empty quoted symbols' do
|
205
|
-
parse_one(':""', AST::SymbolLiteral)
|
206
|
-
@first.value.should == :""
|
207
|
-
end
|
208
|
-
|
209
|
-
it 'parses empty lists' do
|
210
|
-
parse_one('()', AST::List)
|
211
|
-
@first.elements.should be_empty
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'parses lists' do
|
215
|
-
parse_one('(1 two)', AST::List)
|
216
|
-
@first[0].should be_a(AST::Literal)
|
217
|
-
@first[1].should be_a(AST::Identifier)
|
218
|
-
end
|
219
|
-
|
220
|
-
it 'parses empty arrays' do
|
221
|
-
parse_one('[]', AST::ArrayLiteral)
|
222
|
-
@first.elements.should be_empty
|
223
|
-
end
|
224
|
-
|
225
|
-
it 'parses arrays' do
|
226
|
-
parse_one('[1 two]', AST::ArrayLiteral)
|
227
|
-
@first[0].should be_a(AST::Literal)
|
228
|
-
@first[1].should be_a(AST::Identifier)
|
229
|
-
end
|
230
|
-
|
231
|
-
it 'parses empty hashes' do
|
232
|
-
parse_one('{}', AST::HashLiteral)
|
233
|
-
@first.elements.should be_empty
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'parses hashes' do
|
237
|
-
parse_one('{:example 1}', AST::HashLiteral)
|
238
|
-
@first[0].should be_a(AST::SymbolLiteral)
|
239
|
-
@first[1].should be_a(AST::Literal)
|
240
|
-
end
|
241
|
-
|
242
|
-
it 'does not parse invalid hashes' do
|
243
|
-
expect_syntax_error '{:foo 1 :bar}'
|
244
|
-
end
|
245
|
-
|
246
|
-
it 'parses empty sets' do
|
247
|
-
parse_one('#{}', AST::SetLiteral)
|
248
|
-
@first.elements.should be_empty
|
249
|
-
end
|
250
|
-
|
251
|
-
it 'parses sets' do
|
252
|
-
parse_one('#{1 two}', AST::SetLiteral)
|
253
|
-
@first[0].should be_a(AST::Literal)
|
254
|
-
@first[1].should be_a(AST::Identifier)
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'parses multiple forms' do
|
258
|
-
parse('foo bar').length.should == 2
|
259
|
-
@ast[0].should be_a(AST::Identifier)
|
260
|
-
@ast[1].should be_a(AST::Identifier)
|
261
|
-
end
|
262
|
-
|
263
|
-
it 'parses quoted forms' do
|
264
|
-
parse_one("'test", AST::List)
|
265
|
-
@first.elements.length.should == 2
|
266
|
-
@first[0].should be_a(AST::Identifier)
|
267
|
-
@first[0].name.should == :quote
|
268
|
-
@first[1].should be_a(AST::Identifier)
|
269
|
-
@first[1].name.should == :test
|
270
|
-
end
|
271
|
-
|
272
|
-
it 'parses syntax quoted forms' do
|
273
|
-
begin
|
274
|
-
old_gensym = Apricot.instance_variable_get :@gensym
|
275
|
-
Apricot.instance_variable_set :@gensym, 41
|
276
|
-
|
277
|
-
parse_one("`(foo ~bar ~@baz quux#)", AST::List)
|
278
|
-
@first.should == AST::Node.from_value(
|
279
|
-
List[Identifier.intern(:concat),
|
280
|
-
List[Identifier.intern(:list),
|
281
|
-
List[Identifier.intern(:quote),
|
282
|
-
Identifier.intern(:foo)]],
|
283
|
-
List[Identifier.intern(:list),
|
284
|
-
Identifier.intern(:bar)],
|
285
|
-
Identifier.intern(:baz),
|
286
|
-
List[Identifier.intern(:list),
|
287
|
-
List[Identifier.intern(:quote),
|
288
|
-
Identifier.intern(:'quux#__42')]]])
|
289
|
-
ensure
|
290
|
-
Apricot.instance_variable_set :@gensym, old_gensym
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
it 'parses #() shorthand' do
|
295
|
-
Apricot.stub(:gensym).and_return(*:a..:z)
|
296
|
-
|
297
|
-
parse("#()").should == parse("(fn [] ())")
|
298
|
-
parse("#(foo)").should == parse("(fn [] (foo))")
|
299
|
-
parse("#(%)").should == parse("(fn [a] (a))")
|
300
|
-
parse("#(% %2)").should == parse("(fn [b c] (b c))")
|
301
|
-
parse("#(%1 %2)").should == parse("(fn [d e] (d e))")
|
302
|
-
parse("#(%2)").should == parse("(fn [g f] (f))")
|
303
|
-
parse("#(%&)").should == parse("(fn [& h] (h))")
|
304
|
-
parse("#(% %&)").should == parse("(fn [i & j] (i j))")
|
305
|
-
|
306
|
-
expect_syntax_error("#(%0)")
|
307
|
-
expect_syntax_error("#(%-1)")
|
308
|
-
expect_syntax_error("#(%x)")
|
309
|
-
expect_syntax_error("#(%1.1)")
|
310
|
-
expect_syntax_error("#(%1asdf)")
|
311
|
-
end
|
312
|
-
end
|