liquidscript 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 +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
|