# frozen_string_literal: true module Tipi module Configuration class Interpreter # make_blank_slate def initialize(assembler) @assembler = assembler end def gzip_response @assembler.emit 'req = Tipi::GZip.wrap(req)' end def log(out) @assembler.wrap_current_frame 'logger.log_request(req) do |req|' end def error(&block) assembler.emit_exception_handler &block end def match(pattern, &block) @assembler.emit_conditional "if req.path =~ #{pattern.inspect}", &block end end class Assembler def self.from_source(code) new.from_source code end def from_source(code) @stack = [new_frame] @app_procs = {} @interpreter = Interpreter.new self @interpreter.instance_eval code loop do frame = @stack.pop return assemble_app_proc(frame).join("\n") if @stack.empty? @stack.last[:body] << assemble_frame(frame) end end def new_frame { prelude: [], body: [] } end def add_frame(&block) @stack.push new_frame yield ensure frame = @stack.pop emit assemble(frame) end def wrap_current_frame(head) frame = @stack.pop wrapper = new_frame wrapper[:body] << head @stack.push wrapper @stack.push frame end def emit(code) @stack.last[:body] << code end def emit_prelude(code) @stack.last[:prelude] << code end def emit_exception_handler(&block) proc_id = add_app_proc block @stack.last[:rescue_proc_id] = proc_id end def emit_block(conditional, &block) proc_id = add_app_proc block @stack.last[:branched] = true emit conditional add_frame &block end def add_app_proc(proc) id = :"proc#{@app_procs.size}" @app_procs[id] = proc id end def assemble_frame(frame) indent = 0 lines = [] emit_code lines, frame[:prelude], indent if frame[:rescue_proc_id] emit_code lines, 'begin', indent indent += 1 end emit_code lines, frame[:body], indent if frame[:rescue_proc_id] emit_code lines, 'rescue => e', indent emit_code lines, " app_procs[#{frame[:rescue_proc_id].inspect}].call(req, e)", indent emit_code lines, 'end', indent indent -= 1 end lines end def assemble_app_proc(frame) indent = 0 lines = [] emit_code lines, frame[:prelude], indent emit_code lines, 'proc do |req|', indent emit_code lines, frame[:body], indent + 1 emit_code lines, 'end', indent lines end def emit_code(lines, code, indent) if code.is_a? Array code.each { |l| emit_code lines, l, indent + 1 } else lines << (indent_line code, indent) end end @@indents = Hash.new { |h, k| h[k] = ' ' * k } def indent_line(code, indent) indent == 0 ? code : "#{ @@indents[indent] }#{code}" end end end end def assemble(code) Tipi::Configuration::Assembler.from_source(code) end code = assemble <<~RUBY gzip_response log STDOUT RUBY puts code