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 CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{goling}
8
- s.version = "0.1.1"
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-09-08}
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
  ]
@@ -4,7 +4,13 @@ require 'goling/linguified'
4
4
  require 'goling/reduction'
5
5
 
6
6
  def reduce(regexp,&code)
7
- Goling::rules << { :match => regexp.keys[0], :result => regexp.values[0], :proc => code }
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)&&regexp.values[0].has_key?(:inline) ? regexp.values[0][:inline] : false,
12
+ :proc => code
13
+ }
8
14
  end
9
15
 
10
16
  module Goling
@@ -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
- found = Goling.rules.select do |rule|
17
- if rule[:match] =~ str
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,bind
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
 
@@ -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
- ap str
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
- 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
82
+ args = @named_args.keys
83
+ # args[] now has the symbolized argument names of the code block
37
84
 
38
- block_args = s.sexp[2]
39
- #ap "block args: #{block_args}"
40
- if block_args
41
- if block_args[0]==:lasgn
42
- # single argument
43
- args = [block_args[1]]
44
- elsif block_args[0]==:masgn
45
- # multiple arguments
46
- args = block_args[1]
47
- raise "unsupported argument type #{args}" unless args[0]==:array
48
- args = args[1..-1].map{ |arg|
49
- raise "unsupported argument type #{arg}" unless arg[0]==:lasgn
50
- arg[1]
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
- else
83
- code = s.sexp[3].dup
84
- end
85
- replace.each do |k,v|
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 return_variable
91
- code = Sexp.new(:lasgn, return_variable, s.sexp[3].dup)
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 = s.sexp[3].dup
94
- end
95
- replace.each do |k,v|
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 "number of arguments in reduction rule do not match the number of block arguments" unless s.rule_args.size == 0
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
- code[1]=replacement if code[1] == needle
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
- code[1..-1].each do |h|
142
- replace_variable_references(h,replacement,needle) if h && h.kind_of?(Sexp)
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
@@ -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.1.1
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-09-08 00:00:00 Z
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: 4126556151488913212
105
+ hash: -3022085012113281803
105
106
  segments:
106
107
  - 0
107
108
  version: "0"