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 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"