ruby2js 3.6.1 → 4.0.0
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 +4 -4
- data/README.md +1 -7
- data/lib/ruby2js.rb +32 -9
- data/lib/ruby2js/converter.rb +8 -2
- data/lib/ruby2js/converter/assign.rb +159 -0
- data/lib/ruby2js/converter/begin.rb +7 -2
- data/lib/ruby2js/converter/case.rb +7 -2
- data/lib/ruby2js/converter/class.rb +77 -21
- data/lib/ruby2js/converter/class2.rb +39 -11
- data/lib/ruby2js/converter/def.rb +6 -2
- data/lib/ruby2js/converter/dstr.rb +8 -3
- data/lib/ruby2js/converter/hash.rb +9 -5
- data/lib/ruby2js/converter/hide.rb +13 -0
- data/lib/ruby2js/converter/if.rb +10 -2
- data/lib/ruby2js/converter/import.rb +18 -3
- data/lib/ruby2js/converter/kwbegin.rb +9 -2
- data/lib/ruby2js/converter/literal.rb +2 -2
- data/lib/ruby2js/converter/module.rb +37 -5
- data/lib/ruby2js/converter/opasgn.rb +8 -0
- data/lib/ruby2js/converter/send.rb +41 -2
- data/lib/ruby2js/converter/vasgn.rb +5 -0
- data/lib/ruby2js/demo.rb +53 -0
- data/lib/ruby2js/es2022.rb +5 -0
- data/lib/ruby2js/es2022/strict.rb +3 -0
- data/lib/ruby2js/filter.rb +9 -1
- data/lib/ruby2js/filter/active_functions.rb +1 -0
- data/lib/ruby2js/filter/cjs.rb +2 -0
- data/lib/ruby2js/filter/esm.rb +44 -10
- data/lib/ruby2js/filter/functions.rb +84 -95
- data/lib/ruby2js/filter/{wunderbar.rb → jsx.rb} +29 -7
- data/lib/ruby2js/filter/react.rb +191 -56
- data/lib/ruby2js/filter/require.rb +100 -5
- data/lib/ruby2js/filter/return.rb +13 -1
- data/lib/ruby2js/filter/stimulus.rb +185 -0
- data/lib/ruby2js/jsx.rb +291 -0
- data/lib/ruby2js/namespace.rb +75 -0
- data/lib/ruby2js/version.rb +3 -3
- metadata +12 -4
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ruby2js'
|
2
|
+
require 'pathname'
|
2
3
|
|
3
4
|
module Ruby2JS
|
4
5
|
module Filter
|
@@ -13,9 +14,17 @@ module Ruby2JS
|
|
13
14
|
|
14
15
|
def initialize(*args)
|
15
16
|
@require_expr = nil
|
17
|
+
@require_seen = {}
|
18
|
+
@require_relative = '.'
|
16
19
|
super
|
17
20
|
end
|
18
21
|
|
22
|
+
def options=(options)
|
23
|
+
super
|
24
|
+
@require_autoexports = !@disable_autoexports && options[:autoexports]
|
25
|
+
@require_recursive = options[:require_recursive]
|
26
|
+
end
|
27
|
+
|
19
28
|
def on_send(node)
|
20
29
|
if \
|
21
30
|
not @require_expr and # only statements
|
@@ -44,11 +53,97 @@ module Ruby2JS
|
|
44
53
|
filename += '.js.rb'
|
45
54
|
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
realpath = File.realpath(filename)
|
57
|
+
if @require_seen[realpath]
|
58
|
+
ast = s(:hide)
|
59
|
+
else
|
60
|
+
@require_seen[realpath] = []
|
61
|
+
|
62
|
+
@options[:file2] = filename
|
63
|
+
ast, comments = Ruby2JS.parse(File.read(filename), filename)
|
64
|
+
@comments.merge! Parser::Source::Comment.associate(ast, comments)
|
65
|
+
@comments[node] += @comments[ast]
|
66
|
+
end
|
67
|
+
|
68
|
+
children = ast.type == :begin ? ast.children : [ast]
|
69
|
+
|
70
|
+
named_exports = []
|
71
|
+
auto_exports = []
|
72
|
+
default_exports = []
|
73
|
+
children.each do |child|
|
74
|
+
if child&.type == :send and child.children[0..1] == [nil, :export]
|
75
|
+
child = child.children[2]
|
76
|
+
if child&.type == :send and child.children[0..1] == [nil, :default]
|
77
|
+
child = child.children[2]
|
78
|
+
target = default_exports
|
79
|
+
else
|
80
|
+
target = named_exports
|
81
|
+
end
|
82
|
+
elsif @require_autoexports
|
83
|
+
target = auto_exports
|
84
|
+
else
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
if %i[class module].include? child.type and child.children[0].children[0] == nil
|
89
|
+
target << child.children[0].children[1]
|
90
|
+
elsif child.type == :casgn and child.children[0] == nil
|
91
|
+
target << child.children[1]
|
92
|
+
elsif child.type == :def
|
93
|
+
target << child.children[0]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if @require_autoexports == :default and auto_exports.length == 1
|
98
|
+
default_exports += auto_exports
|
99
|
+
else
|
100
|
+
named_exports += auto_exports
|
101
|
+
end
|
102
|
+
|
103
|
+
imports = @require_seen[realpath]
|
104
|
+
imports << s(:const, nil, default_exports.first) unless default_exports.empty?
|
105
|
+
imports << named_exports.map {|id| s(:const, nil, id)} unless named_exports.empty?
|
106
|
+
|
107
|
+
if imports.empty?
|
108
|
+
process ast
|
109
|
+
else
|
110
|
+
@require_seen[realpath] = imports
|
111
|
+
|
112
|
+
importname = Pathname.new(filename).relative_path_from(Pathname.new(dirname)).to_s
|
113
|
+
importname = Pathname.new(@require_relative).join(importname).to_s
|
114
|
+
|
115
|
+
prepend_list << s(:import, importname, *imports)
|
116
|
+
|
117
|
+
save_prepend_list = prepend_list.dup
|
118
|
+
|
119
|
+
begin
|
120
|
+
require_relative = @require_relative
|
121
|
+
@require_relative = Pathname.new(@require_relative).join(basename).parent.to_s
|
122
|
+
node = process s(:hide, ast)
|
123
|
+
ensure
|
124
|
+
@require_relative = require_relative
|
125
|
+
end
|
126
|
+
|
127
|
+
if @require_recursive
|
128
|
+
block = node.children
|
129
|
+
while block.length == 1 and block.first.type == :begin
|
130
|
+
block = block.first.children
|
131
|
+
end
|
132
|
+
|
133
|
+
block.each do |child|
|
134
|
+
if child&.type == :import
|
135
|
+
puts ['rr', basename, child.inspect]
|
136
|
+
prepend_list << child
|
137
|
+
end
|
138
|
+
end
|
139
|
+
else
|
140
|
+
prepend_list.keep_if do |import|
|
141
|
+
save_prepend_list.include? import
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
node
|
146
|
+
end
|
52
147
|
ensure
|
53
148
|
if file2
|
54
149
|
@options[:file2] = file2
|
@@ -20,7 +20,7 @@ module Ruby2JS
|
|
20
20
|
|
21
21
|
def on_def(node)
|
22
22
|
node = super
|
23
|
-
return node unless node.type == :def
|
23
|
+
return node unless node.type == :def or node.type == :deff
|
24
24
|
return node if [:constructor, :initialize].include?(node.children.first)
|
25
25
|
|
26
26
|
children = node.children[1..-1]
|
@@ -30,6 +30,18 @@ module Ruby2JS
|
|
30
30
|
node.updated nil, [node.children[0], children.first,
|
31
31
|
s(:autoreturn, *children[1..-1])]
|
32
32
|
end
|
33
|
+
|
34
|
+
def on_deff(node)
|
35
|
+
on_def(node)
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_defs(node)
|
39
|
+
node = super
|
40
|
+
return node unless node.type == :defs
|
41
|
+
children = node.children[3..-1]
|
42
|
+
children[-1] = s(:nil) if children.last == nil
|
43
|
+
node.updated nil, [*node.children[0..2], s(:autoreturn, *children)]
|
44
|
+
end
|
33
45
|
end
|
34
46
|
|
35
47
|
DEFAULTS.push Return
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#
|
2
|
+
require 'ruby2js'
|
3
|
+
|
4
|
+
module Ruby2JS
|
5
|
+
module Filter
|
6
|
+
module Stimulus
|
7
|
+
include SEXP
|
8
|
+
extend SEXP
|
9
|
+
|
10
|
+
STIMULUS_IMPORT = s(:import,
|
11
|
+
[s(:pair, s(:sym, :as), s(:const, nil, :Stimulus)),
|
12
|
+
s(:pair, s(:sym, :from), s(:str, "stimulus"))],
|
13
|
+
s(:str, '*'))
|
14
|
+
|
15
|
+
STIMULUS_IMPORT_SKYPACK = s(:import,
|
16
|
+
[s(:pair, s(:sym, :as), s(:const, nil, :Stimulus)),
|
17
|
+
s(:pair, s(:sym, :from), s(:str, "https://cdn.skypack.dev/stimulus"))],
|
18
|
+
s(:str, '*'))
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
@stim_scope = []
|
23
|
+
@stim_subclasses = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_module(node)
|
27
|
+
save_scope = @stim_scope
|
28
|
+
@stim_scope += @namespace.resolve(node.children.first)
|
29
|
+
super
|
30
|
+
ensure
|
31
|
+
@stim_scope = save_scope
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_class(node)
|
35
|
+
cname, inheritance, *body = node.children
|
36
|
+
return super unless inheritance == s(:const, nil, :Stimulus) or
|
37
|
+
inheritance == s(:const, s(:const, nil, :Stimulus), :Controller) or
|
38
|
+
inheritance == s(:send, s(:const, nil, :Stimulus), :Controller) or
|
39
|
+
@stim_subclasses.include? @namespace.resolve(inheritance)
|
40
|
+
|
41
|
+
if inheritance == s(:const, nil, :Stimulus)
|
42
|
+
node = node.updated(nil, [node.children.first,
|
43
|
+
s(:const, s(:const, nil, :Stimulus), :Controller),
|
44
|
+
*node.children[2..-1]])
|
45
|
+
end
|
46
|
+
|
47
|
+
@stim_subclasses << @stim_scope + @namespace.resolve(cname)
|
48
|
+
|
49
|
+
@stim_targets = Set.new
|
50
|
+
@stim_values = Set.new
|
51
|
+
@stim_classes = Set.new
|
52
|
+
stim_walk(node)
|
53
|
+
|
54
|
+
prepend_list << (@options[:import_from_skypack] ?
|
55
|
+
STIMULUS_IMPORT_SKYPACK : STIMULUS_IMPORT)
|
56
|
+
|
57
|
+
nodes = body
|
58
|
+
if nodes.length == 1 and nodes.first&.type == :begin
|
59
|
+
nodes = nodes.first.children.dup
|
60
|
+
end
|
61
|
+
|
62
|
+
unless @stim_classes.empty?
|
63
|
+
classes = nodes.find_index {|child|
|
64
|
+
child.type == :send and child.children[0..1] == [s(:self), :classes=]
|
65
|
+
}
|
66
|
+
|
67
|
+
if classes == nil
|
68
|
+
nodes.unshift s(:send, s(:self), :classes=, s(:array, *@stim_classes))
|
69
|
+
elsif nodes[classes].children[2].type == :array
|
70
|
+
@stim_classes.merge(nodes[classes].children[2].children)
|
71
|
+
nodes[classes] = nodes[classes].updated(nil,
|
72
|
+
[*nodes[classes].children[0..1], s(:array, *@stim_classes)])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
unless @stim_values.empty?
|
77
|
+
values = nodes.find_index {|child|
|
78
|
+
child.type == :send and child.children[0..1] == [s(:self), :values=]
|
79
|
+
}
|
80
|
+
|
81
|
+
if values == nil
|
82
|
+
nodes.unshift s(:send, s(:self), :values=, s(:hash,
|
83
|
+
*@stim_values.map {|name| s(:pair, name, s(:const, nil, :String))}))
|
84
|
+
elsif nodes[values].children[2].type == :hash
|
85
|
+
stim_values = @stim_values.map {|name|
|
86
|
+
[s(:sym, name.children.first.to_sym), s(:const, nil, :String)]
|
87
|
+
}.to_h.merge(
|
88
|
+
nodes[values].children[2].children.map {|pair| pair.children}.to_h
|
89
|
+
)
|
90
|
+
|
91
|
+
nodes[values] = nodes[values].updated(nil,
|
92
|
+
[*nodes[values].children[0..1], s(:hash,
|
93
|
+
*stim_values.map{|name, value| s(:pair, name, value)})])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
unless @stim_targets.empty?
|
98
|
+
targets = nodes.find_index {|child|
|
99
|
+
child.type == :send and child.children[0..1] == [s(:self), :targets=]
|
100
|
+
}
|
101
|
+
|
102
|
+
if targets == nil
|
103
|
+
nodes.unshift s(:send, s(:self), :targets=, s(:array, *@stim_targets))
|
104
|
+
elsif nodes[targets].children[2].type == :array
|
105
|
+
@stim_targets.merge(nodes[targets].children[2].children)
|
106
|
+
nodes[targets] = nodes[targets].updated(nil,
|
107
|
+
[*nodes[targets].children[0..1], s(:array, *@stim_targets)])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
props = [:element, :application]
|
112
|
+
|
113
|
+
props += @stim_targets.map do |name|
|
114
|
+
name = name.children.first
|
115
|
+
["#{name}Target", "#{name}Targets", "has#{name[0].upcase}#{name[1..-1]}Target"]
|
116
|
+
end
|
117
|
+
|
118
|
+
props += @stim_values.map do |name|
|
119
|
+
name = name.children.first
|
120
|
+
["#{name}Value", "has#{name[0].upcase}#{name[1..-1]}Value"]
|
121
|
+
end
|
122
|
+
|
123
|
+
props += @stim_classes.map do |name|
|
124
|
+
name = name.children.first
|
125
|
+
["#{name}Class", "has#{name[0].upcase}#{name[1..-1]}Class"]
|
126
|
+
end
|
127
|
+
|
128
|
+
props = props.flatten.map {|prop| [prop.to_sym, s(:self)]}.to_h
|
129
|
+
|
130
|
+
props[:initialize] = s(:autobind, s(:self))
|
131
|
+
|
132
|
+
nodes.unshift s(:defineProps, props)
|
133
|
+
|
134
|
+
nodes.pop unless nodes.last
|
135
|
+
|
136
|
+
node.updated(nil, [*node.children[0..1], s(:begin, *process_all(nodes))])
|
137
|
+
end
|
138
|
+
|
139
|
+
# analyze ivar usage
|
140
|
+
def stim_walk(node)
|
141
|
+
node.children.each do |child|
|
142
|
+
next unless Parser::AST::Node === child
|
143
|
+
stim_walk(child)
|
144
|
+
|
145
|
+
if child.type == :send and child.children.length == 2 and
|
146
|
+
[nil, s(:self), s(:send, nil, :this)].include? child.children[0]
|
147
|
+
|
148
|
+
if child.children[1] =~ /^has([A-Z]\w*)(Target|Value|Class)$/
|
149
|
+
name = s(:str, $1[0].downcase + $1[1..-1])
|
150
|
+
@stim_targets.add name if $2 == 'Target'
|
151
|
+
@stim_values.add name if $2 == 'Value'
|
152
|
+
@stim_classes.add name if $2 == 'Class'
|
153
|
+
elsif child.children[1] =~ /^(\w+)Targets?$/
|
154
|
+
@stim_targets.add s(:str, $1)
|
155
|
+
elsif child.children[1] =~ /^(\w+)Value=?$/
|
156
|
+
@stim_values.add s(:str, $1)
|
157
|
+
elsif child.children[1] =~ /^(\w+)Class$/
|
158
|
+
@stim_classes.add s(:str, $1)
|
159
|
+
end
|
160
|
+
|
161
|
+
elsif child.type == :send and child.children.length == 3 and
|
162
|
+
[s(:self), s(:send, nil, :this)].include? child.children[0]
|
163
|
+
|
164
|
+
if child.children[1] =~ /^(\w+)Value=$/
|
165
|
+
@stim_values.add s(:str, $1)
|
166
|
+
end
|
167
|
+
|
168
|
+
elsif child.type == :lvasgn
|
169
|
+
if child.children[0] =~ /^(\w+)Value$/
|
170
|
+
@stim_values.add s(:str, $1)
|
171
|
+
end
|
172
|
+
|
173
|
+
elsif child.type == :def
|
174
|
+
if child.children[0] =~ /^(\w+)ValueChanged$/
|
175
|
+
@stim_values.add s(:str, $1)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
DEFAULTS.push Stimulus
|
184
|
+
end
|
185
|
+
end
|
data/lib/ruby2js/jsx.rb
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
# convert a JSX expression into wunderbar statements
|
2
|
+
#
|
3
|
+
# Once the syntax is converted to pure Ruby statements,
|
4
|
+
# it can then be converted into either React or Vue
|
5
|
+
# rendering instructions.
|
6
|
+
|
7
|
+
module Ruby2JS
|
8
|
+
def self.jsx2_rb(string)
|
9
|
+
JsxParser.new(string.chars.each).parse.join("\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
class JsxParser
|
13
|
+
def initialize(stream)
|
14
|
+
@stream = stream
|
15
|
+
@state = :text
|
16
|
+
@text = ''
|
17
|
+
@result = []
|
18
|
+
@element = ''
|
19
|
+
@attrs = {}
|
20
|
+
@attr_name = ''
|
21
|
+
@value = ''
|
22
|
+
@tag_stack = []
|
23
|
+
@expr_nesting = 0
|
24
|
+
@wrap_value = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse(state = :text, wrap_value = true)
|
28
|
+
@wrap_value = wrap_value
|
29
|
+
@state = state
|
30
|
+
backtrace = ''
|
31
|
+
prev = nil
|
32
|
+
|
33
|
+
loop do
|
34
|
+
c = @stream.next
|
35
|
+
|
36
|
+
if c == "\n"
|
37
|
+
backtrace = ''
|
38
|
+
else
|
39
|
+
backtrace += c
|
40
|
+
end
|
41
|
+
|
42
|
+
case @state
|
43
|
+
when :text
|
44
|
+
if c == '<'
|
45
|
+
@result << "_(\"#{@text.strip}\")" unless @text.strip.empty?
|
46
|
+
if @tag_stack.empty?
|
47
|
+
@result += self.class.new(@stream).parse(:element)
|
48
|
+
@state = :text
|
49
|
+
@text = ''
|
50
|
+
else
|
51
|
+
@state = :element
|
52
|
+
@element = ''
|
53
|
+
@attrs = {}
|
54
|
+
end
|
55
|
+
elsif c == '\\'
|
56
|
+
@text += c + c
|
57
|
+
elsif c == '{'
|
58
|
+
@result << "_(\"#{@text}\")" unless @text.empty?
|
59
|
+
@result += parse_expr
|
60
|
+
@text = ''
|
61
|
+
else
|
62
|
+
@text += c unless @text.empty? and c =~ /\s/
|
63
|
+
end
|
64
|
+
|
65
|
+
when :element
|
66
|
+
if c == '/'
|
67
|
+
if @element == ''
|
68
|
+
@state = :close
|
69
|
+
@element = ''
|
70
|
+
else
|
71
|
+
@state = :void
|
72
|
+
end
|
73
|
+
elsif c == '>'
|
74
|
+
@result << "_#{@element} do"
|
75
|
+
@tag_stack << @element
|
76
|
+
@state = :text
|
77
|
+
@text = ''
|
78
|
+
elsif c == ' '
|
79
|
+
@state = :attr_name
|
80
|
+
@attr_name = ''
|
81
|
+
@attrs = {}
|
82
|
+
elsif c == '-'
|
83
|
+
@element += '_'
|
84
|
+
elsif c =~ /^\w$/
|
85
|
+
@element += c
|
86
|
+
else
|
87
|
+
raise SyntaxError.new("invalid character in element name: #{c.inspect}")
|
88
|
+
end
|
89
|
+
|
90
|
+
when :close
|
91
|
+
if c == '>'
|
92
|
+
if @element == @tag_stack.last
|
93
|
+
@tag_stack.pop
|
94
|
+
elsif @tag_stack.last
|
95
|
+
raise SyntaxError.new("missing close tag for: #{@tag_stack.last.inspect}")
|
96
|
+
else
|
97
|
+
raise SyntaxError.new("close tag for element that is not open: #{@element}")
|
98
|
+
end
|
99
|
+
|
100
|
+
@result << 'end'
|
101
|
+
return @result if @tag_stack.empty?
|
102
|
+
elsif c =~ /^\w$/
|
103
|
+
@element += c
|
104
|
+
elsif c != ' '
|
105
|
+
raise SyntaxError.new("invalid character in element: #{c.inspect}")
|
106
|
+
end
|
107
|
+
|
108
|
+
when :void
|
109
|
+
if c == '>'
|
110
|
+
if @attrs.empty?
|
111
|
+
@result << "_#{@element}"
|
112
|
+
else
|
113
|
+
@result << "_#{@element}(#{@attrs.map {|name, value| "#{name}: #{value}"}.join(' ')})"
|
114
|
+
end
|
115
|
+
return @result if @tag_stack.empty?
|
116
|
+
|
117
|
+
@state = :text
|
118
|
+
@text = ''
|
119
|
+
elsif c != ' '
|
120
|
+
raise SyntaxError.new('invalid character in element: "/"')
|
121
|
+
end
|
122
|
+
|
123
|
+
when :attr_name
|
124
|
+
if c =~ /^\w$/
|
125
|
+
@attr_name += c
|
126
|
+
elsif c == '='
|
127
|
+
@state = :attr_value
|
128
|
+
@value = ''
|
129
|
+
elsif c == '/' and @attr_name == ''
|
130
|
+
@state = :void
|
131
|
+
elsif c == ' ' or c == "\n" or c == '>'
|
132
|
+
if not @attr_name.empty?
|
133
|
+
raise SyntaxError.new("missing \"=\" after attribute #{@attr_name.inspect} " +
|
134
|
+
"in element #{@element.inspect}")
|
135
|
+
elsif c == '>'
|
136
|
+
@result << "_#{@element}(#{@attrs.map {|name, value| "#{name}: #{value}"}.join(' ')}) do"
|
137
|
+
@tag_stack << @element
|
138
|
+
@state = :text
|
139
|
+
@text = ''
|
140
|
+
end
|
141
|
+
else
|
142
|
+
raise SyntaxError.new("invalid character in attribute name: #{c.inspect}")
|
143
|
+
end
|
144
|
+
|
145
|
+
when :attr_value
|
146
|
+
if c == '"'
|
147
|
+
@state = :dquote
|
148
|
+
elsif c == "'"
|
149
|
+
@state = :squote
|
150
|
+
elsif c == '{'
|
151
|
+
@attrs[@attr_name] = parse_value
|
152
|
+
@state = :attr_name
|
153
|
+
@attr_name = ''
|
154
|
+
else
|
155
|
+
raise SyntaxError.new("invalid value for attribute #{@attr_name.inspect} " +
|
156
|
+
"in element #{@element.inspect}")
|
157
|
+
end
|
158
|
+
|
159
|
+
when :dquote
|
160
|
+
if c == '"'
|
161
|
+
@attrs[@attr_name] = '"' + @value + '"'
|
162
|
+
@state = :attr_name
|
163
|
+
@attr_name = ''
|
164
|
+
elsif c == "\\"
|
165
|
+
@value += c + c
|
166
|
+
else
|
167
|
+
@value += c
|
168
|
+
end
|
169
|
+
|
170
|
+
when :squote
|
171
|
+
if c == "'"
|
172
|
+
@attrs[@attr_name] = "'" + @value + "'"
|
173
|
+
@state = :attr_name
|
174
|
+
@attr_name = ''
|
175
|
+
elsif c == "\\"
|
176
|
+
@value += c + c
|
177
|
+
else
|
178
|
+
@value += c
|
179
|
+
end
|
180
|
+
|
181
|
+
when :expr
|
182
|
+
if c == "}"
|
183
|
+
if @expr_nesting > 0
|
184
|
+
@value += c
|
185
|
+
@expr_nesting -= 1
|
186
|
+
else
|
187
|
+
@result << (@wrap_value ? "_(#{@value})" : @value)
|
188
|
+
return @result
|
189
|
+
end
|
190
|
+
elsif c == '<'
|
191
|
+
if prev =~ /[\w\)\]\}]/
|
192
|
+
@value += c # less than
|
193
|
+
elsif prev == ' '
|
194
|
+
if @stream.peek =~ /[a-zA-Z]/
|
195
|
+
@value += parse_element.join(';')
|
196
|
+
@wrap_value = false
|
197
|
+
else
|
198
|
+
@value += c
|
199
|
+
end
|
200
|
+
else
|
201
|
+
@value += parse_element.join(';')
|
202
|
+
@wrap_value = false
|
203
|
+
end
|
204
|
+
else
|
205
|
+
@value += c
|
206
|
+
@state = :expr_squote if c == "'"
|
207
|
+
@state = :expr_dquote if c == '"'
|
208
|
+
@expr_nesting += 1 if c == '{'
|
209
|
+
end
|
210
|
+
|
211
|
+
when :expr_squote
|
212
|
+
@value += c
|
213
|
+
if c == "\\"
|
214
|
+
@state = :expr_squote_backslash
|
215
|
+
elsif c == "'"
|
216
|
+
@state = :expr
|
217
|
+
end
|
218
|
+
|
219
|
+
when :expr_squote_backslash
|
220
|
+
@value += c
|
221
|
+
@state = :expr_squote
|
222
|
+
|
223
|
+
when :expr_dquote
|
224
|
+
@value += c
|
225
|
+
if c == "\\"
|
226
|
+
@state = :expr_dquote_backslash
|
227
|
+
elsif c == '#'
|
228
|
+
@state = :expr_dquote_hash
|
229
|
+
elsif c == '"'
|
230
|
+
@state = :expr
|
231
|
+
end
|
232
|
+
|
233
|
+
when :expr_dquote_backslash
|
234
|
+
@value += c
|
235
|
+
@state = :expr_dquote
|
236
|
+
|
237
|
+
when :expr_dquote_hash
|
238
|
+
@value += c
|
239
|
+
@value += parse_value + '}' if c == '{'
|
240
|
+
@state = :expr_dquote
|
241
|
+
|
242
|
+
else
|
243
|
+
raise RangeError.new("internal state error in JSX: #{@state.inspect}")
|
244
|
+
end
|
245
|
+
|
246
|
+
prev = c
|
247
|
+
end
|
248
|
+
|
249
|
+
unless @tag_stack.empty?
|
250
|
+
raise SyntaxError.new("missing close tag for: #{@tag_stack.last.inspect}")
|
251
|
+
end
|
252
|
+
|
253
|
+
case @state
|
254
|
+
when :text
|
255
|
+
@result << "_(\"#{@text.strip}\")" unless @text.strip.empty?
|
256
|
+
|
257
|
+
when :element, :attr_name, :attr_value
|
258
|
+
raise SyntaxError.new("unclosed element #{@element.inspect}")
|
259
|
+
|
260
|
+
when :dquote, :squote, :expr_dquote, :expr_dquote_backslash,
|
261
|
+
:expr_squote, :expr_squote_backslash
|
262
|
+
raise SyntaxError.new("unclosed quote")
|
263
|
+
|
264
|
+
when :expr
|
265
|
+
raise SyntaxError.new("unclosed value")
|
266
|
+
|
267
|
+
else
|
268
|
+
raise RangeError.new("internal state error in JSX: #{@state.inspect}")
|
269
|
+
end
|
270
|
+
|
271
|
+
@result
|
272
|
+
rescue SyntaxError => e
|
273
|
+
e.set_backtrace backtrace
|
274
|
+
raise e
|
275
|
+
end
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
def parse_value
|
280
|
+
self.class.new(@stream).parse(:expr, false).join(',')
|
281
|
+
end
|
282
|
+
|
283
|
+
def parse_expr
|
284
|
+
self.class.new(@stream).parse(:expr, true)
|
285
|
+
end
|
286
|
+
|
287
|
+
def parse_element
|
288
|
+
self.class.new(@stream).parse(:element)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|