diff options
Diffstat (limited to 'actionpack/lib/action_controller/code_generation.rb')
-rw-r--r-- | actionpack/lib/action_controller/code_generation.rb | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/code_generation.rb b/actionpack/lib/action_controller/code_generation.rb new file mode 100644 index 0000000000..6519980198 --- /dev/null +++ b/actionpack/lib/action_controller/code_generation.rb @@ -0,0 +1,235 @@ +module ActionController + module CodeGeneration #:nodoc + class GenerationError < StandardError + end + + class Source + attr_reader :lines, :indentation_level + IndentationString = ' ' + def initialize + @lines, @indentation_level = [], 0 + end + def line(line) + @lines << (IndentationString * @indentation_level + line) + end + alias :<< :line + + def indent + @indentation_level += 1 + yield + ensure + @indentation_level -= 1 + end + + def to_s() lines.join("\n") end + end + + class CodeGenerator + attr_accessor :source, :locals + def initialize(source = nil) + @locals = [] + @source = source || Source.new + end + + BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym} + ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym} + Keywords = BeginKeywords + ResumeKeywords + + def method_missing(keyword, *text) + if Keywords.include? keyword + if ResumeKeywords.include? keyword + raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/ + source.lines.pop # Remove the 'end' + end + + line "#{keyword} #{text.join ' '}" + begin source.indent { yield(self.dup) } + ensure line 'end' + end + else + super(keyword, *text) + end + end + + def line(*args) self.source.line(*args) end + alias :<< :line + def indent(*args, &block) source(*args, &block) end + def to_s() source.to_s end + + def share_locals_with(other) + other.locals = self.locals = (other.locals | locals) + end + + FieldsToDuplicate = [:locals] + def dup + copy = self.class.new(source) + self.class::FieldsToDuplicate.each do |sym| + value = self.send(sym) + value = value.dup unless value.nil? || value.is_a?(Numeric) + copy.send("#{sym}=", value) + end + return copy + end + end + + class RecognitionGenerator < CodeGenerator + Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement] + attr_accessor(*Attributes) + FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes + + def initialize(*args) + super(*args) + @after, @before = [], [] + @current = nil + @results, @constants = {}, {} + @depth = 0 + @move_ahead = nil + @finish_statement = Proc.new {|hash_expr| hash_expr} + end + + def if_next_matches(string, &block) + test = Routing.test_condition(next_segment(true), string) + self.if(test, &block) + end + + def move_forward(places = 1) + dup = self.dup + dup.depth += 1 + dup.move_ahead = places + yield dup + end + + def next_segment(assign_inline = false, default = nil) + if locals.include?(segment_name) + code = segment_name + else + code = "#{segment_name} = #{path_name}[#{index_name}]" + if assign_inline + code = "(#{code})" + else + line(code) + code = segment_name + end + + locals << segment_name + end + code = "(#{code} || #{default.inspect})" if default + + return code + end + + def segment_name() "segment#{depth}".to_sym end + def path_name() :path end + def index_name + move_ahead, @move_ahead = @move_ahead, nil + move_ahead ? "index += #{move_ahead}" : 'index' + end + + def continue + dup = self.dup + dup.before << dup.current + dup.current = dup.after.shift + dup.go + end + + def go + if current then current.write_recognition(self) + else self.finish + end + end + + def result(key, expression, delay = false) + unless delay + line "#{key}_value = #{expression}" + expression = "#{key}_value" + end + results[key] = expression + end + def constant_result(key, object) + constants[key] = object + end + + def finish(ensure_traversal_finished = true) + pairs = [] + (results.keys + constants.keys).uniq.each do |key| + pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}" + end + hash_expr = "{#{pairs.join(', ')}}" + + statement = finish_statement.call(hash_expr) + if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement} + else self << statement + end + end + end + + class GenerationGenerator < CodeGenerator + Attributes = [:after, :before, :current, :segments] + attr_accessor(*Attributes) + FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes + + def initialize(*args) + super(*args) + @after, @before = [], [] + @current = nil + @segments = [] + end + + def hash_name() 'hash' end + def local_name(key) "#{key}_value" end + + def hash_value(key, assign = true, default = nil) + if locals.include?(local_name(key)) then code = local_name(key) + else + code = "hash[#{key.to_sym.inspect}]" + if assign + code = "(#{local_name(key)} = #{code})" + locals << local_name(key) + end + end + code = "(#{code} || #{default.inspect})" if default + return code + end + + def expire_for_keys(*keys) + return if keys.empty? + conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"} + line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}" + end + + def add_segment(*segments) + d = dup + d.segments.concat segments + yield d + end + + def go + if current then current.write_generation(self) + else self.finish + end + end + + def continue + d = dup + d.before << d.current + d.current = d.after.shift + d.go + end + + def finish + line %("#{segments.join('/')}") + end + + def check_conditions(conditions) + tests = [] + generator = nil + conditions.each do |key, condition| + tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp + tests << Routing.test_condition((generator || self).hash_value(key, false), condition) + generator = self.dup unless generator + end + return tests.join(' && ') + end + end + end +end |