aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/code_generation.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/code_generation.rb')
-rw-r--r--actionpack/lib/action_controller/code_generation.rb235
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