$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) require "stringio" module CBS class Compiler attr_reader :bindings def initialize(io) io = StringIO.new(io) if io.is_a? String @io = io end def parse @selectors = Array.new @bindings = Array.new @classes = Array.new selector = '' while (char = @io.getc) case char when ?, @selectors << selector.strip selector = '' when ?{ @selectors << selector.strip selector = '' @bindings << BlockBinding.new(@selectors, parseBlock(?}, 1, "{")) @selectors = Array.new when ?% @selectors << selector.strip selector = '' @bindings << ObjectBinding.new(@selectors, parseBlock(?;, 0).sub(/\s*;$/, '')) @selectors = Array.new when ?@ if match('behavior') @classes << parseClass('Behavior', 'create', false) elsif match('class') @classes << parseClass('Class', 'create', false) elsif match('extention') @classes << parseClass('_empty_', 'addMethods', true) end else selector << char end end end def parseClass(metaclass, initializer, switchNames) name = readUntil(':{').strip superclass = nil functions = nil char = @io.getc if char == ?: superclass = readUntil('{').strip char = @io.getc end if char == ?{ functions = parseClassBlock end if switchNames metaclass = name superclass = nil name = nil end Container.new(name, superclass, metaclass, initializer, functions) end def parseClassBlock eob = false functions = [] until eob readUntil('}\':abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$') case (char = @io.getc) when nil then eob = true when ?} then eob = true else functions << parseFunction("%c" % char) end end return functions end def parseFunction(name) name += readUntil('{(').strip arguments = '' block = nil char = @io.getc if char == ?( arguments += '(' arguments += readUntil(')') arguments += @io.read(1) readUntil('{') char = @io.getc else arguments = '()' end if char == ?{ block = parseBlock(?}, 1, '{') end Function.new(name, arguments, block) end def parseBlock(terminator, level, code='') eob = false while (!eob) and (char = @io.getc) case char when ?' code << parseString(?', "'") when ?" code << parseString(?", '"') when ?{ level += 1 code << char when ?} level -= 1 code << char else code << char end eob = true if char == terminator and level == 0 end code.strip.gsub(/\n/, "\n ") end def parseString(terminator, string) eob = false while (!eob) and (char = @io.getc) case char when ?\\ string << @io.getc else string << char end eob = true if char == terminator end string end def readUntil(delimiters) string = '' while char = @io.getc if delimiters.include? char @io.pos = @io.pos - 1 return string end string << char end string end def match(string) pos = @io.pos return true if @io.read(string.length) == string @io.pos = pos return false end def format code = "" code << @classes.inject('') do |c, klass| c << klass.format c end code << "\n" code << "Event.addBehavior({\n" code << @bindings.inject('') do |c, binding| c << binding.format c << ",\n" unless @bindings.last === binding c end code << "\n});\n" code end end class Container attr_accessor :name, :superclass, :metaclass, :initializer, :functions def initialize(name, superclass, metaclass, initializer, functions) @name, @superclass, @metaclass, @initializer, @functions = name, superclass, metaclass, initializer, functions end def format code = '' code += name_for_code code += "#{metaclass}.#{initializer}(" code += "#{superclass}, " unless superclass.nil? code += '{' code += functions.inject('') do |m, function| m += function.format(m.empty?) end code += "\n});\n\n" end def name_for_code return '' if name.nil? if (@name || '').include? '.' "#{@name} = " else "var #{@name} = " end end end class Function attr_accessor :name, :arguments, :block def initialize(name, arguments, block) @name, @arguments, @block = name, arguments, block end def format(first) code = '' code += ',' unless first code += "\n" code += " #{name} : function#{arguments}#{block.gsub("\n ", "\n")}" end end class Binding attr_accessor :selectors, :code def initialize(selectors, code) @selectors, @code = selectors, code end def format @selectors.inject('') do |code, selector| code << format_with(selector) code << ",\n" unless @selectors.last === selector code end end end class BlockBinding < Binding def format_with(selector) " '#{selector.gsub("'", "\\'")}' : function(event)#{code}" end end class ObjectBinding < Binding def format_with(selector) " '#{selector.gsub("'", "\\'")}' : #{code}" end end end