rbexy 0.1.0
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/.rspec +1 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +213 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +452 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +15 -0
- data/example.rb +113 -0
- data/lib/rbexy.rb +35 -0
- data/lib/rbexy/component.rb +100 -0
- data/lib/rbexy/component_providers/rbexy_provider.rb +23 -0
- data/lib/rbexy/component_providers/view_component_provider.rb +23 -0
- data/lib/rbexy/component_tag_builder.rb +19 -0
- data/lib/rbexy/configuration.rb +15 -0
- data/lib/rbexy/hash_mash.rb +15 -0
- data/lib/rbexy/lexer.rb +279 -0
- data/lib/rbexy/nodes.rb +142 -0
- data/lib/rbexy/output_buffer.rb +8 -0
- data/lib/rbexy/parser.rb +204 -0
- data/lib/rbexy/rails.rb +8 -0
- data/lib/rbexy/rails/engine.rb +25 -0
- data/lib/rbexy/rails/template_handler.rb +9 -0
- data/lib/rbexy/runtime.rb +33 -0
- data/lib/rbexy/version.rb +3 -0
- data/lib/rbexy/view_context_helper.rb +11 -0
- data/rbexy.gemspec +40 -0
- metadata +269 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rbexy"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
version: '3'
|
2
|
+
|
3
|
+
volumes:
|
4
|
+
bundle:
|
5
|
+
|
6
|
+
services:
|
7
|
+
rbexy:
|
8
|
+
build: .
|
9
|
+
volumes:
|
10
|
+
- .:/app
|
11
|
+
- bundle:/usr/local/bundle
|
12
|
+
- $HOME/.ssh:/root/.ssh:ro
|
13
|
+
- $HOME/.gitconfig:/root/.gitconfig:ro
|
14
|
+
- $HOME/.gem/credentials:/root/.gem/credentials
|
15
|
+
working_dir: /app
|
data/example.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.require
|
3
|
+
|
4
|
+
require "active_support/all"
|
5
|
+
require "action_view/helpers"
|
6
|
+
require "action_view/context"
|
7
|
+
require "action_view/buffers"
|
8
|
+
|
9
|
+
template_string = <<-RBX
|
10
|
+
# A comment here
|
11
|
+
<div>
|
12
|
+
<h1 {**{ class: "myClass" }} {**splat_attrs}>Hello world</h1>
|
13
|
+
<div {**{ class: "myClass" }}></div>
|
14
|
+
Some words
|
15
|
+
# A comment here
|
16
|
+
# A comment here
|
17
|
+
<p>Lorem ipsum</p>
|
18
|
+
<input type="submit" value={@ivar_val} disabled />
|
19
|
+
{true && <p>Is true</p>}
|
20
|
+
{false && <p>Is false</p>}
|
21
|
+
{true ? <p {**{ class: "myClass" }}>Ternary is {'true'.upcase}</p> : <p>Ternary is false</p>}
|
22
|
+
<Button prop1="val1" prop2={true && "val2"} multi-word-prop="value">the content</Button>
|
23
|
+
<Forms.TextField label={->(n) { <label id={n}>Something</label> }} note={<p>the note</p>} />
|
24
|
+
<ul>
|
25
|
+
# A comment here
|
26
|
+
{["hi", "there", "nick"].map { |val| <li>{val}</li> }}
|
27
|
+
</ul>
|
28
|
+
<p
|
29
|
+
class="something">Text</p>
|
30
|
+
<input
|
31
|
+
class="foobar"
|
32
|
+
/>
|
33
|
+
<div
|
34
|
+
with="lots"
|
35
|
+
of="attributes"
|
36
|
+
>
|
37
|
+
Content
|
38
|
+
</div>
|
39
|
+
# comment
|
40
|
+
</div>
|
41
|
+
# comment
|
42
|
+
RBX
|
43
|
+
|
44
|
+
module Components
|
45
|
+
class ButtonComponent
|
46
|
+
def initialize(prop1:, prop2:, multi_word_prop:)
|
47
|
+
@prop1 = prop1
|
48
|
+
@prop2 = prop2
|
49
|
+
@multi_word_prop = multi_word_prop
|
50
|
+
end
|
51
|
+
|
52
|
+
def render
|
53
|
+
# Render it yourself, call one of Rails view helpers (link_to,
|
54
|
+
# content_tag, etc), or use a template file. Be sure to render
|
55
|
+
# children by yielding to the given block.
|
56
|
+
"<button class=\"#{[@prop1, @prop2, @multi_word_prop].join("-")}\">#{yield}</button>"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module Forms
|
61
|
+
class TextFieldComponent
|
62
|
+
def initialize(label:, note:, **attrs)
|
63
|
+
@label = label
|
64
|
+
@note = note
|
65
|
+
end
|
66
|
+
|
67
|
+
def render
|
68
|
+
"#{@label.call(2)} <input type=\"text\" />#{@note}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class ComponentProvider
|
75
|
+
def match?(name)
|
76
|
+
find(name) != nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def render(context, name, **attrs, &block)
|
80
|
+
props = attrs.transform_keys { |k| ActiveSupport::Inflector.underscore(k.to_s).to_sym }
|
81
|
+
find(name).new(**props).render(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def find(name)
|
87
|
+
ActiveSupport::Inflector.constantize("Components::#{name}Component")
|
88
|
+
rescue NameError => e
|
89
|
+
raise e unless e.message =~ /constant/
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class MyRuntime < Rbexy::Runtime
|
95
|
+
def initialize(component_provider)
|
96
|
+
super(component_provider)
|
97
|
+
@ivar_val = "ivar value"
|
98
|
+
end
|
99
|
+
|
100
|
+
def splat_attrs
|
101
|
+
{
|
102
|
+
key1: "val1",
|
103
|
+
key2: "val2"
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
puts "=============== Compiled ruby code ==============="
|
109
|
+
code = Rbexy.compile(template_string)
|
110
|
+
puts code
|
111
|
+
|
112
|
+
puts "=============== Result of eval ==============="
|
113
|
+
puts MyRuntime.new(ComponentProvider.new).evaluate(code)
|
data/lib/rbexy.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "rbexy/version"
|
2
|
+
|
3
|
+
module Rbexy
|
4
|
+
autoload :Lexer, "rbexy/lexer"
|
5
|
+
autoload :Parser, "rbexy/parser"
|
6
|
+
autoload :Nodes, "rbexy/nodes"
|
7
|
+
autoload :Runtime, "rbexy/runtime"
|
8
|
+
autoload :HashMash, "rbexy/hash_mash"
|
9
|
+
autoload :OutputBuffer, "rbexy/output_buffer"
|
10
|
+
autoload :ComponentTagBuilder, "rbexy/component_tag_builder"
|
11
|
+
autoload :ViewContextHelper, "rbexy/view_context_helper"
|
12
|
+
autoload :Configuration, "rbexy/configuration"
|
13
|
+
|
14
|
+
ContextNotFound = Class.new(StandardError)
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def configure
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
|
21
|
+
def configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def compile(template_string)
|
26
|
+
tokens = Rbexy::Lexer.new(template_string).tokenize
|
27
|
+
template = Rbexy::Parser.new(tokens).parse
|
28
|
+
template.compile
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate(template_string, runtime)
|
32
|
+
runtime.evaluate compile(template_string)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module Rbexy
|
4
|
+
class Component < ActionView::Base
|
5
|
+
class LookupContext < ActionView::LookupContext
|
6
|
+
def self.details_hash(context)
|
7
|
+
context.registered_details.each_with_object({}) do |key, details_hash|
|
8
|
+
value = key == :locale ? [context.locale] : context.send(key)
|
9
|
+
details_hash[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# We override any calls to args_for_lookup and set partial=false so that
|
14
|
+
# the lookup context doesn't automatically add a `_` prefix to the
|
15
|
+
# template path, since we're using the Rails partial-rendering
|
16
|
+
# functionality but don't want our templates prefixed with a `_`
|
17
|
+
def args_for_lookup(name, prefixes, partial, keys, details_options)
|
18
|
+
super(name, prefixes, false, keys, details_options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(view_context, **props)
|
23
|
+
super(
|
24
|
+
view_context.lookup_context,
|
25
|
+
view_context.assigns,
|
26
|
+
view_context.controller
|
27
|
+
)
|
28
|
+
|
29
|
+
@view_context = view_context
|
30
|
+
|
31
|
+
setup(**props)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Override in your subclass to handle props, setup your component, etc.
|
35
|
+
# You can also implement `initialize` but you just need to remember to
|
36
|
+
# call super(view_context).
|
37
|
+
def setup(**props); end
|
38
|
+
|
39
|
+
def render(&block)
|
40
|
+
@content = nil
|
41
|
+
@content_block = block_given? ? block : nil
|
42
|
+
call
|
43
|
+
end
|
44
|
+
|
45
|
+
def call
|
46
|
+
old_lookup_context = view_renderer.lookup_context
|
47
|
+
view_renderer.lookup_context = build_lookup_context(old_lookup_context)
|
48
|
+
view_renderer.render(self, partial: component_name, &nil)
|
49
|
+
ensure
|
50
|
+
view_renderer.lookup_context = old_lookup_context
|
51
|
+
end
|
52
|
+
|
53
|
+
def content
|
54
|
+
@content ||= content_block ? view_context.capture(self, &content_block) : ""
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_context(name, value)
|
58
|
+
rbexy_context.last[name] = value
|
59
|
+
end
|
60
|
+
|
61
|
+
def use_context(name)
|
62
|
+
index = rbexy_context.rindex { |c| c.has_key?(name) }
|
63
|
+
index ?
|
64
|
+
rbexy_context[index][name] :
|
65
|
+
raise(ContextNotFound, "no parent context `#{name}`")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :view_context, :content_block
|
71
|
+
|
72
|
+
def build_lookup_context(existing_context)
|
73
|
+
paths = existing_context.view_paths.dup.unshift(
|
74
|
+
*Rbexy.configuration.template_paths.map { |p| ActionView::OptimizedFileSystemResolver.new(p) }
|
75
|
+
)
|
76
|
+
|
77
|
+
LookupContext.new(
|
78
|
+
paths,
|
79
|
+
LookupContext.details_hash(existing_context),
|
80
|
+
Rbexy.configuration.template_prefixes
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def view_renderer
|
85
|
+
view_context.view_renderer
|
86
|
+
end
|
87
|
+
|
88
|
+
def component_name
|
89
|
+
self.class.name.underscore
|
90
|
+
end
|
91
|
+
|
92
|
+
def method_missing(meth, *args, &block)
|
93
|
+
if view_context.respond_to?(meth)
|
94
|
+
view_context.send(meth, *args, &block)
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rbexy
|
2
|
+
module ComponentProviders
|
3
|
+
class RbexyProvider
|
4
|
+
def match?(name)
|
5
|
+
name =~ /^[A-Z]/ && find(name) != nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def render(context, name, **attrs, &block)
|
9
|
+
props = attrs.transform_keys { |k| ActiveSupport::Inflector.underscore(k.to_s).to_sym }
|
10
|
+
find(name).new(context, **props).render(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find(name)
|
16
|
+
ActiveSupport::Inflector.constantize("#{name}Component")
|
17
|
+
rescue NameError => e
|
18
|
+
raise e unless e.message =~ /wrong constant name/ || e.message =~ /uninitialized constant/
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rbexy
|
2
|
+
module ComponentProviders
|
3
|
+
class ViewComponentProvider
|
4
|
+
def match?(name)
|
5
|
+
name =~ /^[A-Z]/ && find(name) != nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def render(context, name, **attrs, &block)
|
9
|
+
props = attrs.transform_keys { |k| ActiveSupport::Inflector.underscore(k.to_s).to_sym }
|
10
|
+
find(name).new(**props).render_in(context, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find(name)
|
16
|
+
ActiveSupport::Inflector.constantize("#{name}Component")
|
17
|
+
rescue NameError => e
|
18
|
+
raise e unless e.message =~ /wrong constant name/ || e.message =~ /uninitialized constant/
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rbexy
|
2
|
+
class ComponentTagBuilder < ActionView::Helpers::TagHelper::TagBuilder
|
3
|
+
attr_reader :component_provider
|
4
|
+
|
5
|
+
def initialize(context, component_provider)
|
6
|
+
super(context)
|
7
|
+
@component_provider = component_provider
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(called, *args, **attrs, &block)
|
11
|
+
component_name = called.to_s.gsub("__", "::")
|
12
|
+
if component_provider.match?(component_name)
|
13
|
+
component_provider.render(@view_context, component_name, **attrs, &block)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rbexy
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :component_provider
|
4
|
+
attr_accessor :template_paths
|
5
|
+
attr_accessor :template_prefixes
|
6
|
+
|
7
|
+
def template_paths
|
8
|
+
@template_paths ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def template_prefixes
|
12
|
+
@template_prefixes ||= []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/rbexy/lexer.rb
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
module Rbexy
|
2
|
+
class Lexer
|
3
|
+
class SyntaxError < StandardError
|
4
|
+
def initialize(lexer)
|
5
|
+
super(
|
6
|
+
"Invalid syntax: `#{lexer.scanner.peek(20)}`\n" +
|
7
|
+
"Stack: #{lexer.stack}\n" +
|
8
|
+
"Tokens: #{lexer.tokens}"
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Patterns = HashMash.new(
|
14
|
+
open_expression: /{/,
|
15
|
+
close_expression: /}/,
|
16
|
+
expression_content: /[^}{"'<]+/,
|
17
|
+
open_tag_def: /<(?!\/)/,
|
18
|
+
open_tag_end: /<\//,
|
19
|
+
close_tag: /\s*\/?>/,
|
20
|
+
close_self_closing_tag: /\s*\/>/,
|
21
|
+
tag_name: /\/?[A-Za-z0-9\-_.]+/,
|
22
|
+
text_content: /[^<{#]+/,
|
23
|
+
comment: /^\p{Blank}*#.*(\n|\z)/,
|
24
|
+
whitespace: /\s+/,
|
25
|
+
attr: /[A-Za-z0-9\-_\.]+/,
|
26
|
+
open_attr_splat: /{\*\*/,
|
27
|
+
attr_assignment: /=/,
|
28
|
+
double_quote: /"/,
|
29
|
+
single_quote: /'/,
|
30
|
+
double_quoted_text_content: /[^"]+/,
|
31
|
+
single_quoted_text_content: /[^']+/,
|
32
|
+
expression_internal_tag_prefixes: /(\s+(&&|\?|:|do|do\s*\|[^\|]+\||{|{\s*\|[^\|]+\|)\s+\z|\A\s*\z)/,
|
33
|
+
declaration: /<![^>]*>/
|
34
|
+
)
|
35
|
+
|
36
|
+
attr_reader :stack, :tokens, :scanner, :curr_expr_quote_levels
|
37
|
+
attr_accessor :curr_expr_bracket_levels, :curr_expr, :curr_default_text,
|
38
|
+
:curr_quoted_text
|
39
|
+
|
40
|
+
def initialize(code)
|
41
|
+
@stack = [:default]
|
42
|
+
@curr_expr_bracket_levels = 0
|
43
|
+
@curr_expr_quote_levels = { single: 0, double: 0 }
|
44
|
+
@curr_expr = ""
|
45
|
+
@curr_default_text = ""
|
46
|
+
@curr_quoted_text = ""
|
47
|
+
@tokens = []
|
48
|
+
@scanner = StringScanner.new(code)
|
49
|
+
end
|
50
|
+
|
51
|
+
def tokenize
|
52
|
+
until scanner.eos?
|
53
|
+
case stack.last
|
54
|
+
when :default
|
55
|
+
if scanner.scan(Patterns.declaration)
|
56
|
+
tokens << [:DECLARATION, scanner.matched]
|
57
|
+
elsif scanner.scan(Patterns.open_tag_def)
|
58
|
+
open_tag_def
|
59
|
+
elsif scanner.scan(Patterns.open_expression)
|
60
|
+
open_expression
|
61
|
+
elsif scanner.scan(Patterns.comment)
|
62
|
+
tokens << [:SILENT_NEWLINE]
|
63
|
+
elsif scanner.check(Patterns.text_content)
|
64
|
+
stack.push(:default_text)
|
65
|
+
else
|
66
|
+
raise SyntaxError, self
|
67
|
+
end
|
68
|
+
when :tag
|
69
|
+
if scanner.scan(Patterns.open_tag_def)
|
70
|
+
open_tag_def
|
71
|
+
elsif scanner.scan(Patterns.open_tag_end)
|
72
|
+
tokens << [:OPEN_TAG_END]
|
73
|
+
stack.push(:tag_end)
|
74
|
+
elsif scanner.scan(Patterns.open_expression)
|
75
|
+
open_expression
|
76
|
+
elsif scanner.scan(Patterns.comment)
|
77
|
+
tokens << [:SILENT_NEWLINE]
|
78
|
+
elsif scanner.check(Patterns.text_content)
|
79
|
+
stack.push(:default_text)
|
80
|
+
else
|
81
|
+
raise SyntaxError, self
|
82
|
+
end
|
83
|
+
when :default_text
|
84
|
+
if scanner.scan(Patterns.text_content)
|
85
|
+
self.curr_default_text += scanner.matched
|
86
|
+
if scanner.matched.end_with?('\\') && scanner.peek(1) == "{"
|
87
|
+
self.curr_default_text += scanner.getch
|
88
|
+
elsif scanner.matched.end_with?('\\') && scanner.peek(1) == "#"
|
89
|
+
self.curr_default_text += scanner.getch
|
90
|
+
else
|
91
|
+
if scanner.peek(1) == "#"
|
92
|
+
# If the next token is a comment, trim trailing whitespace from
|
93
|
+
# the text value so we don't add to the indentation of the next
|
94
|
+
# value that is output after the comment
|
95
|
+
self.curr_default_text = curr_default_text.gsub(/^\p{Blank}*\z/, "")
|
96
|
+
end
|
97
|
+
tokens << [:TEXT, curr_default_text]
|
98
|
+
self.curr_default_text = ""
|
99
|
+
stack.pop
|
100
|
+
end
|
101
|
+
else
|
102
|
+
raise SyntaxError, self
|
103
|
+
end
|
104
|
+
when :expression
|
105
|
+
if scanner.scan(Patterns.close_expression)
|
106
|
+
tokens << [:EXPRESSION_BODY, curr_expr]
|
107
|
+
tokens << [:CLOSE_EXPRESSION]
|
108
|
+
self.curr_expr = ""
|
109
|
+
stack.pop
|
110
|
+
elsif scanner.scan(Patterns.open_expression)
|
111
|
+
expression_inner_bracket
|
112
|
+
elsif scanner.scan(Patterns.double_quote)
|
113
|
+
expression_inner_double_quote
|
114
|
+
elsif scanner.scan(Patterns.single_quote)
|
115
|
+
expression_inner_single_quote
|
116
|
+
elsif scanner.scan(Patterns.open_tag_def)
|
117
|
+
potential_expression_inner_tag
|
118
|
+
elsif scanner.scan(Patterns.expression_content)
|
119
|
+
self.curr_expr += scanner.matched
|
120
|
+
else
|
121
|
+
raise SyntaxError, self
|
122
|
+
end
|
123
|
+
when :expression_inner_bracket
|
124
|
+
if scanner.scan(Patterns.close_expression)
|
125
|
+
self.curr_expr += scanner.matched
|
126
|
+
stack.pop
|
127
|
+
elsif scanner.scan(Patterns.open_expression)
|
128
|
+
expression_inner_bracket
|
129
|
+
elsif scanner.scan(Patterns.double_quote)
|
130
|
+
expression_inner_double_quote
|
131
|
+
elsif scanner.scan(Patterns.single_quote)
|
132
|
+
expression_inner_single_quote
|
133
|
+
elsif scanner.scan(Patterns.open_tag_def)
|
134
|
+
potential_expression_inner_tag
|
135
|
+
elsif scanner.scan(Patterns.expression_content)
|
136
|
+
self.curr_expr += scanner.matched
|
137
|
+
else
|
138
|
+
raise SyntaxError, self
|
139
|
+
end
|
140
|
+
when :expression_inner_double_quote
|
141
|
+
if scanner.check(Patterns.double_quote)
|
142
|
+
expression_quoted_string_content
|
143
|
+
elsif scanner.scan(Patterns.double_quoted_text_content)
|
144
|
+
self.curr_expr += scanner.matched
|
145
|
+
else
|
146
|
+
raise SyntaxError, self
|
147
|
+
end
|
148
|
+
when :expression_inner_single_quote
|
149
|
+
if scanner.check(Patterns.single_quote)
|
150
|
+
expression_quoted_string_content
|
151
|
+
elsif scanner.scan(Patterns.single_quoted_text_content)
|
152
|
+
self.curr_expr += scanner.matched
|
153
|
+
else
|
154
|
+
raise SyntaxError, self
|
155
|
+
end
|
156
|
+
when :tag_def
|
157
|
+
if scanner.scan(Patterns.close_self_closing_tag)
|
158
|
+
tokens << [:CLOSE_TAG_DEF]
|
159
|
+
tokens << [:OPEN_TAG_END]
|
160
|
+
tokens << [:CLOSE_TAG_END]
|
161
|
+
stack.pop(2)
|
162
|
+
elsif scanner.scan(Patterns.close_tag)
|
163
|
+
tokens << [:CLOSE_TAG_DEF]
|
164
|
+
stack.pop
|
165
|
+
elsif scanner.scan(Patterns.tag_name)
|
166
|
+
tokens << [:TAG_NAME, scanner.matched]
|
167
|
+
elsif scanner.scan(Patterns.whitespace)
|
168
|
+
scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
|
169
|
+
tokens << [:OPEN_ATTRS]
|
170
|
+
stack.push(:tag_attrs)
|
171
|
+
else
|
172
|
+
raise SyntaxError, self
|
173
|
+
end
|
174
|
+
when :tag_end
|
175
|
+
if scanner.scan(Patterns.close_tag)
|
176
|
+
tokens << [:CLOSE_TAG_END]
|
177
|
+
stack.pop(2)
|
178
|
+
elsif scanner.scan(Patterns.tag_name)
|
179
|
+
tokens << [:TAG_NAME, scanner.matched]
|
180
|
+
else
|
181
|
+
raise SyntaxError, self
|
182
|
+
end
|
183
|
+
when :tag_attrs
|
184
|
+
if scanner.scan(Patterns.whitespace)
|
185
|
+
scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
|
186
|
+
elsif scanner.check(Patterns.close_tag)
|
187
|
+
tokens << [:CLOSE_ATTRS]
|
188
|
+
stack.pop
|
189
|
+
elsif scanner.scan(Patterns.attr_assignment)
|
190
|
+
tokens << [:OPEN_ATTR_VALUE]
|
191
|
+
stack.push(:tag_attr_value)
|
192
|
+
elsif scanner.scan(Patterns.attr)
|
193
|
+
tokens << [:ATTR_NAME, scanner.matched.strip]
|
194
|
+
elsif scanner.scan(Patterns.open_attr_splat)
|
195
|
+
tokens << [:OPEN_ATTR_SPLAT]
|
196
|
+
tokens << [:OPEN_EXPRESSION]
|
197
|
+
stack.push(:tag_attr_splat, :expression)
|
198
|
+
else
|
199
|
+
raise SyntaxError, self
|
200
|
+
end
|
201
|
+
when :tag_attr_value
|
202
|
+
if scanner.scan(Patterns.double_quote)
|
203
|
+
stack.push(:quoted_text)
|
204
|
+
elsif scanner.scan(Patterns.open_expression)
|
205
|
+
open_expression
|
206
|
+
elsif scanner.scan(Patterns.whitespace) || scanner.check(Patterns.close_tag)
|
207
|
+
tokens << [:CLOSE_ATTR_VALUE]
|
208
|
+
scanner.matched.count("\n").times { tokens << [:SILENT_NEWLINE] }
|
209
|
+
stack.pop
|
210
|
+
else
|
211
|
+
raise SyntaxError, self
|
212
|
+
end
|
213
|
+
when :tag_attr_splat
|
214
|
+
# Splat is consumed by :expression. It pops control back to here once
|
215
|
+
# it's done, and we just record the completion and pop back to :tag_attrs
|
216
|
+
tokens << [:CLOSE_ATTR_SPLAT]
|
217
|
+
stack.pop
|
218
|
+
when :quoted_text
|
219
|
+
if scanner.scan(Patterns.double_quoted_text_content)
|
220
|
+
self.curr_quoted_text += scanner.matched
|
221
|
+
if scanner.matched.end_with?('\\') && scanner.peek(1) == "\""
|
222
|
+
self.curr_quoted_text += scanner.getch
|
223
|
+
end
|
224
|
+
elsif scanner.scan(Patterns.double_quote)
|
225
|
+
tokens << [:TEXT, curr_quoted_text]
|
226
|
+
self.curr_quoted_text = ""
|
227
|
+
stack.pop
|
228
|
+
else
|
229
|
+
raise SyntaxError, self
|
230
|
+
end
|
231
|
+
else
|
232
|
+
raise SyntaxError, self
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
tokens
|
237
|
+
end
|
238
|
+
|
239
|
+
def potential_expression_inner_tag
|
240
|
+
if self.curr_expr =~ Patterns.expression_internal_tag_prefixes
|
241
|
+
tokens << [:EXPRESSION_BODY, curr_expr]
|
242
|
+
self.curr_expr = ""
|
243
|
+
open_tag_def
|
244
|
+
else
|
245
|
+
self.curr_expr += scanner.matched
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def open_tag_def
|
250
|
+
tokens << [:OPEN_TAG_DEF]
|
251
|
+
stack.push(:tag, :tag_def)
|
252
|
+
end
|
253
|
+
|
254
|
+
def open_expression
|
255
|
+
tokens << [:OPEN_EXPRESSION]
|
256
|
+
stack.push(:expression)
|
257
|
+
end
|
258
|
+
|
259
|
+
def expression_inner_bracket
|
260
|
+
self.curr_expr += scanner.matched
|
261
|
+
stack.push(:expression_inner_bracket)
|
262
|
+
end
|
263
|
+
|
264
|
+
def expression_inner_double_quote
|
265
|
+
self.curr_expr += scanner.matched
|
266
|
+
stack.push(:expression_inner_double_quote)
|
267
|
+
end
|
268
|
+
|
269
|
+
def expression_inner_single_quote
|
270
|
+
self.curr_expr += scanner.matched
|
271
|
+
stack.push(:expression_inner_single_quote)
|
272
|
+
end
|
273
|
+
|
274
|
+
def expression_quoted_string_content
|
275
|
+
self.curr_expr += scanner.getch
|
276
|
+
stack.pop unless curr_expr.end_with?('\\')
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|