goling 0.1.1 → 0.2.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.
- 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"
|