goling 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/goling.gemspec +3 -2
- data/lib/goling.rb +7 -1
- data/lib/goling/linguified.rb +32 -16
- data/lib/goling/reduction.rb +147 -93
- data/lib/goling/translators/javascript.rb +503 -0
- data/test/test_goling.rb +123 -0
- metadata +4 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/goling.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{goling}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = [%q{Patrick Hanevold}]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-10-07}
|
13
13
|
s.description = %q{Goling is a linguistic compiler allowing you to compile and execute plain english.}
|
14
14
|
s.email = %q{patrick.hanevold@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
"lib/goling.rb",
|
27
27
|
"lib/goling/linguified.rb",
|
28
28
|
"lib/goling/reduction.rb",
|
29
|
+
"lib/goling/translators/javascript.rb",
|
29
30
|
"test/helper.rb",
|
30
31
|
"test/test_goling.rb"
|
31
32
|
]
|
data/lib/goling.rb
CHANGED
@@ -4,7 +4,13 @@ require 'goling/linguified'
|
|
4
4
|
require 'goling/reduction'
|
5
5
|
|
6
6
|
def reduce(regexp,&code)
|
7
|
-
Goling::rules << {
|
7
|
+
Goling::rules << {
|
8
|
+
:match => regexp.keys[0],
|
9
|
+
:result => regexp.values[0].kind_of?(Hash) ? regexp.values[0][:to] : regexp.values[0],
|
10
|
+
:lang => regexp.values[0].kind_of?(Hash) ? regexp.values[0][:lang] : :ruby,
|
11
|
+
:inline => regexp.values[0].kind_of?(Hash)&®exp.values[0].has_key?(:inline) ? regexp.values[0][:inline] : false,
|
12
|
+
:proc => code
|
13
|
+
}
|
8
14
|
end
|
9
15
|
|
10
16
|
module Goling
|
data/lib/goling/linguified.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'sourcify'
|
4
|
+
require 'goling/translators/javascript'
|
4
5
|
|
5
6
|
module Goling
|
6
7
|
|
@@ -10,26 +11,22 @@ module Goling
|
|
10
11
|
|
11
12
|
def initialize str,bind
|
12
13
|
|
14
|
+
#
|
15
|
+
# reduction loop
|
16
|
+
#
|
13
17
|
@sentence = str.dup
|
14
18
|
@bind = bind
|
15
19
|
loop do
|
16
|
-
|
17
|
-
|
18
|
-
true
|
19
|
-
else
|
20
|
-
false
|
21
|
-
end
|
22
|
-
end
|
23
|
-
raise "no step definition for #{str}" if found.size == 0
|
24
|
-
|
25
|
-
rule = found[0]
|
26
|
-
match = rule[:match].match(str)
|
20
|
+
rule = find_rule(str)
|
21
|
+
|
27
22
|
reduced = Reduction.new(
|
28
23
|
:returns => rule[:result],
|
24
|
+
:lang => rule[:lang],
|
25
|
+
:inline => rule[:inline],
|
29
26
|
:location => rule[:proc].source_location[0],
|
30
27
|
:line => rule[:proc].source_location[1],
|
31
28
|
:regexp => rule[:match].inspect,
|
32
|
-
:args => match.to_a[1..-1],
|
29
|
+
:args => rule[:match].match(str).to_a[1..-1],
|
33
30
|
:sexp => rule[:proc].to_sexp,
|
34
31
|
)
|
35
32
|
str.gsub!(rule[:match],reduced.to_rexp)
|
@@ -40,8 +37,10 @@ module Goling
|
|
40
37
|
|
41
38
|
@merged_code = []
|
42
39
|
if /^{(?<code>.*)}$/ =~ @encoded
|
40
|
+
# successfully reduced entire string, compile it
|
43
41
|
code = Reduction::parse(code).compile_with_return_to_var(nil)
|
44
42
|
|
43
|
+
# and wrap it up
|
45
44
|
@sexy = Sexp.new(:block,
|
46
45
|
Sexp.new(:lasgn,:code, Sexp.new(:iter,
|
47
46
|
Sexp.new(:call, nil, :lambda, Sexp.new(:arglist)), nil,
|
@@ -50,23 +49,40 @@ module Goling
|
|
50
49
|
)
|
51
50
|
)
|
52
51
|
),
|
53
|
-
Sexp.new(:call, Sexp.new(:colon2, Sexp.new(:const, :Goling), :Linguified), :trampoline, Sexp.new(:arglist, Sexp.new(:lvar, :code)))
|
54
52
|
)
|
55
53
|
|
56
54
|
@@me = self
|
57
|
-
eval to_ruby
|
55
|
+
eval to_ruby(
|
56
|
+
Sexp.new(:call,
|
57
|
+
Sexp.new(:colon2, Sexp.new(:const, :Goling), :Linguified), :trampoline, Sexp.new(:arglist, Sexp.new(:lvar, :code))
|
58
|
+
)
|
59
|
+
),bind
|
58
60
|
raise "hell" unless @proc
|
59
61
|
else
|
60
62
|
raise "hell"
|
61
63
|
end
|
62
64
|
end
|
63
|
-
|
65
|
+
|
66
|
+
def find_rule str
|
67
|
+
found = Goling.rules.select do |rule|
|
68
|
+
if rule[:match] =~ str
|
69
|
+
true
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
raise "no step definition for #{str}" if found.size == 0
|
75
|
+
|
76
|
+
found[0]
|
77
|
+
end
|
78
|
+
|
64
79
|
def to_sexp
|
65
80
|
@sexy
|
66
81
|
end
|
67
82
|
|
68
|
-
def to_ruby
|
83
|
+
def to_ruby additional=nil
|
69
84
|
clone = Marshal.load(Marshal.dump(@sexy)) # sexy is not cleanly duplicated
|
85
|
+
clone << additional if additional
|
70
86
|
Ruby2Ruby.new.process(clone)
|
71
87
|
end
|
72
88
|
|
data/lib/goling/reduction.rb
CHANGED
@@ -1,15 +1,30 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Goling
|
4
|
+
|
5
|
+
class Replacement
|
6
|
+
attr_reader :sexp
|
7
|
+
|
8
|
+
def initialize args
|
9
|
+
@sexp = args[:sexp]
|
10
|
+
@inline = args[:inline] || false
|
11
|
+
end
|
12
|
+
|
13
|
+
def inline?
|
14
|
+
@inline
|
15
|
+
end
|
16
|
+
end
|
4
17
|
|
5
18
|
class Reduction
|
6
19
|
|
7
|
-
attr_accessor :returns, :location, :line, :regexp, :args, :sexp, :rule_args, :from, :reduction_id
|
20
|
+
attr_accessor :returns, :location, :line, :regexp, :args, :sexp, :rule_args, :from, :reduction_id, :lang, :inline, :named_args
|
8
21
|
|
9
22
|
@@reductions = []
|
10
23
|
|
11
24
|
def initialize params
|
12
25
|
@returns = params[:returns]
|
26
|
+
@lang = params[:lang]
|
27
|
+
@inline = params[:inline]
|
13
28
|
@location = params[:location]
|
14
29
|
@line = params[:line]
|
15
30
|
@regexp = params[:regexp]
|
@@ -17,133 +32,172 @@ module Goling
|
|
17
32
|
@args = params[:args]
|
18
33
|
@sexp = params[:sexp]
|
19
34
|
@reduction_id = @@reductions.size
|
35
|
+
determine_arguments
|
20
36
|
@@reductions << self
|
21
37
|
end
|
22
38
|
|
39
|
+
def determine_arguments
|
40
|
+
s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
|
41
|
+
raise "what is this?" unless s.sexp[0] == :iter && s.sexp[1][0] == :call && s.sexp[1][1] == nil && s.sexp[1][2] == :proc && s.sexp[1][3][0] == :arglist
|
42
|
+
|
43
|
+
block_args = s.sexp[2]
|
44
|
+
if block_args
|
45
|
+
if block_args[0]==:lasgn
|
46
|
+
# single argument
|
47
|
+
args = [block_args[1]]
|
48
|
+
elsif block_args[0]==:masgn
|
49
|
+
# multiple arguments
|
50
|
+
args = block_args[1]
|
51
|
+
raise "unsupported argument type #{args}" unless args[0]==:array
|
52
|
+
args = args[1..-1].map{ |arg|
|
53
|
+
raise "unsupported argument type #{arg}" unless arg[0]==:lasgn
|
54
|
+
arg[1]
|
55
|
+
}
|
56
|
+
else
|
57
|
+
raise "unsupported argument type #{args}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# args[] now has the symbolized argument names of the code block
|
61
|
+
@named_args = Hash[*args.zip(@args).flatten] if args
|
62
|
+
@named_args ||= {}
|
63
|
+
end
|
64
|
+
|
23
65
|
def self.parse str
|
24
66
|
if /^{(?<return>[^:]*):(?<rid>[0-9]+)}$/ =~ str
|
25
67
|
@@reductions[rid.to_i]
|
26
68
|
elsif /^(?<return>[^:]*):(?<rid>[0-9]+)$/ =~ str
|
27
69
|
@@reductions[rid.to_i]
|
28
70
|
else
|
29
|
-
|
30
|
-
raise "hell"
|
71
|
+
raise "hell #{str}"
|
31
72
|
end
|
32
73
|
end
|
33
74
|
|
75
|
+
# Compile self
|
76
|
+
#
|
77
|
+
# * +return_variable+ - The return variable. Can either be a symbol representing the variable name or nil to skip variable assigning.
|
78
|
+
# * +replace+ - A list of variables in need of a new unique name or replacement with inlined code
|
79
|
+
#
|
34
80
|
def compile_with_return_to_var(return_variable, replace = {})
|
35
81
|
s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
|
36
|
-
|
82
|
+
args = @named_args.keys
|
83
|
+
# args[] now has the symbolized argument names of the code block
|
37
84
|
|
38
|
-
|
39
|
-
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
52
|
-
else
|
53
|
-
raise "unsupported argument type #{args}"
|
54
|
-
end
|
55
|
-
unless s.rule_args.size == args.size
|
56
|
-
raise "number of arguments in reduction rule (#{s.rule_args}) do not match the number of block arguments (#{args.size}) at #{s.location}:#{s.line}"
|
57
|
-
end
|
58
|
-
# args[] now has the symbolized argument names of the code block
|
59
|
-
|
60
|
-
args_code = []
|
61
|
-
s.args.each_with_index do |arg,i|
|
62
|
-
if /^{(?<ret>[^:]*):(?<n>[0-9]+)}$/ =~ arg
|
63
|
-
args_code += Reduction::parse(arg).compile_with_return_to_var("#{ret}_#{n}".to_sym,replace)
|
64
|
-
elsif /^[0-9]+$/ =~ arg
|
65
|
-
args_code << Sexp.new(:lasgn, args[i], Sexp.new(:lit, arg.to_i))
|
66
|
-
else
|
67
|
-
raise "hell"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
s.args.each_with_index do |arg,i|
|
71
|
-
if /^{(?<ret>[^:]*):(?<n>[0-9]+)}$/ =~ arg
|
72
|
-
replace[args[i]] = "#{ret}_#{n}".to_sym
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
if s.sexp[3][0] == :block
|
77
|
-
if return_variable
|
78
|
-
code = Sexp.new(:lasgn, return_variable, Sexp.new(:block,
|
79
|
-
*(s.sexp[3][1..-1].map{ |s| s.dup })
|
80
|
-
)
|
85
|
+
args_code = []
|
86
|
+
s.args.each_with_index do |arg,i|
|
87
|
+
if /^{(?<ret>[^:]*):(?<n>[0-9]+)}$/ =~ arg
|
88
|
+
# got a argument that referes to a reduction
|
89
|
+
# pseudo allocate a return variable name and compile the reduction
|
90
|
+
red = Reduction::parse(arg)
|
91
|
+
if red.lang != lang && red.lang == :js && lang == :ruby
|
92
|
+
# paste javascript code into a ruby variable
|
93
|
+
code = red.compile_with_return_to_var(nil,replace)
|
94
|
+
clone = Marshal.load(Marshal.dump(code)) # code is not cleanly duplicated
|
95
|
+
code = Sexp.new(:iter,Sexp.new(:call, nil, :lambda, Sexp.new(:arglist)), nil,
|
96
|
+
Sexp.new(:block,
|
97
|
+
*clone
|
81
98
|
)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
replace_variable_references(code,v,k)
|
87
|
-
end
|
88
|
-
return *args_code + [code]
|
99
|
+
)
|
100
|
+
code = Ruby2Js.new.process(code)
|
101
|
+
code = [Sexp.new(:lasgn, args[i], Sexp.new(:lit, code))]
|
102
|
+
args_code += code
|
89
103
|
else
|
90
|
-
if
|
91
|
-
|
104
|
+
raise "trying to reference #{red.lang} code in #{lang} code" if red.lang != lang
|
105
|
+
if red.inline
|
106
|
+
code = red.compile_with_return_to_var(nil,replace)
|
107
|
+
replace[args[i]] = Replacement.new(:sexp => Sexp.new(:block,*code), :inline => true, :rule => rule)
|
92
108
|
else
|
93
|
-
code =
|
94
|
-
|
95
|
-
|
96
|
-
replace_variable_references(code,v,k)
|
109
|
+
code = red.compile_with_return_to_var("#{ret}_#{n}".to_sym,replace)
|
110
|
+
args_code += code
|
111
|
+
replace[args[i]] = Replacement.new(:sexp => "#{ret}_#{n}".to_sym)
|
97
112
|
end
|
98
|
-
return *args_code + [code]
|
99
113
|
end
|
114
|
+
elsif /^[0-9]+$/ =~ arg
|
115
|
+
# got a number argument, stuff it in a integer variable
|
116
|
+
args_code << Sexp.new(:lasgn, args[i], Sexp.new(:lit, arg.to_i))
|
100
117
|
else
|
101
|
-
raise "
|
102
|
-
# code block without arguments
|
103
|
-
if s.sexp[3][0] == :block
|
104
|
-
if return_variable
|
105
|
-
code = Sexp.new(:lasgn, return_variable, Sexp.new(:block,
|
106
|
-
*(s.sexp[3][1..-1].map{ |s| s.dup })
|
107
|
-
)
|
108
|
-
)
|
109
|
-
else
|
110
|
-
code = s.sexp[3].dup
|
111
|
-
end
|
112
|
-
replace.each do |k,v|
|
113
|
-
replace_variable_references(code,v,k)
|
114
|
-
end
|
115
|
-
[code]
|
116
|
-
else
|
117
|
-
if return_variable
|
118
|
-
code = Sexp.new(:lasgn, return_variable, s.sexp[3].dup)
|
119
|
-
else
|
120
|
-
code = s.sexp[3].dup
|
121
|
-
end
|
122
|
-
replace.each do |k,v|
|
123
|
-
replace_variable_references(code,v,k)
|
124
|
-
end
|
125
|
-
[code]
|
126
|
-
end
|
118
|
+
raise "hell"
|
127
119
|
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if return_variable
|
123
|
+
if s.sexp[3][0] == :block
|
124
|
+
code = Sexp.new(:lasgn, return_variable,
|
125
|
+
Sexp.new(:block,
|
126
|
+
*(s.sexp[3][1..-1].map{ |s| s.dup })
|
127
|
+
)
|
128
|
+
)
|
129
|
+
else
|
130
|
+
code = Sexp.new(:lasgn, return_variable, s.sexp[3].dup)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
code = s.sexp[3].dup
|
134
|
+
end
|
135
|
+
|
136
|
+
replace.each do |k,v|
|
137
|
+
replace_variable_references(code,v,k)
|
138
|
+
end
|
139
|
+
|
140
|
+
return *args_code + [code]
|
128
141
|
end
|
129
142
|
|
143
|
+
# Recurcively replace all references in a code section
|
144
|
+
#
|
145
|
+
# * +code+ - The code haystack to search and replace in
|
146
|
+
# * +replacement+ - The replacement code. Either a Sexp (containing code to inline) or a symbol
|
147
|
+
# * +needle+ - The search needle
|
148
|
+
#
|
130
149
|
def replace_variable_references(code,replacement,needle)
|
150
|
+
|
151
|
+
#inline = replacement.kind_of?(Sexp)
|
152
|
+
|
131
153
|
case code[0]
|
132
154
|
when :lasgn
|
133
|
-
code[1]=replacement if code[1] == needle
|
155
|
+
code[1]=replacement.sexp if code[1] == needle
|
134
156
|
when :lvar
|
135
|
-
|
157
|
+
if code[1] == needle
|
158
|
+
unless replacement.inline?
|
159
|
+
code[1]=replacement.sexp
|
160
|
+
end
|
161
|
+
end
|
136
162
|
when :call
|
137
|
-
code[2]=replacement if code[2] == needle
|
163
|
+
code[2]=replacement.sexp if code[2] == needle
|
138
164
|
when :lvar
|
139
|
-
code[1]=replacement if code[1] == needle
|
165
|
+
code[1]=replacement.sexp if code[1] == needle
|
140
166
|
end
|
141
|
-
|
142
|
-
|
167
|
+
# inlining requires complex code:
|
168
|
+
if replacement.inline? && [:iter, :block].include?(code[0])
|
169
|
+
# we have a inline and a block, replace any references with the sexp
|
170
|
+
code[1..-1].each_with_index do |h,i|
|
171
|
+
if h && h.kind_of?(Sexp) && h == Sexp.new(:lvar, needle)
|
172
|
+
# inline references like s(:lvar, :needle)
|
173
|
+
# indicates a call to the needle, thus code wants to inline
|
174
|
+
h[0] = replacement.sexp[0]
|
175
|
+
h[1] = replacement.sexp[1]
|
176
|
+
elsif h && h.kind_of?(Sexp) && @named_args.has_key?(needle) &&
|
177
|
+
Reduction.parse(@named_args[needle]).named_args.select{ |k,v|
|
178
|
+
h == Sexp.new(:call, Sexp.new(:lvar, needle), :[], Sexp.new(:arglist, Sexp.new(:lit, k)))
|
179
|
+
}.size == 1
|
180
|
+
# code is asking for a injection of one of the argument's with:
|
181
|
+
# s(:call, s(:lvar, :needle), :[], s(:arglist, s(:lit, :argumen)))
|
182
|
+
# which in ruby looks like:
|
183
|
+
# needle[:argument]
|
184
|
+
# which again is the way we support calling arguments of the neede
|
185
|
+
arg = h[3][1][1]
|
186
|
+
sexy = Marshal.load(Marshal.dump(Reduction.parse(Reduction.parse(@named_args[needle]).named_args[arg]).sexp)) # sexp handling is not clean cut
|
187
|
+
code[i+1] = sexy[3]
|
188
|
+
else
|
189
|
+
replace_variable_references(h,replacement,needle) if h && h.kind_of?(Sexp)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
else
|
193
|
+
code[1..-1].each do |h|
|
194
|
+
replace_variable_references(h,replacement,needle) if h && h.kind_of?(Sexp)
|
195
|
+
end
|
143
196
|
end
|
144
197
|
end
|
145
198
|
|
146
199
|
def to_rexp
|
200
|
+
raise "hell" if returns.kind_of?(Array)
|
147
201
|
"{#{returns}:#{reduction_id}}"
|
148
202
|
end
|
149
203
|
end
|
@@ -0,0 +1,503 @@
|
|
1
|
+
|
2
|
+
require 'sexp_processor'
|
3
|
+
|
4
|
+
class Ruby2Js < SexpProcessor
|
5
|
+
VERSION = '1.3.1'
|
6
|
+
LINE_LENGTH = 78
|
7
|
+
|
8
|
+
BINARY = [:<=>, :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :%, :<<, :>>, :**]
|
9
|
+
|
10
|
+
##
|
11
|
+
# Nodes that represent assignment and probably need () around them.
|
12
|
+
#
|
13
|
+
# TODO: this should be replaced with full precedence support :/
|
14
|
+
|
15
|
+
ASSIGN_NODES = [
|
16
|
+
:dasgn,
|
17
|
+
:flip2,
|
18
|
+
:flip3,
|
19
|
+
:lasgn,
|
20
|
+
:masgn,
|
21
|
+
:attrasgn,
|
22
|
+
:op_asgn1,
|
23
|
+
:op_asgn2,
|
24
|
+
:op_asgn_and,
|
25
|
+
:op_asgn_or,
|
26
|
+
:return,
|
27
|
+
:if, # HACK
|
28
|
+
]
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
@indent = " "
|
33
|
+
self.auto_shift_type = true
|
34
|
+
self.strict = true
|
35
|
+
self.expected = String
|
36
|
+
|
37
|
+
@calls = []
|
38
|
+
|
39
|
+
# self.debug[:defn] = /zsuper/
|
40
|
+
end
|
41
|
+
|
42
|
+
def parenthesize exp
|
43
|
+
case self.context[1]
|
44
|
+
when nil, :scope, :if, :iter, :resbody, :when, :while, :until then
|
45
|
+
exp
|
46
|
+
else
|
47
|
+
"(#{exp})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def indent(s)
|
52
|
+
s.to_s.split(/\n/).map{|line| @indent + line}.join("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
def cond_loop(exp, name)
|
56
|
+
cond = process(exp.shift)
|
57
|
+
body = process(exp.shift)
|
58
|
+
head_controlled = exp.shift
|
59
|
+
|
60
|
+
body = indent(body).chomp if body
|
61
|
+
|
62
|
+
code = []
|
63
|
+
if head_controlled then
|
64
|
+
code << "#{name}(#{cond}){"
|
65
|
+
code << body if body
|
66
|
+
code << "}"
|
67
|
+
else
|
68
|
+
code << "begin"
|
69
|
+
code << body if body
|
70
|
+
code << "end #{name} #{cond}"
|
71
|
+
end
|
72
|
+
code.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_and(exp)
|
76
|
+
parenthesize "#{process exp.shift} and #{process exp.shift}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def process_arglist(exp) # custom made node
|
80
|
+
code = []
|
81
|
+
until exp.empty? do
|
82
|
+
code << process(exp.shift)
|
83
|
+
end
|
84
|
+
code.join ', '
|
85
|
+
end
|
86
|
+
|
87
|
+
def process_args(exp)
|
88
|
+
args = []
|
89
|
+
|
90
|
+
until exp.empty? do
|
91
|
+
arg = exp.shift
|
92
|
+
case arg
|
93
|
+
when Symbol then
|
94
|
+
args << arg
|
95
|
+
when Array then
|
96
|
+
case arg.first
|
97
|
+
when :block then
|
98
|
+
asgns = {}
|
99
|
+
arg[1..-1].each do |lasgn|
|
100
|
+
asgns[lasgn[1]] = process(lasgn)
|
101
|
+
end
|
102
|
+
|
103
|
+
args.each_with_index do |name, index|
|
104
|
+
args[index] = asgns[name] if asgns.has_key? name
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raise "unknown arg type #{arg.first.inspect}"
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise "unknown arg type #{arg.inspect}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
return "(#{args.join ', '})"
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_array(exp)
|
118
|
+
"[#{process_arglist(exp)}]"
|
119
|
+
end
|
120
|
+
|
121
|
+
def process_attrasgn(exp)
|
122
|
+
receiver = process exp.shift
|
123
|
+
name = exp.shift
|
124
|
+
args = exp.empty? ? nil : exp.shift
|
125
|
+
|
126
|
+
case name
|
127
|
+
when :[]= then
|
128
|
+
rhs = process args.pop
|
129
|
+
"#{receiver}[#{process(args)}] = #{rhs}"
|
130
|
+
else
|
131
|
+
name = name.to_s.sub(/=$/, '')
|
132
|
+
if args && args != s(:arglist) then
|
133
|
+
"#{receiver}.#{name} = #{process(args)}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_block(exp)
|
139
|
+
result = []
|
140
|
+
|
141
|
+
exp << nil if exp.empty?
|
142
|
+
until exp.empty? do
|
143
|
+
code = exp.shift
|
144
|
+
if code.nil? or code.first == :nil then
|
145
|
+
result << "# do nothing\n"
|
146
|
+
else
|
147
|
+
result << process(code)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
result = parenthesize result.join ";\n"
|
152
|
+
result += ";\n" unless result.start_with? "("
|
153
|
+
|
154
|
+
return result
|
155
|
+
end
|
156
|
+
|
157
|
+
def process_break(exp)
|
158
|
+
val = exp.empty? ? nil : process(exp.shift)
|
159
|
+
# HACK "break" + (val ? " #{val}" : "")
|
160
|
+
if val then
|
161
|
+
"break #{val}"
|
162
|
+
else
|
163
|
+
"break"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def process_call(exp)
|
168
|
+
str = exp.inspect
|
169
|
+
receiver_node_type = exp.first.nil? ? nil : exp.first.first
|
170
|
+
receiver = process exp.shift
|
171
|
+
receiver = "(#{receiver})" if ASSIGN_NODES.include? receiver_node_type
|
172
|
+
|
173
|
+
name = exp.shift
|
174
|
+
args = []
|
175
|
+
raw = []
|
176
|
+
|
177
|
+
# this allows us to do both old and new sexp forms:
|
178
|
+
exp.push(*exp.pop[1..-1]) if exp.size == 1 && exp.first.first == :arglist
|
179
|
+
|
180
|
+
@calls.push name
|
181
|
+
|
182
|
+
in_context :arglist do
|
183
|
+
until exp.empty? do
|
184
|
+
arg_type = exp.first.sexp_type
|
185
|
+
e = exp.shift
|
186
|
+
raw << e.dup
|
187
|
+
arg = process e
|
188
|
+
|
189
|
+
next if arg.empty?
|
190
|
+
|
191
|
+
strip_hash = (arg_type == :hash and
|
192
|
+
not BINARY.include? name and
|
193
|
+
(exp.empty? or exp.first.sexp_type == :splat))
|
194
|
+
wrap_arg = Ruby2Ruby::ASSIGN_NODES.include? arg_type
|
195
|
+
|
196
|
+
arg = arg[2..-3] if strip_hash
|
197
|
+
arg = "(#{arg})" if wrap_arg
|
198
|
+
|
199
|
+
args << arg
|
200
|
+
end
|
201
|
+
end
|
202
|
+
#str+
|
203
|
+
case name
|
204
|
+
when *BINARY then
|
205
|
+
"(#{receiver} #{name} #{args.join(', ')})"
|
206
|
+
when :[] then
|
207
|
+
receiver ||= "self"
|
208
|
+
if raw.size == 1 && raw.first.sexp_type == :lit && raw.first.to_a[1].kind_of?(Symbol)
|
209
|
+
"#{receiver}.#{args.first[1..-1]}"
|
210
|
+
else
|
211
|
+
"#{receiver}[#{args.join(', ')}]"
|
212
|
+
end
|
213
|
+
when :[]= then
|
214
|
+
receiver ||= "self"
|
215
|
+
rhs = args.pop
|
216
|
+
"#{receiver}[#{args.join(', ')}] = #{rhs}"
|
217
|
+
when :"-@" then
|
218
|
+
"-#{receiver}"
|
219
|
+
when :"+@" then
|
220
|
+
"+#{receiver}"
|
221
|
+
when :new
|
222
|
+
args = nil if args.empty?
|
223
|
+
args = "(#{args.join(',')})" if args
|
224
|
+
receiver = "#{receiver}" if receiver
|
225
|
+
|
226
|
+
"#{name} #{receiver}#{args}"
|
227
|
+
when :lambda
|
228
|
+
receiver = "#{receiver}." if receiver
|
229
|
+
|
230
|
+
"#{receiver}function"
|
231
|
+
else
|
232
|
+
args = nil if args.empty?
|
233
|
+
args = "#{args.join(',')}" if args
|
234
|
+
receiver = "#{receiver}." if receiver
|
235
|
+
|
236
|
+
"#{receiver}#{name}(#{args})"
|
237
|
+
end
|
238
|
+
ensure
|
239
|
+
@calls.pop
|
240
|
+
end
|
241
|
+
|
242
|
+
def process_const(exp)
|
243
|
+
exp.shift.to_s
|
244
|
+
end
|
245
|
+
|
246
|
+
def process_defn(exp)
|
247
|
+
type1 = exp[1].first
|
248
|
+
type2 = exp[2].first rescue nil
|
249
|
+
|
250
|
+
if type1 == :args and [:ivar, :attrset].include? type2 then
|
251
|
+
name = exp.shift
|
252
|
+
case type2
|
253
|
+
when :ivar then
|
254
|
+
exp.clear
|
255
|
+
return "attr_reader #{name.inspect}"
|
256
|
+
when :attrset then
|
257
|
+
exp.clear
|
258
|
+
return "attr_writer :#{name.to_s[0..-2]}"
|
259
|
+
else
|
260
|
+
raise "Unknown defn type: #{exp.inspect}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
case type1
|
265
|
+
when :scope, :args then
|
266
|
+
name = exp.shift
|
267
|
+
args = process(exp.shift)
|
268
|
+
args = "" if args == "()"
|
269
|
+
body = []
|
270
|
+
until exp.empty? do
|
271
|
+
body << indent(process(exp.shift))
|
272
|
+
end
|
273
|
+
body = body.join("\n")
|
274
|
+
return "#{exp.comments}#{name}#{args}{\n#{body}\n}".gsub(/\n\s*\n+/, "\n")
|
275
|
+
else
|
276
|
+
raise "Unknown defn type: #{type1} for #{exp.inspect}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def process_hash(exp)
|
281
|
+
result = []
|
282
|
+
|
283
|
+
until exp.empty?
|
284
|
+
e = exp.shift
|
285
|
+
if e.sexp_type == :lit
|
286
|
+
lhs = process(e)
|
287
|
+
rhs = exp.shift
|
288
|
+
t = rhs.first
|
289
|
+
rhs = process rhs
|
290
|
+
rhs = "(#{rhs})" unless [:lit, :str, :array, :iter].include? t # TODO: verify better!
|
291
|
+
|
292
|
+
result << "\n#{lhs[1..-1]}: #{rhs}"
|
293
|
+
else
|
294
|
+
lhs = process(e)
|
295
|
+
rhs = exp.shift
|
296
|
+
t = rhs.first
|
297
|
+
rhs = process rhs
|
298
|
+
rhs = "(#{rhs})" unless [:lit, :str].include? t # TODO: verify better!
|
299
|
+
|
300
|
+
result << "\n#{lhs}: #{rhs}"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
return "{ #{indent(result.join(', '))} }"
|
305
|
+
end
|
306
|
+
|
307
|
+
def process_iasgn(exp)
|
308
|
+
lhs = exp.shift
|
309
|
+
if exp.empty? then # part of an masgn
|
310
|
+
lhs.to_s
|
311
|
+
else
|
312
|
+
if lhs.to_s[0] == '@'
|
313
|
+
"self.#{lhs.to_s[1..-1]} = #{process exp.shift}"
|
314
|
+
else
|
315
|
+
"#{lhs} = #{process exp.shift}"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def process_if(exp)
|
321
|
+
expand = Ruby2Ruby::ASSIGN_NODES.include? exp.first.first
|
322
|
+
c = process exp.shift
|
323
|
+
t_type = exp.first.sexp_type
|
324
|
+
t = process exp.shift
|
325
|
+
f_type = exp.first.sexp_type if exp.first
|
326
|
+
f = process exp.shift
|
327
|
+
|
328
|
+
c = "(#{c.chomp})" #if c =~ /\n/
|
329
|
+
|
330
|
+
if t then
|
331
|
+
#unless expand then
|
332
|
+
# if f then
|
333
|
+
# r = "#{c} ? (#{t}) : (#{f})"
|
334
|
+
# r = nil if r =~ /return/ # HACK - need contextual awareness or something
|
335
|
+
# else
|
336
|
+
# r = "#{t} if #{c}"
|
337
|
+
# end
|
338
|
+
# return r if r and (@indent+r).size < LINE_LENGTH and r !~ /\n/
|
339
|
+
#end
|
340
|
+
|
341
|
+
r = "if#{c}{\n#{indent(t)}#{[:block, :while, :if].include?(t_type) ? '':';'}\n"
|
342
|
+
r << "}else{\n#{indent(f)}#{[:block, :while, :if].include?(f_type) ? '':';'}\n" if f
|
343
|
+
r << "}"
|
344
|
+
|
345
|
+
r
|
346
|
+
elsif f
|
347
|
+
unless expand then
|
348
|
+
r = "#{f} unless #{c}"
|
349
|
+
return r if (@indent+r).size < LINE_LENGTH and r !~ /\n/
|
350
|
+
end
|
351
|
+
"unless #{c} then\n#{indent(f)}\nend"
|
352
|
+
else
|
353
|
+
# empty if statement, just do it in case of side effects from condition
|
354
|
+
"if #{c} then\n#{indent '# do nothing'}\nend"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def process_iter(exp)
|
359
|
+
iter = process exp.shift
|
360
|
+
args = exp.shift
|
361
|
+
args = (args == 0) ? '' : process(args)
|
362
|
+
body = exp.empty? ? nil : process(exp.shift)
|
363
|
+
|
364
|
+
b, e = #if iter == "END" then
|
365
|
+
[ "{", "}" ]
|
366
|
+
#else
|
367
|
+
# [ "do", "end" ]
|
368
|
+
# end
|
369
|
+
|
370
|
+
iter.sub!(/\(\)$/, '')
|
371
|
+
|
372
|
+
result = []
|
373
|
+
result << "#{iter}(#{args})"
|
374
|
+
result << "#{b}"
|
375
|
+
result << "\n"
|
376
|
+
if body then
|
377
|
+
result << indent(body.strip)
|
378
|
+
result << "\n"
|
379
|
+
end
|
380
|
+
result << e
|
381
|
+
result.join
|
382
|
+
end
|
383
|
+
|
384
|
+
def process_ivar(exp)
|
385
|
+
"this.#{exp.shift.to_s[1..-1]}"
|
386
|
+
end
|
387
|
+
|
388
|
+
def process_lasgn(exp)
|
389
|
+
s = "#{exp.shift}"
|
390
|
+
s += " = #{process exp.shift}" unless exp.empty?
|
391
|
+
s
|
392
|
+
end
|
393
|
+
|
394
|
+
def process_lit(exp)
|
395
|
+
obj = exp.shift
|
396
|
+
case obj
|
397
|
+
when Range then
|
398
|
+
"(#{obj.inspect})"
|
399
|
+
else
|
400
|
+
obj.inspect
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def process_lvar(exp)
|
405
|
+
exp.shift.to_s
|
406
|
+
end
|
407
|
+
|
408
|
+
def process_masgn(exp)
|
409
|
+
lhs = exp.shift
|
410
|
+
rhs = exp.empty? ? nil : exp.shift
|
411
|
+
|
412
|
+
case lhs.first
|
413
|
+
when :array then
|
414
|
+
lhs.shift
|
415
|
+
lhs = lhs.map do |l|
|
416
|
+
case l.first
|
417
|
+
when :masgn then
|
418
|
+
"(#{process(l)})"
|
419
|
+
else
|
420
|
+
process(l)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
when :lasgn then
|
424
|
+
lhs = [ splat(lhs.last) ]
|
425
|
+
when :splat then
|
426
|
+
lhs = [ :"*" ]
|
427
|
+
else
|
428
|
+
raise "no clue: #{lhs.inspect}"
|
429
|
+
end
|
430
|
+
|
431
|
+
if context[1] == :iter and rhs then
|
432
|
+
lhs << splat(rhs[1])
|
433
|
+
rhs = nil
|
434
|
+
end
|
435
|
+
|
436
|
+
unless rhs.nil? then
|
437
|
+
t = rhs.first
|
438
|
+
rhs = process rhs
|
439
|
+
rhs = rhs[1..-2] if t == :array # FIX: bad? I dunno
|
440
|
+
return "#{lhs.join(", ")} = #{rhs}"
|
441
|
+
else
|
442
|
+
return lhs.join(", ")
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def process_nil(exp)
|
447
|
+
"null"
|
448
|
+
end
|
449
|
+
|
450
|
+
def process_return(exp)
|
451
|
+
if exp.empty? then
|
452
|
+
return "return"
|
453
|
+
else
|
454
|
+
return "return #{process exp.shift}"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def process_scope(exp)
|
459
|
+
exp.empty? ? "" : process(exp.shift)
|
460
|
+
end
|
461
|
+
|
462
|
+
def process_true(exp)
|
463
|
+
"true"
|
464
|
+
end
|
465
|
+
|
466
|
+
def process_until(exp)
|
467
|
+
cond_loop(exp, 'until')
|
468
|
+
end
|
469
|
+
|
470
|
+
def process_while(exp)
|
471
|
+
cond_loop(exp, 'while')
|
472
|
+
end
|
473
|
+
|
474
|
+
end
|
475
|
+
|
476
|
+
module Goling
|
477
|
+
class Linguified
|
478
|
+
|
479
|
+
def indent
|
480
|
+
@indenture ||= ''
|
481
|
+
@indenture += ' '
|
482
|
+
end
|
483
|
+
def indenture
|
484
|
+
@indenture ||= ''
|
485
|
+
@indenture
|
486
|
+
end
|
487
|
+
def indenture= str
|
488
|
+
@indenture = str
|
489
|
+
end
|
490
|
+
def new_line
|
491
|
+
"\n" + indenture
|
492
|
+
end
|
493
|
+
def dent
|
494
|
+
@indenture ||= ''
|
495
|
+
@indenture = @indenture[2..-1]
|
496
|
+
end
|
497
|
+
|
498
|
+
def to_js sexy = @sexy
|
499
|
+
Ruby2Js.new.process(sexy)
|
500
|
+
end
|
501
|
+
|
502
|
+
end
|
503
|
+
end
|
data/test/test_goling.rb
CHANGED
@@ -34,4 +34,127 @@ class TestGoling < Test::Unit::TestCase
|
|
34
34
|
|
35
35
|
"view all files inside all directories recursively".linguify.to_ruby.size > 0
|
36
36
|
end
|
37
|
+
|
38
|
+
should "generate mixed ruby & javascript" do
|
39
|
+
|
40
|
+
reduce /single day plunge/ => {:to => 'move', :lang => :js, :inline => true} do
|
41
|
+
@traffic.forEach(lambda{ |day|
|
42
|
+
histogram.add(day.page_views,item);
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
reduce /largest ({move:[^}]*}) last month traffic records/ => {:to => 'histogram_rule', :lang => :js, :inline => true} do |move|
|
48
|
+
if k>=@list[lst][:k]
|
49
|
+
@list.push(n)
|
50
|
+
else
|
51
|
+
while true
|
52
|
+
if k<@list[mid][:k]
|
53
|
+
lst=mid
|
54
|
+
else
|
55
|
+
beg=mid
|
56
|
+
end
|
57
|
+
mid = (beg+lst)>>1
|
58
|
+
break if mid==beg
|
59
|
+
end
|
60
|
+
|
61
|
+
if k<@list[mid][:k]
|
62
|
+
if @list.length >= @max && mid==0
|
63
|
+
# lowest k in max days
|
64
|
+
#print(tojson(this.list[mid].v));
|
65
|
+
emit(@list[mid][:v][:date],@list[mid][:v])
|
66
|
+
end
|
67
|
+
@list.splice(mid,0,n)
|
68
|
+
elsif k<@list[lst][:k]
|
69
|
+
@list.splice(lst,0,n)
|
70
|
+
else
|
71
|
+
#print("!!!");
|
72
|
+
#print(k);
|
73
|
+
#print(this.list[mid].k);
|
74
|
+
#print(this.list[end].k);
|
75
|
+
while true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
reduce /({histogram_rule:[^}]*}) on all pages on the whole site/ => {:to => 'traffic_map', :lang => :js} do |rule|
|
82
|
+
def Node k,v
|
83
|
+
@next = nil
|
84
|
+
@prev = nil
|
85
|
+
@k = nil
|
86
|
+
@v = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
histogram = {
|
90
|
+
max: 30,
|
91
|
+
list: [],
|
92
|
+
find: lambda{
|
93
|
+
beg = 0
|
94
|
+
lst = @list.length-1
|
95
|
+
mid = (beg+lst)>>1
|
96
|
+
while true
|
97
|
+
if k<@list[mid][:k]
|
98
|
+
lst=mid
|
99
|
+
else
|
100
|
+
beg=mid
|
101
|
+
end
|
102
|
+
mid = (beg+lst)>>1
|
103
|
+
break if mid==beg
|
104
|
+
end
|
105
|
+
|
106
|
+
# may not be exact match (multiple of same key value), searh in both directions for a exact match
|
107
|
+
start = mid
|
108
|
+
dir = 1
|
109
|
+
while v != @list[mid][:v]
|
110
|
+
mid += dir
|
111
|
+
if mid == @list.length
|
112
|
+
mid = start
|
113
|
+
dir =- 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
return mid
|
117
|
+
},
|
118
|
+
|
119
|
+
add: lambda{ |k,v|
|
120
|
+
if @list.length
|
121
|
+
|
122
|
+
beg = 0
|
123
|
+
lst = @list.length-1
|
124
|
+
mid = (beg+lst)>>1
|
125
|
+
|
126
|
+
n = Node.new(k,v)
|
127
|
+
@last.next = n
|
128
|
+
n.prev = @last
|
129
|
+
@last = n
|
130
|
+
|
131
|
+
rule
|
132
|
+
|
133
|
+
if @list.length > @max
|
134
|
+
# remove first
|
135
|
+
@list.splice(this.find(@first[:k],@first[:v]),1)
|
136
|
+
@first[:next].prev = nil
|
137
|
+
@first = @first[:next]
|
138
|
+
end
|
139
|
+
else
|
140
|
+
@last = Node.new(k,v)
|
141
|
+
@first = @last
|
142
|
+
@list.push(@last)
|
143
|
+
end
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
rule[:move]
|
148
|
+
end
|
149
|
+
|
150
|
+
reduce /view ({traffic_map:[^}]*})/ => '' do |map|
|
151
|
+
reduce = "function(k,vals){ return 1; }"
|
152
|
+
testing = Site.collection.map_reduce(map, reduce, :out => "testing", :query => { :page => page })
|
153
|
+
# I dont like how the id in mongodb doesnt match the id from the map_reduce!
|
154
|
+
traffic.any_in(_id: testing.find.map{ |h| h['_id'].to_s.downcase.gsub(/:/,' colon ').gsub(/ /,'-') }).all.map{ |m| m }
|
155
|
+
end
|
156
|
+
|
157
|
+
"view largest single day plunge last month traffic records on all pages on the whole site".linguify.to_ruby.size > 0
|
158
|
+
end
|
159
|
+
|
37
160
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: goling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Patrick Hanevold
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-10-07 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: sourcify
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- lib/goling.rb
|
87
87
|
- lib/goling/linguified.rb
|
88
88
|
- lib/goling/reduction.rb
|
89
|
+
- lib/goling/translators/javascript.rb
|
89
90
|
- test/helper.rb
|
90
91
|
- test/test_goling.rb
|
91
92
|
homepage: http://github.com/patrickhno/goling
|
@@ -101,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
102
|
requirements:
|
102
103
|
- - ">="
|
103
104
|
- !ruby/object:Gem::Version
|
104
|
-
hash:
|
105
|
+
hash: -3022085012113281803
|
105
106
|
segments:
|
106
107
|
- 0
|
107
108
|
version: "0"
|