├── .gitignore ├── README ├── compiled.php ├── grammar.y ├── input.aw ├── lexer.rb ├── lexer_test.rb ├── nodes.rb ├── parser.rb ├── parser_output.txt ├── parser_test.rb ├── phlower.rb ├── runtime.rb └── tests.rb /.gitignore: -------------------------------------------------------------------------------- 1 | test.rb 2 | yield.rb 3 | 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | __ 2 | .-.' '.-. 3 | .-( \ / )-. 4 | / '..pHPh..' \ 5 | , \.--.hPHPHPHp.--./ 6 | |\ , ( :hPHPHPHp: ) 7 | _\.\/| /'--'pPHPHPHp'--'\ 8 | '-.. ;/| \ .''pHPh''. / 9 | .--`'. :/|'-( / \ )-' 10 | '--. `. / //'-'.__.'-; 11 | `'-,_';// , /| 12 | '(( |\/./_ 13 | \\ . |\; ..-' 14 | \\ |\: .'`--. 15 | \\, .' .--' 16 | ))'_,-'` 17 | //-' 18 | // 19 | // 20 | |/ 21 | 22 | ==Introduction== 23 | phlower (pronounced flower) is a small ruby script that compiles the Awesome code (invented by MACournoyer for his book http://createyourproglang.com/ ) into PHP code. 24 | This script is written in Ruby and it depends on the racc gem. 25 | 26 | ==Usage== 27 | 28 | input.aw file should contain your Awesome input code : 29 | 30 | class Awesome{ 31 | def init(){ 32 | pass(); 33 | } 34 | 35 | def x(){ 36 | return(2 + 2); 37 | } 38 | 39 | def z(){ 40 | print("poulet"); 41 | } 42 | } 43 | 44 | aw = Awesome.new(); 45 | 46 | 47 | And : 48 | $ ruby phlower.rb input.aw compiled.php 49 | 50 | Should output this PHP code : 51 | Class::method() 86 | * elsif 87 | * foreach 88 | * while, do..while 89 | * for 90 | * switch..case 91 | * try..catch 92 | * arrays 93 | * ++,--,+=,-=,*=,/=,.=,%=,!,!=,||,&&,==,>,<,<=,>= operators 94 | * break, continue 95 | * constants defined with define() 96 | * extends (for classes) 97 | * type-casting , eq: (int)$var 98 | -------------------------------------------------------------------------------- /compiled.php: -------------------------------------------------------------------------------- 1 | #{current_indent}" 72 | # end 73 | # # Adjust the current indentation level. 74 | # current_indent = indent.size 75 | # indent_stack.push(current_indent) 76 | # tokens << [:INDENT, indent.size] 77 | # i += indent.size + 2 78 | 79 | # This one takes care of cases 2 and 3. 80 | # We stay in the same block if the indent level is the 81 | # same as current_indent, or close a block, if it is lower. 82 | # elsif indent = chunk[/\A\n( *)/m, 1] 83 | # if indent.size < current_indent 84 | # indent_stack.pop 85 | # current_indent = indent_stack.first || 0 86 | # tokens << [:DEDENT, indent.size] 87 | # tokens << [:NEWLINE, "\n"] 88 | # elsif indent.size == current_indent 89 | # # Nothing to do, we're still in the same block 90 | # tokens << [:NEWLINE, "\n"] 91 | # else # indent.size > current_indent 92 | # # Cannot increase indent level without using ":", so 93 | # # this is an error. 94 | # raise "Missing ':'" 95 | # end 96 | # i += indent.size + 1 97 | 98 | elsif indent = chunk[/\A\n( *)/m, 1] 99 | tokens << [:NEWLINE, "\n"] 100 | i += 1 101 | # Ignore whitespace 102 | elsif chunk.match(/\A /) 103 | i += 1 104 | 105 | # We treat all other single characters as a token. 106 | # Eg.: ( ) , . ! 107 | else 108 | value = chunk[0,1] 109 | tokens << [value, value] 110 | i += 1 111 | 112 | end 113 | 114 | end 115 | 116 | # Close all open blocks 117 | # while indent = indent_stack.pop 118 | # tokens << [:DEDENT, indent_stack.first || 0] 119 | # end 120 | 121 | tokens 122 | end 123 | end 124 | 125 | # [[:DEF, "def"], [:IDENTIFIER, "pass"], ["(", "("], [")", ")"], 126 | # [:INDENT, 2], [:IDENTIFIER, "echo"], ["(", "("], [:STRING, "joie"], [")", ")"], 127 | # [:NEWLINE, "\n"], 128 | # [:DEDENT, 0], 129 | # [:NEWLINE, "\n"], 130 | # [:CLASS, "class"], [:CONSTANT, "Awesome"], 131 | # [:INDENT, 2], [:DEF, "def"], [:IDENTIFIER, "init"], ["(", "("], [")", ")"], 132 | # [:INDENT, 4], [:IDENTIFIER, "pass"], ["(", "("], [")", ")"], 133 | # [:DEDENT, 2], 134 | # [:NEWLINE, "\n"], 135 | # [:NEWLINE, "\n"], 136 | # [:DEF, "def"], [:IDENTIFIER, "x"], ["(", "("], [")", ")"], 137 | # [:INDENT, 4], [:IDENTIFIER, "return"], ["(", "("], [:NUMBER, 2], ["+", "+"], [:NUMBER, 2], [")", ")"], 138 | # [:DEDENT, 0], 139 | # [:NEWLINE, "\n"], 140 | # [:NEWLINE, "\n"], [:DEF, "def"], [:IDENTIFIER, "z"], ["(", "("], [")", ")"], 141 | # [:INDENT, 4], [:IDENTIFIER, "print"], ["(", "("], [:STRING, "poulet"], [")", ")"], 142 | # [:DEDENT, 0], 143 | # [:NEWLINE, "\n"], 144 | # [:DEDENT, 0], 145 | # [:NEWLINE, "\n"], [:IDENTIFIER, "poulet"], ["=", "="], [:TRUE, "true"], 146 | # [:NEWLINE, "\n"], [:IF, "if"], [:IDENTIFIER, "poulet"], 147 | # [:INDENT, 2], [:IDENTIFIER, "aw"], ["=", "="], [:CONSTANT, "Awesome"], [".", "."], [:IDENTIFIER, "new"], ["(", "("], [:STRING, "brilliant!"], [",", ","], [:NUMBER, 2], [")", ")"], 148 | # [:NEWLINE, "\n"], [:IDENTIFIER, "pouti"], ["=", "="], [:IDENTIFIER, "aw"], [".", "."], [:IDENTIFIER, "x"], ["(", "("], [")", ")"], 149 | # [:NEWLINE, "\n"], [:IDENTIFIER, "awe"], ["=", "="], [:IDENTIFIER, "aw"], [".", "."], [:IDENTIFIER, "init"], ["(", "("], [")", ")"], [".", "."], [:IDENTIFIER, "x"], ["(", "("], [")", ")"], [".", "."], [:IDENTIFIER, "z"], ["(", "("], [")", ")"], 150 | # [:NEWLINE, "\n"], [:IDENTIFIER, "print"], ["(", "("], [:IDENTIFIER, "awe"], [")", ")"], 151 | # [:DEDENT, 0], 152 | # [:NEWLINE, "\n"], [:ELSE, "else"], 153 | # [:INDENT, 2], [:IDENTIFIER, "weird"], ["(", "("], [")", ")"], 154 | # [:DEDENT, 0], 155 | # [:NEWLINE, "\n"]] 156 | -------------------------------------------------------------------------------- /lexer_test.rb: -------------------------------------------------------------------------------- 1 | require "lexer" 2 | 3 | code = <<-EOS 4 | if 1: 5 | print "..." 6 | if false: 7 | pass 8 | print "done!" 9 | print "The End" 10 | EOS 11 | 12 | p Lexer.new.tokenize(code) 13 | # [[:IF, "if"], [:NUMBER, 1], 14 | # [:INDENT, 2], [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"], 15 | # [:IF, "if"], [:IDENTIFIER, "false"], 16 | # [:INDENT, 4], [:IDENTIFIER, "pass"], 17 | # [:DEDENT, 2], [:NEWLINE, "\n"], 18 | # [:IDENTIFIER, "print"], [:STRING, "done!"], 19 | # [:DEDENT, 0], [:NEWLINE, "\n"], 20 | # [:IDENTIFIER, "print"], [:STRING, "The End"]] -------------------------------------------------------------------------------- /nodes.rb: -------------------------------------------------------------------------------- 1 | class Awesome 2 | end 3 | # Collection of nodes each one representing an expression. 4 | class Nodes < Awesome 5 | attr_accessor :nodes 6 | def initialize(nodes) 7 | @nodes = nodes 8 | end 9 | 10 | def <<(node) 11 | @nodes << node 12 | self 13 | end 14 | 15 | # This method is the "interpreter" part of our language. 16 | # All nodes know how to eval itself and returns the result 17 | # of its evaluation. 18 | # The "context" variable is the environment in which the node 19 | # is evaluated (local variables, current class, etc.). 20 | def eval(context) 21 | # The last value evaluated in a method is the return value. 22 | @nodes.map { |node| node.eval(context) }.last 23 | end 24 | end 25 | 26 | # Literals are static values that have a Ruby representation, 27 | # eg.: a string, a number, true, false, nil, etc. 28 | class LiteralNode < Awesome 29 | attr_accessor :value 30 | def initialize(value) 31 | @value = value 32 | end 33 | 34 | def eval(context) 35 | case @value 36 | when Numeric 37 | Runtime["Number"].new_value(@value) 38 | when String 39 | Runtime["String"].new_value(@value) 40 | when TrueClass 41 | Runtime["true"] 42 | when FalseClass 43 | Runtime["false"] 44 | when NilClass 45 | Runtime["nil"] 46 | else 47 | raise "Unknown literal type: " + @value.class.name 48 | end 49 | end 50 | end 51 | 52 | class VarNode < Awesome 53 | attr_accessor :name 54 | def initialize(name) 55 | @name = name 56 | end 57 | 58 | def eval(context) 59 | context[@name] 60 | end 61 | end 62 | 63 | 64 | # Node of a method call or local variable access, 65 | # can take any of these forms: 66 | # 67 | # method # this form can also be a local variable 68 | # method(argument1, argument2) 69 | # receiver.method 70 | # receiver.method(argument1, argument2) 71 | # 72 | class CallNode < Awesome 73 | attr_accessor :receiver, :method, :arguments, :is_end 74 | def initialize(receiver, method, arguments=[], is_end=false) 75 | @receiver = receiver 76 | @method = method 77 | @arguments = arguments 78 | @is_end = is_end 79 | end 80 | 81 | def eval(context) 82 | # If there's no receiver and the method name is 83 | # the name of a local variable, then it's a local 84 | # variable access. 85 | # This trick allows us to skip the () when calling 86 | # a method. 87 | if @receiver.nil? && context.locals[@method] 88 | context.locals[@method] 89 | 90 | # Method call 91 | else 92 | # In case there's no receiver we default to self 93 | # So that calling "print" is like "self.print". 94 | if @receiver 95 | receiver = @receiver.eval(context) 96 | else 97 | receiver = context.current_self 98 | end 99 | arguments = @arguments.map { |arg| arg.eval(context) } 100 | receiver.call(@method, arguments) 101 | end 102 | end 103 | end 104 | 105 | class ArrayNode < Awesome 106 | attr_accessor :values 107 | def initialize(arguments=[]) 108 | @values = arguments 109 | end 110 | 111 | def end(context) 112 | context[@value] 113 | end 114 | end 115 | 116 | # Retreiving the value of a constant. 117 | class GetConstantNode < Awesome 118 | attr_accessor :name 119 | def initialize(name) 120 | @name = name 121 | end 122 | 123 | def eval(context) 124 | context[@name] 125 | end 126 | end 127 | 128 | # Setting the value of a constant. 129 | class SetConstantNode < Awesome 130 | attr_accessor :name, :value 131 | def initialize(name, value) 132 | @name = name 133 | @value = value 134 | end 135 | 136 | def eval(context) 137 | context[@name] = @value.eval(context) 138 | end 139 | end 140 | 141 | # Setting the value of a local variable. 142 | class SetLocalNode < Awesome 143 | attr_accessor :name, :value, :is_end 144 | def initialize(name, value, is_end) 145 | @name = name 146 | @value = value 147 | @is_end = is_end 148 | end 149 | 150 | def eval(context) 151 | context.locals[@name] = @value.eval(context) 152 | end 153 | end 154 | 155 | # Method definition. 156 | class DefNode < Awesome 157 | attr_accessor :name, :params, :body 158 | def initialize(name, params, body) 159 | @name = name 160 | @params = params 161 | @body = body 162 | end 163 | 164 | def eval(context) 165 | context.current_class.awesome_methods[@name] = AwesomeMethod.new(@params, @body) 166 | end 167 | end 168 | 169 | # Class definition. 170 | class ClassNode < Awesome 171 | attr_accessor :name, :body 172 | def initialize(name, body) 173 | @name = name 174 | @body = body 175 | end 176 | 177 | def eval(context) 178 | # Create the class and put it's value in a constant. 179 | awesome_class = AwesomeClass.new 180 | context[@name] = awesome_class 181 | 182 | # Evaluate the body of the class in its context. 183 | @body.eval(Context.new(awesome_class, awesome_class)) 184 | 185 | awesome_class 186 | end 187 | end 188 | 189 | # if-else control structure. 190 | # Look at this node if you want to implement other 191 | # control structures like while, for, loop, etc. 192 | class IfNode < Awesome 193 | attr_accessor :condition, :body, :else_body 194 | def initialize(condition, body, else_body=nil) 195 | @condition = condition 196 | @body = body 197 | @else_body = else_body 198 | end 199 | 200 | def eval(context) 201 | # We turn the condition node into a Ruby value 202 | # to use Ruby's "if" control structure. 203 | if @condition.eval(context).ruby_value 204 | @body.eval(context) 205 | elsif @else_body 206 | @else_body.eval(context) 207 | end 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /parser.rb: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT MODIFY!!!! 3 | # This file is automatically generated by Racc 1.4.6 4 | # from Racc grammer file "". 5 | # 6 | 7 | require 'racc/parser.rb' 8 | 9 | require "lexer" 10 | require "nodes" 11 | 12 | class Parser < Racc::Parser 13 | 14 | module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 170) 15 | def parse(code, show_tokens=false) 16 | @tokens = Lexer.new.tokenize(code) 17 | puts @tokens.inspect if show_tokens 18 | do_parse 19 | end 20 | 21 | def next_token 22 | @tokens.shift 23 | end 24 | ...end grammar.y/module_eval... 25 | ##### State transition tables begin ### 26 | 27 | racc_action_table = [ 28 | -5, 40, 6, 65, 10, 12, -5, 15, 17, 19, 29 | 21, 1, 4, 7, 53, 71, -5, 26, 57, 72, 30 | 37, 49, 52, 41, -1, 8, 6, 25, 10, 12, 31 | -5, 15, 17, 19, 21, 1, 4, 7, 27, 73, 32 | 49, 70, 62, 52, 28, 6, 42, 10, 12, 8, 33 | 15, 17, 19, 21, 1, 4, 7, 47, 31, 32, 34 | 33, 34, 35, 49, 6, 30, 10, 12, 8, 15, 35 | 17, 19, 21, 1, 4, 7, 49, 31, 32, 33, 36 | 34, 35, 75, 6, 30, 10, 12, 8, 15, 17, 37 | 19, 21, 1, 4, 7, 56, 31, 32, 33, 34, 38 | 35, nil, 6, 30, 10, 12, 8, 15, 17, 19, 39 | 21, 1, 4, 7, nil, 31, 32, 33, 34, 35, 40 | nil, 6, 30, 10, 12, 8, 15, 17, 19, 21, 41 | 1, 4, 7, nil, 31, 32, 33, 34, 35, nil, 42 | 6, 30, 10, 12, 8, 15, 17, 19, 21, 1, 43 | 4, 7, 51, 52, nil, nil, nil, nil, nil, 6, 44 | nil, 10, 12, 8, 15, 17, 19, 21, 1, 4, 45 | 7, nil, nil, nil, nil, nil, nil, nil, 6, nil, 46 | 10, 12, 8, 15, 17, 19, 21, 1, 4, 7, 47 | nil, nil, nil, nil, nil, nil, nil, 6, nil, 10, 48 | 12, 8, 15, 17, 19, 21, 1, 4, 7, 26, 49 | nil, 26, nil, nil, nil, nil, nil, nil, 26, 25, 50 | 8, 25, 31, 32, 33, 34, 35, 26, 25, 30, 51 | nil, nil, nil, 26, 60, nil, nil, 25, 31, 32, 52 | 33, 34, 35, 25, 26, 30, 31, 32, 33, 34, 53 | 35, nil, nil, 30, 25, nil, nil, nil, 49 ] 54 | 55 | racc_action_check = [ 56 | 24, 10, 24, 53, 24, 24, 24, 24, 24, 24, 57 | 24, 24, 24, 24, 40, 66, 24, 63, 44, 66, 58 | 7, 40, 44, 12, 0, 24, 0, 63, 0, 0, 59 | 24, 0, 0, 0, 0, 0, 0, 0, 4, 68, 60 | 62, 63, 48, 68, 4, 8, 23, 8, 8, 0, 61 | 8, 8, 8, 8, 8, 8, 8, 30, 50, 50, 62 | 50, 50, 50, 41, 6, 50, 6, 6, 8, 6, 63 | 6, 6, 6, 6, 6, 6, 71, 5, 5, 5, 64 | 5, 5, 72, 28, 5, 28, 28, 6, 28, 28, 65 | 28, 28, 28, 28, 28, 42, 43, 43, 43, 43, 66 | 43, nil, 60, 43, 60, 60, 28, 60, 60, 60, 67 | 60, 60, 60, 60, nil, 64, 64, 64, 64, 64, 68 | nil, 52, 64, 52, 52, 60, 52, 52, 52, 52, 69 | 52, 52, 52, nil, 38, 38, 38, 38, 38, nil, 70 | 27, 38, 27, 27, 52, 27, 27, 27, 27, 27, 71 | 27, 27, 39, 39, nil, nil, nil, nil, nil, 29, 72 | nil, 29, 29, 27, 29, 29, 29, 29, 29, 29, 73 | 29, nil, nil, nil, nil, nil, nil, nil, 37, nil, 74 | 37, 37, 29, 37, 37, 37, 37, 37, 37, 37, 75 | nil, nil, nil, nil, nil, nil, nil, 49, nil, 49, 76 | 49, 37, 49, 49, 49, 49, 49, 49, 49, 3, 77 | nil, 46, nil, nil, nil, nil, nil, nil, 47, 3, 78 | 49, 46, 46, 46, 46, 46, 46, 45, 47, 46, 79 | nil, nil, nil, 57, 47, nil, nil, 45, 45, 45, 80 | 45, 45, 45, 57, 73, 45, 36, 36, 36, 36, 81 | 36, nil, nil, 36, 73, nil, nil, nil, 36 ] 82 | 83 | racc_action_pointer = [ 84 | 24, nil, nil, 203, 16, 60, 62, -8, 43, nil, 85 | -11, nil, 10, nil, nil, nil, nil, nil, nil, nil, 86 | nil, nil, nil, 46, 0, nil, nil, 138, 81, 157, 87 | 45, nil, nil, nil, nil, nil, 229, 176, 117, 126, 88 | -8, 34, 95, 79, -5, 221, 205, 212, 39, 195, 89 | 41, nil, 119, -9, nil, nil, nil, 227, nil, nil, 90 | 100, nil, 11, 11, 98, nil, -8, nil, 16, nil, 91 | nil, 47, 70, 238, nil, nil, nil ] 92 | 93 | racc_action_default = [ 94 | -36, -21, -14, -2, -27, -3, -36, -41, -36, -6, 95 | -54, -7, -54, -8, -9, -17, -10, -18, -11, -19, 96 | -12, -20, -13, -54, -36, -16, -15, -36, -36, -36, 97 | -54, -22, -23, -24, -25, -26, -54, -36, -39, -54, 98 | -54, -54, -54, -4, -54, -42, -31, -29, -52, -36, 99 | -44, -37, -36, -47, -45, -50, 77, -28, -43, -35, 100 | -36, -33, -54, -54, -40, -48, -54, -32, -54, -51, 101 | -53, -54, -54, -30, -46, -49, -34 ] 102 | 103 | racc_goto_table = [ 104 | 39, 5, 3, 23, 66, 48, nil, 36, nil, 54, 105 | 55, 58, 59, 61, nil, nil, nil, nil, nil, 44, 106 | nil, nil, nil, 67, nil, 43, nil, nil, nil, 45, 107 | 46, 69, nil, nil, nil, nil, nil, nil, 50, 76, 108 | 74, nil, nil, nil, nil, nil, nil, nil, nil, nil, 109 | 5, 63, 68, 64 ] 110 | 111 | racc_goto_check = [ 112 | 15, 3, 2, 1, 17, 16, nil, 3, nil, 16, 113 | 16, 4, 4, 4, nil, nil, nil, nil, nil, 15, 114 | nil, nil, nil, 4, nil, 3, nil, nil, nil, 3, 115 | 3, 16, nil, nil, nil, nil, nil, nil, 3, 4, 116 | 16, nil, nil, nil, nil, nil, nil, nil, nil, nil, 117 | 3, 2, 15, 3 ] 118 | 119 | racc_goto_pointer = [ 120 | nil, 3, 2, 1, -34, nil, nil, nil, nil, nil, 121 | nil, nil, nil, nil, nil, -8, -31, -49 ] 122 | 123 | racc_goto_default = [ 124 | nil, nil, nil, 38, 24, 9, 11, 13, 14, 16, 125 | 18, 20, 22, 2, 29, nil, nil, nil ] 126 | 127 | racc_reduce_table = [ 128 | 0, 0, :racc_error, 129 | 0, 32, :_reduce_1, 130 | 1, 32, :_reduce_2, 131 | 1, 33, :_reduce_3, 132 | 3, 33, :_reduce_4, 133 | 2, 33, :_reduce_5, 134 | 1, 34, :_reduce_none, 135 | 1, 34, :_reduce_none, 136 | 1, 34, :_reduce_none, 137 | 1, 34, :_reduce_none, 138 | 1, 34, :_reduce_none, 139 | 1, 34, :_reduce_none, 140 | 1, 34, :_reduce_none, 141 | 1, 34, :_reduce_none, 142 | 1, 34, :_reduce_none, 143 | 1, 35, :_reduce_none, 144 | 1, 35, :_reduce_none, 145 | 1, 36, :_reduce_17, 146 | 1, 36, :_reduce_18, 147 | 1, 36, :_reduce_19, 148 | 1, 36, :_reduce_20, 149 | 1, 36, :_reduce_21, 150 | 1, 45, :_reduce_none, 151 | 1, 45, :_reduce_none, 152 | 1, 45, :_reduce_none, 153 | 1, 45, :_reduce_none, 154 | 1, 45, :_reduce_none, 155 | 1, 38, :_reduce_27, 156 | 4, 37, :_reduce_28, 157 | 3, 37, :_reduce_29, 158 | 6, 37, :_reduce_30, 159 | 3, 37, :_reduce_31, 160 | 5, 37, :_reduce_32, 161 | 4, 37, :_reduce_33, 162 | 7, 37, :_reduce_34, 163 | 4, 37, :_reduce_35, 164 | 0, 44, :_reduce_none, 165 | 3, 44, :_reduce_37, 166 | 0, 46, :_reduce_38, 167 | 1, 46, :_reduce_39, 168 | 3, 46, :_reduce_40, 169 | 1, 39, :_reduce_41, 170 | 3, 40, :_reduce_42, 171 | 4, 40, :_reduce_43, 172 | 3, 40, :_reduce_44, 173 | 3, 41, :_reduce_45, 174 | 6, 41, :_reduce_46, 175 | 0, 48, :_reduce_47, 176 | 1, 48, :_reduce_48, 177 | 3, 48, :_reduce_49, 178 | 3, 42, :_reduce_50, 179 | 5, 43, :_reduce_51, 180 | 3, 43, :_reduce_52, 181 | 3, 47, :_reduce_53 ] 182 | 183 | racc_reduce_n = 54 184 | 185 | racc_shift_n = 77 186 | 187 | racc_token_table = { 188 | false => 0, 189 | :error => 1, 190 | :IF => 2, 191 | :ELSE => 3, 192 | :DEF => 4, 193 | :CLASS => 5, 194 | :NEWLINE => 6, 195 | :NUMBER => 7, 196 | :STRING => 8, 197 | :TRUE => 9, 198 | :FALSE => 10, 199 | :NIL => 11, 200 | :IDENTIFIER => 12, 201 | :CONSTANT => 13, 202 | :INDENT => 14, 203 | :DEDENT => 15, 204 | ";" => 16, 205 | "+" => 17, 206 | "-" => 18, 207 | "*" => 19, 208 | "/" => 20, 209 | "%" => 21, 210 | "(" => 22, 211 | ")" => 23, 212 | "." => 24, 213 | "[" => 25, 214 | "]" => 26, 215 | "," => 27, 216 | "=" => 28, 217 | "{" => 29, 218 | "}" => 30 } 219 | 220 | racc_nt_base = 31 221 | 222 | racc_use_result_var = true 223 | 224 | Racc_arg = [ 225 | racc_action_table, 226 | racc_action_check, 227 | racc_action_default, 228 | racc_action_pointer, 229 | racc_goto_table, 230 | racc_goto_check, 231 | racc_goto_default, 232 | racc_goto_pointer, 233 | racc_nt_base, 234 | racc_reduce_table, 235 | racc_token_table, 236 | racc_shift_n, 237 | racc_reduce_n, 238 | racc_use_result_var ] 239 | 240 | Racc_token_to_s_table = [ 241 | "$end", 242 | "error", 243 | "IF", 244 | "ELSE", 245 | "DEF", 246 | "CLASS", 247 | "NEWLINE", 248 | "NUMBER", 249 | "STRING", 250 | "TRUE", 251 | "FALSE", 252 | "NIL", 253 | "IDENTIFIER", 254 | "CONSTANT", 255 | "INDENT", 256 | "DEDENT", 257 | "\";\"", 258 | "\"+\"", 259 | "\"-\"", 260 | "\"*\"", 261 | "\"/\"", 262 | "\"%\"", 263 | "\"(\"", 264 | "\")\"", 265 | "\".\"", 266 | "\"[\"", 267 | "\"]\"", 268 | "\",\"", 269 | "\"=\"", 270 | "\"{\"", 271 | "\"}\"", 272 | "$start", 273 | "Root", 274 | "Expressions", 275 | "Expression", 276 | "Terminator", 277 | "Literal", 278 | "Call", 279 | "Var", 280 | "Constant", 281 | "Assign", 282 | "Def", 283 | "Class", 284 | "If", 285 | "Array", 286 | "BinaryOperator", 287 | "ArgList", 288 | "Block", 289 | "ParamList" ] 290 | 291 | Racc_debug_parser = false 292 | 293 | ##### State transition tables end ##### 294 | 295 | # reduce 0 omitted 296 | 297 | module_eval(<<'.,.,', 'grammar.y', 33) 298 | def _reduce_1(val, _values, result) 299 | result = Nodes.new([]) 300 | result 301 | end 302 | .,., 303 | 304 | module_eval(<<'.,.,', 'grammar.y', 34) 305 | def _reduce_2(val, _values, result) 306 | result = val[0] 307 | result 308 | end 309 | .,., 310 | 311 | module_eval(<<'.,.,', 'grammar.y', 39) 312 | def _reduce_3(val, _values, result) 313 | result = Nodes.new(val) 314 | result 315 | end 316 | .,., 317 | 318 | module_eval(<<'.,.,', 'grammar.y', 40) 319 | def _reduce_4(val, _values, result) 320 | result = val[0] << val[2] 321 | result 322 | end 323 | .,., 324 | 325 | module_eval(<<'.,.,', 'grammar.y', 42) 326 | def _reduce_5(val, _values, result) 327 | result = Nodes.new([val[0]]) 328 | result 329 | end 330 | .,., 331 | 332 | # reduce 6 omitted 333 | 334 | # reduce 7 omitted 335 | 336 | # reduce 8 omitted 337 | 338 | # reduce 9 omitted 339 | 340 | # reduce 10 omitted 341 | 342 | # reduce 11 omitted 343 | 344 | # reduce 12 omitted 345 | 346 | # reduce 13 omitted 347 | 348 | # reduce 14 omitted 349 | 350 | # reduce 15 omitted 351 | 352 | # reduce 16 omitted 353 | 354 | module_eval(<<'.,.,', 'grammar.y', 65) 355 | def _reduce_17(val, _values, result) 356 | result = LiteralNode.new(val[0]) 357 | result 358 | end 359 | .,., 360 | 361 | module_eval(<<'.,.,', 'grammar.y', 66) 362 | def _reduce_18(val, _values, result) 363 | result = LiteralNode.new(val[0]) 364 | result 365 | end 366 | .,., 367 | 368 | module_eval(<<'.,.,', 'grammar.y', 67) 369 | def _reduce_19(val, _values, result) 370 | result = LiteralNode.new(true) 371 | result 372 | end 373 | .,., 374 | 375 | module_eval(<<'.,.,', 'grammar.y', 68) 376 | def _reduce_20(val, _values, result) 377 | result = LiteralNode.new(false) 378 | result 379 | end 380 | .,., 381 | 382 | module_eval(<<'.,.,', 'grammar.y', 69) 383 | def _reduce_21(val, _values, result) 384 | result = LiteralNode.new(nil) 385 | result 386 | end 387 | .,., 388 | 389 | # reduce 22 omitted 390 | 391 | # reduce 23 omitted 392 | 393 | # reduce 24 omitted 394 | 395 | # reduce 25 omitted 396 | 397 | # reduce 26 omitted 398 | 399 | module_eval(<<'.,.,', 'grammar.y', 83) 400 | def _reduce_27(val, _values, result) 401 | result = VarNode.new(val[0]) 402 | result 403 | end 404 | .,., 405 | 406 | module_eval(<<'.,.,', 'grammar.y', 89) 407 | def _reduce_28(val, _values, result) 408 | result = CallNode.new(nil, val[0], val[2], false) 409 | result 410 | end 411 | .,., 412 | 413 | module_eval(<<'.,.,', 'grammar.y', 91) 414 | def _reduce_29(val, _values, result) 415 | result = CallNode.new(val[0], val[2], false) 416 | result 417 | end 418 | .,., 419 | 420 | module_eval(<<'.,.,', 'grammar.y', 94) 421 | def _reduce_30(val, _values, result) 422 | result = CallNode.new(val[0], val[2], val[4], false) 423 | result 424 | end 425 | .,., 426 | 427 | module_eval(<<'.,.,', 'grammar.y', 95) 428 | def _reduce_31(val, _values, result) 429 | result = CallNode.new(val[0], val[1], [val[2]], false) 430 | result 431 | end 432 | .,., 433 | 434 | module_eval(<<'.,.,', 'grammar.y', 97) 435 | def _reduce_32(val, _values, result) 436 | result = CallNode.new(nil, val[0], val[2], true) 437 | result 438 | end 439 | .,., 440 | 441 | module_eval(<<'.,.,', 'grammar.y', 99) 442 | def _reduce_33(val, _values, result) 443 | result = CallNode.new(val[0], val[2], nil, true) 444 | result 445 | end 446 | .,., 447 | 448 | module_eval(<<'.,.,', 'grammar.y', 102) 449 | def _reduce_34(val, _values, result) 450 | result = CallNode.new(val[0], val[2], val[4], true) 451 | result 452 | end 453 | .,., 454 | 455 | module_eval(<<'.,.,', 'grammar.y', 103) 456 | def _reduce_35(val, _values, result) 457 | result = CallNode.new(val[0], val[1], [val[2]], true) 458 | result 459 | end 460 | .,., 461 | 462 | # reduce 36 omitted 463 | 464 | module_eval(<<'.,.,', 'grammar.y', 107) 465 | def _reduce_37(val, _values, result) 466 | result = ArrayNode.new(val[1]) 467 | result 468 | end 469 | .,., 470 | 471 | module_eval(<<'.,.,', 'grammar.y', 111) 472 | def _reduce_38(val, _values, result) 473 | result = [] 474 | result 475 | end 476 | .,., 477 | 478 | module_eval(<<'.,.,', 'grammar.y', 112) 479 | def _reduce_39(val, _values, result) 480 | result = val 481 | result 482 | end 483 | .,., 484 | 485 | module_eval(<<'.,.,', 'grammar.y', 113) 486 | def _reduce_40(val, _values, result) 487 | result = val[0] << val[2] 488 | result 489 | end 490 | .,., 491 | 492 | module_eval(<<'.,.,', 'grammar.y', 117) 493 | def _reduce_41(val, _values, result) 494 | result = GetConstantNode.new(val[0]) 495 | result 496 | end 497 | .,., 498 | 499 | module_eval(<<'.,.,', 'grammar.y', 122) 500 | def _reduce_42(val, _values, result) 501 | result = SetLocalNode.new(val[0], val[2], false) 502 | result 503 | end 504 | .,., 505 | 506 | module_eval(<<'.,.,', 'grammar.y', 123) 507 | def _reduce_43(val, _values, result) 508 | result = SetLocalNode.new(val[0], val[2], true) 509 | result 510 | end 511 | .,., 512 | 513 | module_eval(<<'.,.,', 'grammar.y', 124) 514 | def _reduce_44(val, _values, result) 515 | result = SetConstantNode.new(val[0], val[2]) 516 | result 517 | end 518 | .,., 519 | 520 | module_eval(<<'.,.,', 'grammar.y', 130) 521 | def _reduce_45(val, _values, result) 522 | result = DefNode.new(val[1], [], val[2]) 523 | result 524 | end 525 | .,., 526 | 527 | module_eval(<<'.,.,', 'grammar.y', 132) 528 | def _reduce_46(val, _values, result) 529 | result = DefNode.new(val[1], val[3], val[5]) 530 | result 531 | end 532 | .,., 533 | 534 | module_eval(<<'.,.,', 'grammar.y', 136) 535 | def _reduce_47(val, _values, result) 536 | result = [] 537 | result 538 | end 539 | .,., 540 | 541 | module_eval(<<'.,.,', 'grammar.y', 137) 542 | def _reduce_48(val, _values, result) 543 | result = val 544 | result 545 | end 546 | .,., 547 | 548 | module_eval(<<'.,.,', 'grammar.y', 138) 549 | def _reduce_49(val, _values, result) 550 | result = val[0] << val[2] 551 | result 552 | end 553 | .,., 554 | 555 | module_eval(<<'.,.,', 'grammar.y', 143) 556 | def _reduce_50(val, _values, result) 557 | result = ClassNode.new(val[1], val[2]) 558 | result 559 | end 560 | .,., 561 | 562 | module_eval(<<'.,.,', 'grammar.y', 149) 563 | def _reduce_51(val, _values, result) 564 | result = IfNode.new(val[1], val[2], val[4]) 565 | result 566 | end 567 | .,., 568 | 569 | module_eval(<<'.,.,', 'grammar.y', 150) 570 | def _reduce_52(val, _values, result) 571 | result = IfNode.new(val[1], val[2]) 572 | result 573 | end 574 | .,., 575 | 576 | module_eval(<<'.,.,', 'grammar.y', 160) 577 | def _reduce_53(val, _values, result) 578 | result = val[1] 579 | result 580 | end 581 | .,., 582 | 583 | def _reduce_none(val, _values, result) 584 | val[0] 585 | end 586 | 587 | end # class Parser 588 | -------------------------------------------------------------------------------- /parser_output.txt: -------------------------------------------------------------------------------- 1 | input: 2 | class Awesome: 3 | def initialize(name): 4 | pass 5 | 6 | def x: 7 | 2 8 | 9 | if true: 10 | aw = Awesome.new("brilliant!") 11 | else: 12 | weird 13 | 14 | output: 15 | # 21 | ]>, @params=["name"]> 22 | ]>, # 24 | ]>, @params=[]> 25 | ]> 26 | ]>> 27 | , 28 | # 30 | ]>, @body=#, @arguments=[ 32 | # 33 | ], @method="new">> 34 | ]>, @condition=#> 35 | ]> -------------------------------------------------------------------------------- /parser_test.rb: -------------------------------------------------------------------------------- 1 | require "parser.rb" 2 | 3 | code = <<-EOS 4 | class Awesome: 5 | def initialize(name): 6 | pass 7 | 8 | def x: 9 | 2 10 | 11 | if true: 12 | aw = Awesome.new("brilliant!") 13 | else: 14 | weird 15 | EOS 16 | 17 | 18 | p Parser.new.parse(code) 19 | # 25 | # ]>> 26 | # ]>, 27 | # 29 | # ]>> 30 | # ]> 31 | # ]>>, 32 | # , @body=], 36 | # @receiver=>> 37 | # ]>, @else_body= 39 | # ]>> 40 | # ]> 41 | -------------------------------------------------------------------------------- /phlower.rb: -------------------------------------------------------------------------------- 1 | require "parser.rb" 2 | 3 | # 4 | # Synopsis : ruby awesomephp.rb input.aw compiled.php 5 | # input.aw = awesome input file 6 | # compiled.php = php output file 7 | # 8 | 9 | # can be : ext for external, arg for argument (if or method), body for body of a method/block : 10 | 11 | def ifnode(arg, body, elsebody) 12 | @c << "if (" 13 | yield arg 14 | @c << ") {\n" 15 | yield body 16 | if !(elsebody.nil?) 17 | @c << "} else {\n" 18 | yield elsebody 19 | @c << "}\n" 20 | else 21 | @c << "}\n" 22 | end 23 | end 24 | 25 | def classnode(name, body) 26 | @c << 'class ' 27 | yield name 28 | @c << "{\n\n" 29 | yield body 30 | @c << "}\n\n" 31 | end 32 | 33 | def defnode(name, params, body) 34 | @c << 'function ' 35 | if name=='init' 36 | @c << '__construct' 37 | else 38 | @c << name 39 | end 40 | @c << '(' 41 | yield params 42 | @c << "){\n" 43 | yield body 44 | @c << "}\n\n" 45 | end 46 | 47 | def callnode(identifier, arglist, receiver, is_end) 48 | 49 | # new Class(args) 50 | if identifier=='new' && receiver!='' && receiver.instance_of?(GetConstantNode) 51 | @c << "new " 52 | yield receiver 53 | @c << "(" 54 | arglist.each_with_index do |arg, count| 55 | yield arg 56 | if count<(arglist.length-1) 57 | @c << "," 58 | end 59 | end 60 | @c << ")" 61 | if is_end 62 | @c << ";\n" 63 | end 64 | # obj.method(args) 65 | # OR 66 | # 2+2 67 | elsif !(receiver.nil?) 68 | if identifier=='+' 69 | yield receiver 70 | @c << " + " 71 | yield arglist 72 | elsif identifier=='-' 73 | yield receiver 74 | @c << " - " 75 | yield arglist 76 | elsif identifier=='*' 77 | yield receiver 78 | @c << " * " 79 | yield arglist 80 | elsif identifier=='/' 81 | yield receiver 82 | @c << " / " 83 | yield arglist 84 | elsif identifier=='%' 85 | yield receiver 86 | @c << " % " 87 | yield arglist 88 | else 89 | yield receiver,receiver.class 90 | if arglist 91 | @c << "->"+identifier+"(" 92 | arglist.each_with_index do |arg, count| 93 | yield arg 94 | if count<(arglist.length-1) 95 | @c << "," 96 | end 97 | end 98 | @c << ")" 99 | else 100 | @c << "->"+identifier 101 | end 102 | 103 | if is_end 104 | @c << ";\n" 105 | end 106 | end 107 | # function(args) 108 | else 109 | @c << identifier+"(" 110 | arglist.each_with_index do |arg, count| 111 | yield arg 112 | if count<(arglist.length-1) 113 | @c << "," 114 | end 115 | end 116 | @c << ")" 117 | if is_end 118 | @c << ";\n" 119 | end 120 | end 121 | end 122 | 123 | def setlocalnode(name, value, is_end) 124 | if name[0,1] == '@' 125 | @c << "var $"+name[1,name.length]+' = ' 126 | else 127 | @c << "$"+name+' = ' 128 | end 129 | 130 | yield value 131 | # if !value.instance_of?(CallNode) 132 | if is_end 133 | @c << ";\n" 134 | end 135 | # end 136 | end 137 | 138 | def arraynode(values) 139 | @c << "array(" 140 | values.each_with_index do |arg, count| 141 | yield arg 142 | if count<(values.length-1) 143 | @c << "," 144 | end 145 | end 146 | @c << ")" 147 | end 148 | 149 | def getconstantnode(name) 150 | @c << name 151 | end 152 | 153 | def nodenode(nod) 154 | yield nod 155 | end 156 | 157 | def varnode(node) 158 | @c << "$" 159 | yield node 160 | end 161 | 162 | def literalnode(node) 163 | if node.instance_of?(String) 164 | @c << '"' 165 | yield node 166 | @c << '"' 167 | else 168 | yield node 169 | end 170 | end 171 | 172 | def node(objet) 173 | 174 | if objet.instance_of?(SetLocalNode) 175 | # puts objet.inspect 176 | # puts 177 | end 178 | 179 | if objet.instance_of?(ArrayNode) 180 | arraynode(objet.values) do |txt| 181 | if txt.is_a?(Awesome) 182 | node(txt) 183 | elsif(txt.instance_of?(Array)) 184 | txt.each {|tx| node(tx)} 185 | else 186 | @c << txt unless txt.nil? 187 | end 188 | end 189 | end 190 | 191 | if objet.instance_of?(GetConstantNode) 192 | getconstantnode(objet.name) do |txt| 193 | if txt.is_a?(Awesome) 194 | node(txt) 195 | elsif(txt.instance_of?(Array)) 196 | txt.each {|tx| node(tx)} 197 | else 198 | @c << txt 199 | end 200 | end 201 | end 202 | 203 | # if true: 204 | if objet.instance_of?(IfNode) 205 | ifnode(objet.condition, objet.body, objet.else_body) do |txt| 206 | if txt.is_a?(Awesome) 207 | node(txt) 208 | elsif(txt.instance_of?(Array)) 209 | txt.each {|tx| node(tx)} 210 | else 211 | @c << txt 212 | end 213 | end 214 | end 215 | 216 | # class Name: 217 | if objet.instance_of?(ClassNode) 218 | classnode(objet.name, objet.body) do |txt| 219 | if txt.is_a?(Awesome) 220 | node(txt) 221 | elsif(txt.instance_of?(Array)) 222 | txt.each {|tx| node(tx)} 223 | else 224 | @c << txt 225 | end 226 | end 227 | end 228 | 229 | # def methode: 230 | if objet.instance_of?(DefNode) 231 | defnode(objet.name, objet.params, objet.body) do |txt| 232 | if txt.is_a?(Awesome) 233 | node(txt) 234 | elsif(txt.instance_of?(Array)) 235 | txt.each {|tx| node(tx)} 236 | else 237 | @c << txt 238 | end 239 | end 240 | end 241 | 242 | # objet.method(args) 243 | if objet.instance_of?(CallNode) 244 | callnode(objet.method, objet.arguments, objet.receiver, objet.is_end) do |txt, type| 245 | if txt.is_a?(Awesome) 246 | node(txt) 247 | elsif(txt.instance_of?(Array)) 248 | txt.each {|tx,typ| node(tx)} 249 | else 250 | @c << txt unless txt.nil? 251 | end 252 | end 253 | end 254 | 255 | # variable 256 | if objet.instance_of?(VarNode) 257 | varnode(objet.name) do |txt| 258 | if txt.is_a?(Awesome) 259 | node(txt) 260 | elsif(txt.instance_of?(Array)) 261 | txt.each {|tx| node(tx)} 262 | else 263 | @c << txt 264 | end 265 | end 266 | end 267 | 268 | # var = value 269 | if objet.instance_of?(SetLocalNode) 270 | setlocalnode(objet.name, objet.value, objet.is_end) do |txt| 271 | if txt.is_a?(Awesome) 272 | node(txt) 273 | elsif(txt.instance_of?(Array)) 274 | txt.each {|tx| node(tx)} 275 | else 276 | @c << txt 277 | end 278 | end 279 | end 280 | 281 | if objet.instance_of?(Nodes) 282 | nodenode(objet.nodes) do |txt| 283 | if txt.is_a?(Awesome) 284 | node(txt) 285 | elsif(txt.instance_of?(Array)) 286 | txt.each {|tx| node(tx)} 287 | else 288 | @c << txt 289 | end 290 | end 291 | end 292 | 293 | if objet.instance_of?(LiteralNode) 294 | literalnode(objet.value) do |txt| 295 | if txt.is_a?(Awesome) 296 | node(txt) 297 | elsif(txt.instance_of?(Array)) 298 | txt.each {|tx| node(tx)} 299 | else 300 | @c << txt.to_s 301 | end 302 | end 303 | end 304 | 305 | if objet.instance_of?(String) 306 | @c << '"' + objet + '"' 307 | end 308 | end 309 | 310 | 311 | class AwesomePHP 312 | attr_accessor :c 313 | def initialize(inputfile, outputfile=false, isstring=false) 314 | @input = inputfile 315 | @output = outputfile 316 | @isstring = isstring 317 | 318 | if @isstring==true && @output==false 319 | @c = '' 320 | # input 321 | code = @input 322 | 323 | p objects = Parser.new.parse(code) 324 | 325 | # output 326 | returned = '' 327 | if objects.instance_of?(Nodes) 328 | objarray = objects.nodes 329 | objarray.each do |object| 330 | returned << node(object) unless node(object).nil? 331 | end 332 | end 333 | return @c 334 | else 335 | # input 336 | @c = '' 337 | code = '' 338 | File.open(@input, 'r') do |file| 339 | while line = file.gets 340 | code << line unless line.nil? 341 | end 342 | end 343 | 344 | p objects = Parser.new.parse(code) unless code.nil? 345 | 346 | # output 347 | @c << " 374 | # ]> 375 | # ]> 376 | # ]>>, # 382 | # ]>> 383 | # ]>, 384 | # #, @arguments=[ 387 | # # 388 | # ]> 389 | # ]> 390 | # ]>> 391 | # ]>, 392 | # # 395 | # ]> 396 | # ]>> 397 | # ]> 398 | # ]>>, 399 | # #, @name="poulet">, 400 | # # 402 | # ]>, @body=#, @arguments=[ 404 | # #, 405 | # # 406 | # ]>, @name="aw">, 407 | # #, @arguments=[]>, @arguments=[]>, @name="awe">, 408 | # # 410 | # ]> 411 | # ]>, @condition=#> 412 | # ]> 413 | #]> 414 | 415 | -------------------------------------------------------------------------------- /runtime.rb: -------------------------------------------------------------------------------- 1 | # Represents an Awesome object instance in the Ruby world. 2 | class AwesomeObject 3 | attr_accessor :awesome_class, :ruby_value 4 | 5 | # Each object have a class (named awesome_class to prevent 6 | # errors with Ruby, because Ruby objects already have a class 7 | # method). Optionaly an object can hold a Ruby value. 8 | def initialize(awesome_class, ruby_value=self) 9 | @awesome_class = awesome_class 10 | @ruby_value = ruby_value 11 | end 12 | 13 | # Call a method on the object. 14 | def call(method, arguments) 15 | # Like a typical class base runtime model, we store 16 | # methods in the class of the object. 17 | @awesome_class.lookup(method).call(self, arguments) 18 | end 19 | end 20 | 21 | # Represents a Awesome class in the Ruby world. 22 | # Classes are objects in Awesome so they inherit from AwesomeObject. 23 | class AwesomeClass < AwesomeObject 24 | attr_reader :awesome_methods 25 | 26 | # Creates a new class. Number is an instance of Class for example. 27 | def initialize 28 | @awesome_methods = {} 29 | 30 | # Check if we're bootstraping. During this process 31 | # the runtime is not fully initialized and core classes 32 | # do not yet exists, so we defer setting the object class 33 | # once this is all done. 34 | # This solves the chiken and egg problem with the Class class. 35 | # We can initialize Class then set Class.class = Class. 36 | if defined?(Runtime) 37 | awesome_class = Runtime["Class"] 38 | else 39 | awesome_class = nil 40 | end 41 | 42 | super(awesome_class) 43 | end 44 | 45 | # Lookup a method 46 | def lookup(method_name) 47 | method = @awesome_methods[method_name] 48 | unless method 49 | # Here's where we'd implement logic to lookup the method 50 | # in super class to implement inheritance. 51 | raise "Method not found: #{method_name}" 52 | end 53 | method 54 | end 55 | 56 | # Create a new instance of this Awesome class 57 | def new 58 | AwesomeObject.new(self) 59 | end 60 | 61 | # Create an instance of this Awesome class that holds 62 | # a Ruby value. Like a String, Number or true for example. 63 | def new_value(value) 64 | AwesomeObject.new(self, value) 65 | end 66 | end 67 | 68 | # A method defined in the Awesome world. 69 | # We can use Ruby's Proc to define a method in Ruby world. 70 | # We'll learn more about this at the end of this file, during 71 | # the boostraping process. 72 | class AwesomeMethod 73 | def initialize(params, body) 74 | @params = params 75 | @body = body 76 | end 77 | 78 | def call(receiver, arguments) 79 | @body.eval(Context.new(receiver)) 80 | end 81 | end 82 | 83 | # Evaluation context tracking values that change depending on 84 | # where the code is evaluated. 85 | # "locals" holds local variables. 86 | # "current_self" is the object on which methods with no receivers 87 | # are called, eg.: print is like current_self.print 88 | # "current_class" is the class on which methods are defined with 89 | # the "def" keyword. 90 | class Context 91 | attr_reader :locals, :current_self, :current_class 92 | 93 | # We store constants as class variable since they are globally accessible. 94 | # If you want to implement so kind of namespacing, you could store it 95 | # in the instance of this class. 96 | @@constants = {} 97 | 98 | def initialize(current_self, current_class=current_self.awesome_class) 99 | @locals = {} 100 | @current_self = current_self 101 | @current_class = current_class 102 | end 103 | 104 | # Shortcuts to access constants, Runtime[...] instead of Runtime.constants[...] 105 | def [](name) 106 | @@constants[name] 107 | end 108 | def []=(name, value) 109 | @@constants[name] = value 110 | end 111 | end 112 | 113 | # Bootstrap the runtime 114 | awesome_class = AwesomeClass.new # Class is a class 115 | awesome_class.awesome_class = awesome_class # Class.class == Class 116 | awesome_object_class = AwesomeClass.new # Object = Class.new 117 | 118 | # Create the Runtime object (the root context) on which all code will 119 | # start its evaluation. 120 | Runtime = Context.new(awesome_object_class.new) 121 | 122 | # Register the core classes as constants in the runtime. 123 | Runtime["Class"] = awesome_class 124 | Runtime["Object"] = awesome_object_class 125 | Runtime["Number"] = AwesomeClass.new 126 | Runtime["String"] = AwesomeClass.new 127 | Runtime["TrueClass"] = AwesomeClass.new 128 | Runtime["FalseClass"] = AwesomeClass.new 129 | Runtime["NilClass"] = AwesomeClass.new 130 | 131 | # Register primitives that map to Ruby values 132 | Runtime["true"] = Runtime["TrueClass"].new_value(true) 133 | Runtime["false"] = Runtime["FalseClass"].new_value(false) 134 | Runtime["nil"] = Runtime["NilClass"].new_value(nil) 135 | 136 | # Define some Awesome methods in Ruby. We can use a proc since they 137 | # respond to "call". 138 | Runtime["Class"].awesome_methods["new"] = proc do |receiver, arguments| 139 | # Creates a new instance of the class 140 | receiver.new 141 | end 142 | 143 | Runtime["Object"].awesome_methods["print"] = proc do |receiver, arguments| 144 | #puts arguments.first.ruby_value 145 | Runtime["nil"] 146 | end 147 | -------------------------------------------------------------------------------- /tests.rb: -------------------------------------------------------------------------------- 1 | require 'phlower.rb' 2 | require "test/unit" 3 | 4 | class TestAwesomePHP < Test::Unit::TestCase 5 | 6 | def test_singleline 7 | # print : 8 | assert_equal("$ho->print(\"joie\");\n", AwesomePHP.new("ho.print(\"joie\");\n", false, true).c ) 9 | assert_equal("$ho->print(\"joie\")", AwesomePHP.new("ho.print(\"joie\")", false, true).c ) 10 | assert_equal("print(\"joie\");\n", AwesomePHP.new("print(\"joie\");\n", false, true).c ) 11 | assert_equal("print(\"joie\")", AwesomePHP.new("print(\"joie\")", false, true).c ) 12 | assert_equal("print($aw);\n", AwesomePHP.new("print(aw);\n", false, true).c ) 13 | assert_equal("print($aw)", AwesomePHP.new("print(aw)", false, true).c ) 14 | # method access : 15 | assert_equal("$awe = $aw->x();\n", AwesomePHP.new("awe = aw.x();\n", false, true).c ) 16 | assert_equal("$awe = $aw->x()", AwesomePHP.new("awe = aw.x()", false, true).c ) 17 | assert_equal("$awe = $aw->init()->x()->z();\n", AwesomePHP.new("awe = aw.init().x().z();\n", false, true).c ) 18 | assert_equal("$awe = $aw->init()->x()->z()", AwesomePHP.new("awe = aw.init().x().z()", false, true).c ) 19 | # array : 20 | assert_equal("$arr = array();\n", AwesomePHP.new("arr = [];\n", false, true).c ) 21 | assert_equal("$arr = array()", AwesomePHP.new("arr = []", false, true).c ) 22 | # object constructor : 23 | assert_equal("$aw = new Awesome(\"brilliant!\",2);\n", AwesomePHP.new("aw = Awesome.new(\"brilliant!\",2);\n", false, true).c ) 24 | assert_equal("$aw = new Awesome(\"brilliant!\",2)", AwesomePHP.new("aw = Awesome.new(\"brilliant!\",2)", false, true).c ) 25 | # variables : 26 | assert_equal("var $poulet = \"joie\";\n", AwesomePHP.new("@poulet = \"joie\";\n", false, true).c ) 27 | assert_equal("$this->poulet;\n", AwesomePHP.new("this.poulet;\n", false, true).c ) 28 | end 29 | 30 | def test_multilignes 31 | # IF : 32 | assert_equal("if ($aw->joie()) {\nprint();\n}\n", AwesomePHP.new("if aw.joie(){\n print();}\n", false, true).c ) 33 | assert_equal("if ($aw->joie()) {\nprint()}\n", AwesomePHP.new("if aw.joie(){\n print()}", false, true).c ) 34 | # function : 35 | assert_equal("function __construct(){\npass();\n}\n\n", AwesomePHP.new("def init(){ pass();}", false, true).c ) 36 | assert_equal("function __construct(){\npass()}\n\n", AwesomePHP.new("def init(){\n pass()}", false, true).c ) 37 | # IF...ELSE : 38 | assert_equal("if ($poulet->to_i()) {\nprint($awe);\n} else {\nweird();\n}\n", AwesomePHP.new("if poulet.to_i(){\n print(awe);}else{\n weird();}", false, true).c ) 39 | assert_equal("if ($volaille->to_i()) {\nprint($awe);\n} else {\nweird();\n}\n", AwesomePHP.new("if volaille.to_i(){\n print(awe);}else{\n weird();}", false, true).c ) 40 | assert_equal("if ($dindon->to_a()) { 41 | $aw = new Awesome(\"brilliant!\",2); 42 | } else { 43 | weird(); 44 | } 45 | ", AwesomePHP.new("if dindon.to_a(){ 46 | aw = Awesome.new(\"brilliant!\",2); 47 | awe = aw.init().x().z(); 48 | }else{ 49 | weird(); 50 | }", false, true).c ) 51 | end 52 | 53 | end 54 | --------------------------------------------------------------------------------