liquidscript 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +11 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +25 -0
- data/lib/liquidscript.rb +11 -0
- data/lib/liquidscript/buffer.rb +34 -0
- data/lib/liquidscript/compiler.rb +9 -0
- data/lib/liquidscript/compiler/base.rb +101 -0
- data/lib/liquidscript/compiler/base/action.rb +39 -0
- data/lib/liquidscript/compiler/base/blank.rb +24 -0
- data/lib/liquidscript/compiler/base/callable.rb +51 -0
- data/lib/liquidscript/compiler/base/helpers.rb +207 -0
- data/lib/liquidscript/compiler/icr.rb +40 -0
- data/lib/liquidscript/compiler/icr/classes.rb +59 -0
- data/lib/liquidscript/compiler/icr/expressions.rb +94 -0
- data/lib/liquidscript/compiler/icr/functions.rb +42 -0
- data/lib/liquidscript/compiler/icr/helpers.rb +20 -0
- data/lib/liquidscript/compiler/icr/literals.rb +106 -0
- data/lib/liquidscript/errors.rb +51 -0
- data/lib/liquidscript/generator.rb +11 -0
- data/lib/liquidscript/generator/base.rb +25 -0
- data/lib/liquidscript/generator/base/dsl.rb +19 -0
- data/lib/liquidscript/generator/base/replacements.rb +33 -0
- data/lib/liquidscript/generator/context.rb +7 -0
- data/lib/liquidscript/generator/javascript.rb +37 -0
- data/lib/liquidscript/generator/javascript/literals.rb +63 -0
- data/lib/liquidscript/generator/javascript/metas.rb +41 -0
- data/lib/liquidscript/generator/javascript/objects.rb +137 -0
- data/lib/liquidscript/icr.rb +18 -0
- data/lib/liquidscript/icr/code.rb +68 -0
- data/lib/liquidscript/icr/context.rb +94 -0
- data/lib/liquidscript/icr/representable.rb +39 -0
- data/lib/liquidscript/icr/set.rb +147 -0
- data/lib/liquidscript/icr/sexp.rb +41 -0
- data/lib/liquidscript/icr/variable.rb +68 -0
- data/lib/liquidscript/scanner.rb +40 -0
- data/lib/liquidscript/scanner/lexer.rl +106 -0
- data/lib/liquidscript/scanner/token.rb +37 -0
- data/lib/liquidscript/template.rb +16 -0
- data/lib/liquidscript/version.rb +5 -0
- data/liquidscript.gemspec +27 -0
- data/spec/fixtures/class.compile.yml +26 -0
- data/spec/fixtures/class.generate.yml +31 -0
- data/spec/fixtures/combination.generate.yml +33 -0
- data/spec/fixtures/complex.generate.yml +20 -0
- data/spec/fixtures/expression.generate.yml +4 -0
- data/spec/fixtures/function.generate.yml +11 -0
- data/spec/fixtures/get.generate.yml +5 -0
- data/spec/fixtures/literals.generate.yml +8 -0
- data/spec/fixtures/main.compile.yml +32 -0
- data/spec/fixtures/set.generate.yml +4 -0
- data/spec/fixtures/string.generate.yml +6 -0
- data/spec/lib/liquidscript/buffer_spec.rb +14 -0
- data/spec/lib/liquidscript/compiler/icr_spec.rb +139 -0
- data/spec/lib/liquidscript/generator/javascript_spec.rb +15 -0
- data/spec/lib/liquidscript/icr/code_spec.rb +0 -0
- data/spec/lib/liquidscript/icr/context_spec.rb +36 -0
- data/spec/lib/liquidscript/icr/set_spec.rb +59 -0
- data/spec/lib/liquidscript/scanner/lexer_spec.rb +58 -0
- data/spec/lib/liquidscript/scanner/token_spec.rb +0 -0
- data/spec/lib/liquidscript/scanner_spec.rb +21 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/helpers/lexer_helper.rb +5 -0
- data/spec/support/matchers/be_token.rb +9 -0
- data/spec/support/matchers/compile.rb +41 -0
- data/spec/support/matchers/generate.rb +46 -0
- metadata +210 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
module Liquidscript
|
2
|
+
module Compiler
|
3
|
+
class Base
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def allowable
|
8
|
+
@_allowable ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def always(type)
|
12
|
+
case type
|
13
|
+
when Hash
|
14
|
+
allowable.merge!(type)
|
15
|
+
when Symbol
|
16
|
+
allowable[type] = type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(base)
|
22
|
+
base.extend ClassMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
# Normalizes an action for the hash passed to {#expect}. If
|
26
|
+
# a block is given, it returns that block. If the argument is
|
27
|
+
# a proc, it returns that proc. If none of those conditions are
|
28
|
+
# met, it returns the {Action}.
|
29
|
+
#
|
30
|
+
# @yield nothing.
|
31
|
+
# @param act [Proc, nil] the proc to return, if it is a proc.
|
32
|
+
# @return [Proc, Action]
|
33
|
+
def action(act = nil)
|
34
|
+
if block_given?
|
35
|
+
Proc.new
|
36
|
+
elsif act.is_a? Proc
|
37
|
+
act
|
38
|
+
else
|
39
|
+
@action
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Performs a loop while the yield returns true. This
|
44
|
+
# overwrites the core loop on purpose.
|
45
|
+
#
|
46
|
+
# @yieldreturn [Boolean] whether or not to continue looping.
|
47
|
+
# @return [void]
|
48
|
+
def loop
|
49
|
+
result = true
|
50
|
+
|
51
|
+
while result
|
52
|
+
result = yield
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Shift a token over. The given types can be any types. If
|
57
|
+
# the next token's type doesn't match any of the types, then
|
58
|
+
# it will raise an error. In order to shift any token, use
|
59
|
+
# the special type `:_`.
|
60
|
+
#
|
61
|
+
# @param types [Symbol] the token types to look for.
|
62
|
+
# @return [#type] the token.
|
63
|
+
def shift(*types)
|
64
|
+
expect types => action.shift
|
65
|
+
end
|
66
|
+
|
67
|
+
# Shifts a token if its one of the given types; if it's not,
|
68
|
+
# it returns the value of {#scanner_nil}.
|
69
|
+
#
|
70
|
+
# @param types [Symbol] the token types to look for.
|
71
|
+
# @return [#type] the token.
|
72
|
+
def maybe(*types)
|
73
|
+
expect types => action.shift, :_ => action { scanner_nil }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Checks to see if the next token is of any of the given
|
77
|
+
# types. Note that the special type `:_` does not work here.
|
78
|
+
#
|
79
|
+
# @param types [Symbol] the token types to match.
|
80
|
+
# @return [Boolean] whether or not the next token's type
|
81
|
+
# matches any of the given types.
|
82
|
+
def peek?(*types)
|
83
|
+
types.any? { |type| peek.type == type }
|
84
|
+
end
|
85
|
+
|
86
|
+
# The meat and potatos of the compiler. This maps actions to
|
87
|
+
# tokens. In its basic form, it is passed a hash, with the
|
88
|
+
# keys being token types, and the values the corresponding
|
89
|
+
# actions to take. It can be passed individual types, from
|
90
|
+
# which it'll assume the method name (normally,
|
91
|
+
# `compile_<type>`); or, you can pass it a symbol key, which
|
92
|
+
# is the last part of the method name (i.e., not including
|
93
|
+
# `compile_`). It will check the next token, and look for the
|
94
|
+
# correct action; if the next token doesn't match any of the
|
95
|
+
# given keys, it checks for the special type `:_` (which is
|
96
|
+
# basically a catch-all), and if it still doesn't get it, it
|
97
|
+
# raises an {UnexpectedError}. From there, it calls the
|
98
|
+
# corresponding block.
|
99
|
+
#
|
100
|
+
# If the block or method accepts one argument, it {#pop}s the
|
101
|
+
# token, and passes it in as an argument. If it accepts no
|
102
|
+
# arguments, it doesn't.
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# # If the next token has type `:test`, it will output
|
106
|
+
# # "hello!" If the next token has any other type, it
|
107
|
+
# # raises an error.
|
108
|
+
# expect :test => action { puts "hello!" }
|
109
|
+
# @example
|
110
|
+
# # If the next token has the type `:number`, it calls
|
111
|
+
# # the method `compile_number`; if the next token has
|
112
|
+
# # the type `:dstring`, it calls the method
|
113
|
+
# # `compile_string`.
|
114
|
+
# expect :number, :dstring => :string
|
115
|
+
# @example
|
116
|
+
# # Just pops the comma token.
|
117
|
+
# expect :comma => action.shift
|
118
|
+
# @example
|
119
|
+
# # When given a hash with one of the keys as an array, each
|
120
|
+
# # element of that array is treated as a token type, and
|
121
|
+
# # that token type is mapped to the value as an action.
|
122
|
+
# expect [:identifier, :dstring] => action.end_loop
|
123
|
+
# @example
|
124
|
+
# # When given `:_` as an argument, it'll map the next token
|
125
|
+
# # to its corresponding compile function.
|
126
|
+
# expect :_
|
127
|
+
# # If the next token's type is `:identifier`, it will call
|
128
|
+
# # `compile_identifier`.
|
129
|
+
# @param *args [Array<Symbol, Hash<Symbol, Object>>]
|
130
|
+
# @raise [UnexpectedError] if the next token's type didn't
|
131
|
+
# match any of the given types, and the `:_` type wasn't
|
132
|
+
# given.
|
133
|
+
# @return [Object] the result of the block/method call.
|
134
|
+
def expect(*args)
|
135
|
+
hash = normalize_arguments(args)
|
136
|
+
allowable = false
|
137
|
+
|
138
|
+
block = hash.fetch(peek.type) do
|
139
|
+
hash.fetch(:_) do
|
140
|
+
allowable = true
|
141
|
+
self.allowable.fetch(peek.type)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
out = if block.arity == 1
|
146
|
+
block.call pop
|
147
|
+
else
|
148
|
+
block.call
|
149
|
+
end
|
150
|
+
|
151
|
+
if allowable
|
152
|
+
expect(*args)
|
153
|
+
else
|
154
|
+
out
|
155
|
+
end
|
156
|
+
|
157
|
+
rescue KeyError
|
158
|
+
raise UnexpectedError.new(hash.keys, peek)
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
def allowable
|
164
|
+
@_allowable ||= begin
|
165
|
+
self.class.allowable.to_a.inject({}) do |m, (k, v)|
|
166
|
+
m.merge! k => Callable.new(self, v)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Normalizes the arguments for {#expect}. Turns all of the
|
172
|
+
# values into {Callable}s, and everything that's not a hash
|
173
|
+
# is merged into the hash.
|
174
|
+
#
|
175
|
+
# @param args [Array<Hash, Symbol>]
|
176
|
+
# @return [Hash]
|
177
|
+
def normalize_arguments(args)
|
178
|
+
hash = if args.last.is_a? Hash
|
179
|
+
args.pop
|
180
|
+
else
|
181
|
+
{}
|
182
|
+
end
|
183
|
+
|
184
|
+
args.inject(hash) do |h, a|
|
185
|
+
h.merge! a => if a == :_
|
186
|
+
peek.type
|
187
|
+
else
|
188
|
+
a
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
hash.inject({}) do |out, (key, value)|
|
193
|
+
c = Callable.new(self, value)
|
194
|
+
|
195
|
+
if key.is_a? Array
|
196
|
+
key.each { |k| out[k] = c }
|
197
|
+
else
|
198
|
+
out[key] = c
|
199
|
+
end
|
200
|
+
|
201
|
+
out
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "liquidscript/compiler/icr/expressions"
|
2
|
+
require "liquidscript/compiler/icr/functions"
|
3
|
+
require "liquidscript/compiler/icr/literals"
|
4
|
+
require "liquidscript/compiler/icr/classes"
|
5
|
+
require "liquidscript/compiler/icr/helpers"
|
6
|
+
|
7
|
+
module Liquidscript
|
8
|
+
module Compiler
|
9
|
+
class ICR < Base
|
10
|
+
|
11
|
+
include Expressions
|
12
|
+
include Functions
|
13
|
+
include Literals
|
14
|
+
include Classes
|
15
|
+
include Helpers
|
16
|
+
|
17
|
+
always :keyword
|
18
|
+
|
19
|
+
# (see Base#reset!)
|
20
|
+
def reset!
|
21
|
+
@top = Liquidscript::ICR::Set.new
|
22
|
+
@top.context = Liquidscript::ICR::Context.new
|
23
|
+
@set = [@top]
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# (see Base#top)
|
28
|
+
def top
|
29
|
+
@set.last
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the starting point for compiliation.
|
33
|
+
#
|
34
|
+
# @return [ICR::Code]
|
35
|
+
def compile_start
|
36
|
+
expect :class, :module, :_ => :expression
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Liquidscript
|
2
|
+
module Compiler
|
3
|
+
class ICR
|
4
|
+
module Classes
|
5
|
+
|
6
|
+
def compile_class
|
7
|
+
shift :class
|
8
|
+
name = shift :identifier
|
9
|
+
body = _compile_class_body
|
10
|
+
|
11
|
+
set name
|
12
|
+
code :class, name, body
|
13
|
+
end
|
14
|
+
|
15
|
+
def compile_module
|
16
|
+
shift :module
|
17
|
+
name = shift :identifier
|
18
|
+
body = _compile_class_body(true)
|
19
|
+
|
20
|
+
set name
|
21
|
+
code :module, name, body
|
22
|
+
end
|
23
|
+
|
24
|
+
def _compile_class_body(mod = false)
|
25
|
+
shift :lbrack
|
26
|
+
|
27
|
+
components = []
|
28
|
+
|
29
|
+
compile_object = action do
|
30
|
+
components << [
|
31
|
+
_compile_class_body_key(mod),
|
32
|
+
compile_expression
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
loop do
|
37
|
+
expect :rbrack => action.end_loop,
|
38
|
+
:comma => action.shift,
|
39
|
+
:module => action { components << compile_module },
|
40
|
+
:class => action { components << compile_class },
|
41
|
+
[:identifier, :dstring] => compile_object
|
42
|
+
end
|
43
|
+
|
44
|
+
components
|
45
|
+
end
|
46
|
+
|
47
|
+
def _compile_class_body_key(mod)
|
48
|
+
item = shift :identifier, :dstring
|
49
|
+
|
50
|
+
item = compile_property(item) if item.type == :identifier &&
|
51
|
+
peek?(:prop) && mod
|
52
|
+
|
53
|
+
shift :colon
|
54
|
+
item
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Liquidscript
|
2
|
+
module Compiler
|
3
|
+
class ICR < Base
|
4
|
+
module Expressions
|
5
|
+
|
6
|
+
# Compiles an expression. This is primarily used in a general
|
7
|
+
# context, such that anything can be returned.
|
8
|
+
#
|
9
|
+
# @return [ICR::Code]
|
10
|
+
def compile_expression
|
11
|
+
expect :number, :identifier,
|
12
|
+
:dstring, :lparen,
|
13
|
+
:sstring, :keyword,
|
14
|
+
:lbrack => :object,
|
15
|
+
:lbrace => :array,
|
16
|
+
:arrow => :function
|
17
|
+
end
|
18
|
+
|
19
|
+
# Handles an assignment of the form `identifier = expression`,
|
20
|
+
# with the argument being the identifier, and the position of
|
21
|
+
# the compiler being after it.
|
22
|
+
#
|
23
|
+
# @return [ICR::Code]
|
24
|
+
def compile_assignment(identifier)
|
25
|
+
shift :equal
|
26
|
+
value = compile_expression
|
27
|
+
|
28
|
+
if identifier.type == :identifier
|
29
|
+
variable = set(identifier)
|
30
|
+
variable.value = value
|
31
|
+
else
|
32
|
+
variable = identifier
|
33
|
+
end
|
34
|
+
|
35
|
+
code :set, variable, value
|
36
|
+
end
|
37
|
+
|
38
|
+
# We don't know, at this point, whether this is a function
|
39
|
+
# declaration, or an expression; if it's a function declaration,
|
40
|
+
# the contents of the lparen can only be commas and identifiers;
|
41
|
+
# if it's an expression, it can have anything but commas in it.
|
42
|
+
#
|
43
|
+
# @return [ICR::Code]
|
44
|
+
def compile_lparen
|
45
|
+
shift :lparen
|
46
|
+
maybe_func = 1
|
47
|
+
components = []
|
48
|
+
|
49
|
+
unless peek?(:identifier, :rparen)
|
50
|
+
maybe_func = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
expression = action do
|
54
|
+
maybe_func = 0
|
55
|
+
components << compile_expression
|
56
|
+
end
|
57
|
+
|
58
|
+
loop do
|
59
|
+
case maybe_func
|
60
|
+
when 0
|
61
|
+
expect :rparen => action.end_loop,
|
62
|
+
:_ => expression
|
63
|
+
when 1
|
64
|
+
expect :rparen => action.end_loop,
|
65
|
+
:comma => action { maybe_func = 2 },
|
66
|
+
:identifier => action { |i| components << i },
|
67
|
+
:_ => expression
|
68
|
+
when 2
|
69
|
+
expect :rparen => action.end_loop,
|
70
|
+
:comma => action.shift,
|
71
|
+
:identifier => action { |i| components << i }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
func_decl = (maybe_func == 1 && peek?(:arrow)) ||
|
76
|
+
(maybe_func == 2)
|
77
|
+
|
78
|
+
if func_decl
|
79
|
+
compile_function_with_parameters(components)
|
80
|
+
else
|
81
|
+
code(:expression, components.map do |c|
|
82
|
+
if c.is_a?(Scanner::Token)
|
83
|
+
compile_identifier(c)
|
84
|
+
else
|
85
|
+
c
|
86
|
+
end
|
87
|
+
end)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Liquidscript
|
2
|
+
module Compiler
|
3
|
+
class ICR < Base
|
4
|
+
module Functions
|
5
|
+
|
6
|
+
def compile_property(prop)
|
7
|
+
shift :prop
|
8
|
+
|
9
|
+
ref = if prop.type == :identifier
|
10
|
+
ref(prop)
|
11
|
+
else
|
12
|
+
prop
|
13
|
+
end
|
14
|
+
|
15
|
+
property = action do |ident|
|
16
|
+
code :property, ref, ident
|
17
|
+
end
|
18
|
+
|
19
|
+
code = expect :identifier => property
|
20
|
+
|
21
|
+
expect :lparen => action { compile_call(code) },
|
22
|
+
:equal => action { compile_assignment(code) },
|
23
|
+
:prop => action { compile_property(code) },
|
24
|
+
:_ => action { code }
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile_call(subject)
|
28
|
+
shift :lparen
|
29
|
+
arguments = []
|
30
|
+
|
31
|
+
loop do
|
32
|
+
expect :comma => action.shift,
|
33
|
+
:rparen => action.end_loop,
|
34
|
+
:_ => action { arguments << compile_expression }
|
35
|
+
end
|
36
|
+
|
37
|
+
code :call, subject, *arguments
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Liquidscript
|
2
|
+
module Compiler
|
3
|
+
class ICR < Base
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
def ref(literal)
|
7
|
+
top.context.get(literal.value.intern)
|
8
|
+
end
|
9
|
+
|
10
|
+
def set(literal)
|
11
|
+
top.context.set(literal.value.intern)
|
12
|
+
end
|
13
|
+
|
14
|
+
def code(type, *args)
|
15
|
+
Liquidscript::ICR::Code.new type, *args
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|