├── .gitignore ├── README.md ├── build.lua ├── demo.lua ├── demo.sol ├── headers ├── ast.sol ├── edit_distance.sol ├── lexer.sol ├── lua_intrinsics.sol ├── output.sol ├── parser.sol ├── scope.sol ├── sol_debug.sol ├── type.sol ├── type_check.sol └── util.sol ├── install ├── ProFi.lua ├── ast.lua ├── class.lua ├── debugger.lua ├── edit_distance.lua ├── globals.lua ├── lexer.lua ├── lua_intrinsics.lua ├── output.lua ├── parser.lua ├── scope.lua ├── sol.lua ├── sol_debug.lua ├── solc.lua ├── strict.lua ├── type.lua ├── type_check.lua └── util.lua ├── install_rocks.sh ├── multiple_throwaway.lua ├── profile.lua ├── run_tests.lua ├── sol.sublime-project ├── sol ├── ast.sol ├── class.sol ├── edit_distance.sol ├── globals.sol ├── lexer.sol ├── lua_intrinsics.sol ├── output.sol ├── parser.sol ├── scope.sol ├── sol.sol ├── sol_debug.sol ├── solc.sol ├── type.sol ├── type_check.sol └── util.sol ├── sublime_plugin └── Sol │ ├── Main.sublime-menu │ ├── README.md │ ├── Sol.JSON-tmLanguage │ ├── Sol.sublime-build │ ├── Sol.sublime-settings │ ├── Sol.tmLanguage │ ├── package-metadata.json │ ├── parse_sol.py │ ├── sol_errors.py │ └── solc-solc.sublime-build ├── test.lua ├── test.sol └── tests ├── _README.txt ├── __call.sol ├── __index.sol ├── __index_2.sol ├── _lengthy.sol ├── arg_count.sol ├── arg_count_FAIL.sol ├── arg_type.sol ├── arg_type_FAIL.sol ├── bool.sol ├── build_demo.sh ├── class.sol ├── compare_eq_FAIL.sol ├── constant_1_FAIL.sol ├── constant_2_FAIL.sol ├── constant_3_FAIL.sol ├── constant_4_FAIL.sol ├── constant_5.sol ├── constant_6.sol ├── duplicate_obj_members_FAIL.sol ├── empty_list.sol ├── enum.sol ├── enum_FAIL.sol ├── enum_arg.sol ├── enum_arg_FAIL.sol ├── enum_var_FAIL.sol ├── euler_1.sol ├── euler_2.sol ├── functions.sol ├── fwd_declare.sol ├── fwd_declare_1_FAIL.sol ├── fwd_declare_2_FAIL.sol ├── fwd_declare_3_FAIL.sol ├── generators_ipairs_map_FAIL.sol ├── generators_ipairs_obj_FAIL.sol ├── generators_pairs_FAIL.sol ├── global_FAIL.sol ├── global_function.sol ├── global_not_top_level_FAIL.sol ├── if_nilable.sol ├── if_non_nilable_FAIL.sol ├── inherit.sol ├── inherit_FAIL.sol ├── int_literal_FAIL.sol ├── literals.sol ├── local_type_single_FAIL.sol ├── local_with_types.sol ├── member.sol ├── metatable.sol ├── missing_definition_FAIL.sol ├── multiple_throwaway.sol ├── obj.sol ├── obj_FAIL.sol ├── obj_extended.sol ├── obj_medium.sol ├── pack.sol ├── pack_FAIL.sol ├── recursion.sol ├── ret_type.sol ├── ret_type_FAIL.sol ├── scope_else_FAIL.sol ├── scope_elseif_FAIL.sol ├── scope_if_FAIL.sol ├── scope_repeat_FAIL.sol ├── scope_while_FAIL.sol ├── self_FAIL.sol ├── self_ref_declare_FAIL.sol ├── set.sol ├── shadowing_FAIL.sol ├── singleton.sol ├── type_comp_FAIL.sol ├── type_export_1.sol ├── type_function.sol ├── type_function_FAIL.sol ├── type_import_1.sol ├── typedef.sol ├── typedef_namespaced_bad_FAIL.sol ├── typedef_namespaced_premature_FAIL.sol ├── typedef_premature_FAIL.sol ├── underscore_read.sol ├── underscore_read_FAIL.sol ├── use_before_declare_FAIL.sol ├── var_undeducable_FAIL.sol ├── var_uninitialized_FAIL.sol ├── varargs.sol ├── varargs_FAIL.sol ├── varargs_recursion.sol └── varargs_recursion_FAIL.sol /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /solc_profile_reports/ 3 | /tests_built/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sol - Typesafe Lua 2 | ================== 3 | Static type checker and (optional) gradual typing for Lua. 4 | 5 | Sol is to Lua as Typescript is to JS. 6 | 7 | ## What? 8 | Sol is a dialect (almost super-set) of Lua which adds optional type annotations, to provide type safety through [gradual typing](https://en.wikipedia.org/wiki/Gradual_typing). 9 | Sol compiles line-for-line to Lua, and is thus compatible with existing Lua code (both ways) and tools (luajit, profilers, debuggers etc). 10 | 11 | The Sol compiler is written in Sol. 12 | 13 | 14 | ## At a glance 15 | 16 | function sqrt(x: number) -> number? 17 | return a >= 0 and math.sqrt(x) or nil 18 | end 19 | 20 | typedef Interface = { 21 | foo: int, 22 | bar: string 23 | } 24 | 25 | function use(x: Interface) 26 | print(x.bar) 27 | end 28 | 29 | use{foo: 42, bar: "fortytwo"} 30 | 31 | typedef Option = "A" or "B" or "C" 32 | var x = "B" : Option 33 | 34 | var CONSTANT = 42 35 | 36 | -- Stuff the compiler catches: 37 | x = "D" -- ERROR: "D" is not an Option 38 | X = "A" -- ERROR: 'X' is undeclared 39 | sqrt() -- ERROR: Missing non-nilable argument 'x' 40 | use{foo: "fortytwo", bar: 42} -- ERROR 41 | CONSTANT = 1337 -- ERROR: Cannot assign to constant: 'CONSTANT' (upper-case names always assumed constant) 42 | 43 | 44 | ## Why? 45 | > We need to defend ourselves from Murphy’s Million Monkeys 46 | 47 | > *- Chandler Carruth, Clang* 48 | 49 | Scripting languages like Lua has many things going for them, but they all fail to catch errors early. Lua is especially bad due to things like implicit globals and nil-defaulting. We need something better. We need to turn away from the darkness of the moon towards the light of the sun. We need Sol. 50 | 51 | That being said, dynamically typed languages provides a flexibility that a statically typed language like C++ or Java does not. Sol aims to provide the best of both worlds by the concept of *plausible typing*. 52 | 53 | Type annotations also help makes the code more readable as it provides **self-documentation**. 54 | 55 | 56 | ## State 57 | Sol is no longer in active development. 58 | 59 | 60 | ## Similar attempts 61 | There has been other attempts to bring static typing to Lua. However, they all suffer from attempting to be compatible without compilation, which means putting the type into comments or via MetaLua-syntax which makes the code ugly to the point of unintelligibleness. My experience tells me that if it ain't pretty, it ain't gonna be used. 62 | 63 | ### [Tidal Lock](https://github.com/fab13n/metalua/tree/tilo/src/tilo) 64 | 65 | Based on MetaLua. Cumbersome syntax: 66 | 67 | local n #var number = 42 68 | 69 | Compard to Sol: 70 | 71 | var n = 42 : number 72 | 73 | ### [Lua analyzer](https://bitbucket.org/kevinclancy/love-studio/wiki/OptionalTypeSystem) 74 | 75 | Extremely ugly syntax with type-annotations in comments. A non-starter. 76 | 77 | 78 | ## Credit 79 | Parser based on [LuaMinify](https://github.com/stravant/LuaMinify). 80 | -------------------------------------------------------------------------------- /build.lua: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/lua 2 | 3 | io.stdout:setvbuf 'no' 4 | local lfs = require 'lfs' 5 | 6 | local interpreter = ('"'..arg[-1]..'"' or 'lua') 7 | local PLATFORM = os.getenv("windir") and "win" or "unix" 8 | 9 | function os_execute(cmd) 10 | local result = os.execute(cmd) 11 | return result == true or result == 0 12 | end 13 | 14 | function file_exists(path) 15 | local f = io.open(path, "rb") 16 | if f then 17 | f:close() 18 | return true 19 | else 20 | return false 21 | end 22 | end 23 | 24 | function write_protect(path) 25 | if PLATFORM == "unix" then 26 | return os_execute("chmod -w " .. path) 27 | else 28 | return os_execute("attrib +R " .. path) 29 | end 30 | end 31 | 32 | function write_unprotect(path) 33 | if file_exists(path) then 34 | if PLATFORM == "unix" then 35 | return os_execute("chmod +w " .. path) 36 | else 37 | return os_execute("attrib -R " .. path) 38 | end 39 | end 40 | end 41 | 42 | 43 | local function run_cmd(cmd) 44 | print(string.format("Executing '%s'...", cmd)) 45 | if not os_execute(cmd) then 46 | print(string.format("Command '%s' failed", cmd)) 47 | os.exit(1) 48 | end 49 | end 50 | 51 | local function run_lua(cmd) 52 | return run_cmd(interpreter..' '..cmd) 53 | end 54 | 55 | -- From http://kracekumar.com/post/53685731325/cp-command-implementation-and-benchmark-in-python-go 56 | function cp(source, dest) 57 | print(string.format("Copying files from '%s' to '%s'", source, dest)) 58 | for filename in lfs.dir(source) do 59 | if filename ~= '.' and filename ~= '..' then 60 | local source_path = source .. '/' .. filename 61 | local attr = lfs.attributes(source_path) 62 | --print(attr.mode, path) 63 | if type(attr) == "table" and attr.mode == "directory" then 64 | local dest_path = dest .. "/" .. filename 65 | lfs.mkdir(dest_path) 66 | cp(source_path, dest_path) 67 | else 68 | local f = io.open(source_path, "rb") 69 | local content = f:read("*all") 70 | f:close() 71 | 72 | local out_path = dest .. "/" .. filename 73 | write_unprotect(out_path) 74 | local w = io.open(out_path, "wb") 75 | if not w then 76 | error("Failed to write to " .. out_path) 77 | end 78 | w:write(content) 79 | w:close() 80 | write_protect(out_path) -- Ensure the user doesn't accidently modify a .lua file instead of a .sol file! 81 | end 82 | end 83 | end 84 | end 85 | 86 | 87 | -- Allow user to pass in things like -s (spam) and -d (debug), -e0 (force build) 88 | local solc_args = '--ugly --align-lines' 89 | for _,a in ipairs(arg) do 90 | solc_args = solc_args .. ' ' .. a 91 | end 92 | 93 | 94 | print "----------------------------------------" 95 | print "BUILD 1/3: old solc compiling new solc" 96 | print "----------------------------------------" 97 | run_lua( "install/solc.lua "..solc_args.." -o build -ho headers sol/*.sol" ) 98 | 99 | print "----------------------------------------" 100 | print "BUILD 2/3: new solc compiling new solc" 101 | print "----------------------------------------" 102 | run_lua( "build/solc.lua " ..solc_args.." -o build sol/*.sol" ) 103 | 104 | print "----------------------------------------" 105 | print "BUILD 3/3: last santiy check" 106 | print "----------------------------------------" 107 | --run_lua "build/solc.lua -o build sol/*.sol" 108 | run_lua( "build/solc.lua " ..solc_args.." -o build sol/*.sol" ) 109 | 110 | --run_cmd "cp build/* install/" -- no cp in windows 111 | cp("build", "install") 112 | print "----------------------------------------" 113 | print "Build successed, copied to install/" 114 | 115 | --[- 116 | print "----------------------------------------" 117 | print " Running tests..." 118 | print "" 119 | run_lua "run_tests.lua" 120 | --]] 121 | -------------------------------------------------------------------------------- /demo.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM demo.sol --]] ----------------------------------------------------- 2 | --[[ 3 | # SOL IS: 4 | * Lua + static typing 5 | * Flexible, plausible typing 6 | 7 | 8 | # SOL IS NOT: 9 | * Waterproof 10 | * More than Lua (yet!) 11 | 12 | --]] 13 | 14 | 15 | 16 | ----------------------------------------------------- 17 | -- Undefined globals 18 | 19 | x = 1337 20 | local a = x 21 | 22 | 23 | 24 | ----------------------------------------------------- 25 | -- Argument checking 26 | 27 | local Foo = {} 28 | 29 | --Foo.member() 30 | Foo:member() 31 | Foo.member(Foo) 32 | --Foo:member(Foo) 33 | 34 | -- Note - no forward-declare needed! 35 | function Foo:member() 36 | --self.member() 37 | self:member() 38 | self.member(self) 39 | --self:member(self) 40 | end 41 | 42 | 43 | 44 | ----------------------------------------------------- 45 | -- pairs/ipairs 46 | 47 | do 48 | local list = { "one", "two", "three" } 49 | for i,v in ipairs(list) do 50 | print(i .. ': ' .. v) 51 | end 52 | 53 | local map = { ['one'] = 1, ['two'] = 2, ['three'] = 3 } 54 | for k,v in pairs(map) do 55 | k = k .. '42' 56 | v = v + 42 57 | end 58 | end 59 | 60 | 61 | 62 | ----------------------------------------------------- 63 | -- Add a type annotation to the function 64 | 65 | local function int_to_string(arg) 66 | return '' .. arg 67 | end 68 | 69 | local function string_to_int(arg) 70 | if arg then 71 | return tonumber(arg) 72 | else 73 | return 0 74 | end 75 | end 76 | 77 | local i = string_to_int("42") 78 | i = i + 42 79 | 80 | 81 | local function cmp(a, b) 82 | --return a == b 83 | --return true 84 | return nil 85 | end 86 | 87 | 88 | 89 | ----------------------------------------------------- 90 | -- What is the return type of this? 91 | local function win_or_fail() 92 | if math.random() < 0.5 then 93 | return true, nil 94 | else 95 | return false, "bad luck" 96 | end 97 | end 98 | 99 | local win, err_msg = win_or_fail() 100 | 101 | 102 | 103 | 104 | 105 | ----------------------------------------------------- 106 | -- var vs local 107 | 108 | local local_can_be_anything = require 'unfindable' 109 | --var var_must_be_deducible = some_lua_function() 110 | local var_can_be_explicit = require 'unfindable' 111 | 112 | 113 | 114 | ----------------------------------------------------- 115 | -- Maps and lists: 116 | 117 | do 118 | local list = {1, 2, 3} 119 | list[1] = 42 120 | local map 121 | 122 | 123 | = {} 124 | map[1] = 'one' 125 | map[2] = 'two' 126 | --map['three'] = 3 127 | end 128 | 129 | local ok 130 | 131 | 132 | 133 | 134 | 135 | 136 | = 'yes' 137 | --var bad = 'wrong' : Tribool 138 | 139 | local function do_stuff(how) 140 | 141 | end 142 | 143 | do_stuff("quickly") 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | local function filter_number(fp, doWhat) 153 | for i=1,42 do 154 | if fp(i) then 155 | doWhat(i) 156 | end 157 | end 158 | end 159 | 160 | filter_number( 161 | function(i) return i%2==1 end, 162 | function(i) print(i .. " is odd") end 163 | ) 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | local function work_on_node(n) 186 | if n.tag == 'Foo' then 187 | local f = n 188 | end 189 | 190 | local a = n.wild 191 | end 192 | 193 | 194 | work_on_node({ 195 | tag = 'Foo'; 196 | korv = 32 197 | }) 198 | 199 | --[[ 200 | work_on_node({ 201 | tag = 'Baz' 202 | }) 203 | --]] 204 | -------------------------------------------------------------------------------- /demo.sol: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------- 2 | --[[ 3 | # SOL IS: 4 | * Lua + static typing 5 | * Flexible, plausible typing 6 | 7 | 8 | # SOL IS NOT: 9 | * Waterproof 10 | * More than Lua (yet!) 11 | 12 | --]] 13 | 14 | 15 | 16 | ----------------------------------------------------- 17 | -- Undefined globals 18 | 19 | --global x = 1337 20 | local a = x 21 | 22 | 23 | 24 | ----------------------------------------------------- 25 | -- Argument checking 26 | 27 | local Foo = {} 28 | 29 | Foo.member() 30 | Foo:member() 31 | Foo.member(Foo) 32 | Foo:member(Foo) 33 | 34 | -- Note - no forward-declare needed! 35 | function Foo:member() 36 | self.member() 37 | self:member() 38 | self.member(self) 39 | self:member(self) 40 | end 41 | 42 | 43 | 44 | 45 | ----------------------------------------------------- 46 | -- pairs/ipairs 47 | 48 | do 49 | local list = { "one", "two", "three" } 50 | for i,v in pairs(list) do 51 | print(i .. ': ' .. v) 52 | end 53 | 54 | local map = { ['one'] = 1, ['two'] = 2, ['three'] = 3 } 55 | for k,v in ipairs(map) do 56 | k = k + '42' 57 | v = v + 42 58 | end 59 | end 60 | 61 | 62 | 63 | ----------------------------------------------------- 64 | -- Add a type annotation to the function 65 | 66 | local function int_to_string(arg) 67 | return '' .. arg 68 | end 69 | 70 | local function string_to_int(arg) 71 | if arg then 72 | return tonumber(arg) 73 | else 74 | return 0 75 | end 76 | end 77 | 78 | var i = int_to_string("42") 79 | i = i + 42 80 | 81 | 82 | local function cmp(a: int?, b: string?) -> bool? 83 | return a == b 84 | --return true 85 | --return nil 86 | end 87 | 88 | 89 | 90 | typedef fmt_string = string -- TODO 91 | 92 | local function foo(fmt: fmt_string, ...) 93 | end 94 | 95 | foo("hello", 1, 2, 3, 3.14) 96 | 97 | 98 | 99 | 100 | ----------------------------------------------------- 101 | -- What is the return type of this? 102 | local function win_or_fail() 103 | if math.random() < 0.5 then 104 | return true 105 | else 106 | return false, "bad luck" 107 | end 108 | end 109 | 110 | local win, err_msg, too_many = win_or_fail() 111 | 112 | 113 | 114 | 115 | 116 | ----------------------------------------------------- 117 | -- var vs local 118 | 119 | local local_can_be_anything = require 'unfindable' 120 | var var_must_be_deducible = some_lua_function() 121 | var var_can_be_explicit = (require 'unfindable') : int 122 | 123 | 124 | 125 | ----------------------------------------------------- 126 | -- Maps and lists: 127 | 128 | do 129 | var<[int]> list = {1, 2, 3} 130 | list[1] = 42 131 | list[2] = '1337' 132 | 133 | typedef Int2str = {int => string} 134 | var map = {} : Int2str 135 | map[1] = 'one' 136 | map[2] = 'two' 137 | map['three'] = 3 138 | end 139 | 140 | 141 | 142 | ----------------------------------------------------- 143 | -- Enums 144 | 145 | typedef Tribool = 'yes' or 'no' or 'maybe' 146 | 147 | var ok = 'yes' : Tribool 148 | var bad = 'wrong' : Tribool 149 | 150 | local function do_stuff(how: "quickly" or "slowly") 151 | 152 | end 153 | 154 | do_stuff("quickly") 155 | do_stuff("sakta") 156 | 157 | 158 | ----------------------------------------------------- 159 | -- Function pointers 160 | 161 | typedef IntFilter = function(int) -> bool 162 | 163 | local function filter_number(fp: IntFilter, doWhat: function(int)) 164 | for i=1,42 do 165 | if fp(i) then 166 | doWhat(i) 167 | end 168 | end 169 | end 170 | 171 | filter_number( 172 | function(i: int) return i%2==1 end, 173 | function(i: int) print(i .. " is odd") end 174 | ) 175 | 176 | 177 | ----------------------------------------------------- 178 | -- Objects and inheritence 179 | typedef Node = { 180 | tag: 'Foo' or 'Bar'; 181 | korv: int; 182 | } 183 | 184 | typedef FooNode : Node = { 185 | tag: 'Foo'; 186 | int: int; 187 | wild: int; 188 | } 189 | 190 | typedef BarNode : Node = { 191 | tag: 'Bar'; 192 | string: string; 193 | wild: string; 194 | } 195 | 196 | local function work_on_node(n: Node) 197 | if n.tag == 'Foo' then 198 | var f = n : FooNode 199 | end 200 | 201 | var<[string]> a = n.wild 202 | end 203 | 204 | work_on_node({ 205 | tag = 'Foo'; 206 | korv = 32 207 | }) 208 | 209 | 210 | work_on_node({ 211 | tag = 'Baz' 212 | }) 213 | 214 | -------------------------------------------------------------------------------- /headers/ast.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/ast.sol 2 | 3 | return { eq: function(a: Node, b: Node) -> bool; } -------------------------------------------------------------------------------- /headers/edit_distance.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/edit_distance.sol 2 | 3 | return function(s: string or [int], t: string or [int], lim: int?) -> int -------------------------------------------------------------------------------- /headers/lexer.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/lexer.sol 2 | 3 | return { 4 | -- Types: 5 | typedef Token = Token; 6 | typedef TokenList = [Token]; 7 | 8 | -- Members: 9 | lex_sol: function(src: string, filename: string, settings) -> bool, any; 10 | print_stats: function() -> void; 11 | } -------------------------------------------------------------------------------- /headers/lua_intrinsics.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/lua_intrinsics.sol 2 | 3 | return { add_intrinsics_to_global_scope: function() -> void; } -------------------------------------------------------------------------------- /headers/output.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/output.sol 2 | 3 | return function(ast, filename: string, strip_white_space: bool?) -> string -------------------------------------------------------------------------------- /headers/parser.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/parser.sol 2 | 3 | return { 4 | -- Types: 5 | typedef AssignmentStatement = { 6 | ast_type: 'AssignmentStatement'; 7 | lhs: [ExprNode]; 8 | rhs: [ExprNode]; 9 | scope: Scope?; 10 | semicolon: bool?; 11 | tokens: [Token]; 12 | where: string; 13 | }; 14 | typedef BinopExpr = { 15 | ast_type: 'BinopExpr'; 16 | lhs: ExprNode; 17 | op: string; 18 | rhs: ExprNode; 19 | tokens: [Token]; 20 | where: string; 21 | }; 22 | typedef BooleanExpr = { 23 | ast_type: 'BooleanExpr'; 24 | tokens: [Token]; 25 | value: bool; 26 | where: string; 27 | }; 28 | typedef BreakStatement = { 29 | ast_type: 'BreakStatement'; 30 | scope: Scope?; 31 | semicolon: bool?; 32 | tokens: [Token]; 33 | where: string; 34 | }; 35 | typedef CallExpr = { 36 | arguments: [ExprNode]; 37 | ast_type: 'CallExpr'; 38 | base: ExprNode; 39 | tokens: [Token]; 40 | where: string; 41 | }; 42 | typedef CallStatement = { 43 | ast_type: 'CallStatement'; 44 | expression: ExprNode; 45 | scope: Scope?; 46 | semicolon: bool?; 47 | tokens: [Token]; 48 | where: string; 49 | }; 50 | typedef CastExpr = { 51 | ast_type: 'CastExpr'; 52 | expr: ExprNode; 53 | tokens: [Token]; 54 | type: Type; 55 | where: string; 56 | }; 57 | typedef ClassDeclStatement = { 58 | ast_type: 'ClassDeclStatement'; 59 | is_local: bool; 60 | name: string; 61 | rhs: ExprNode; 62 | scope: Scope?; 63 | semicolon: bool?; 64 | tokens: [Token]; 65 | where: string; 66 | }; 67 | typedef ConstructorExpr = { 68 | ast_type: 'ConstructorExpr'; 69 | entry_list: [ConstructorExprEntry]; 70 | tokens: [Token]; 71 | where: string; 72 | }; 73 | typedef DoStatement = { 74 | ast_type: 'DoStatement'; 75 | body: Statlist; 76 | scope: Scope?; 77 | semicolon: bool?; 78 | tokens: [Token]; 79 | where: string; 80 | }; 81 | typedef DotsExpr = { 82 | ast_type: 'DotsExpr'; 83 | tokens: [Token]; 84 | where: string; 85 | }; 86 | typedef Eof = { 87 | ast_type: 'Eof'; 88 | scope: Scope?; 89 | semicolon: bool?; 90 | tokens: [Token]; 91 | where: string; 92 | }; 93 | typedef ExprNode = { 94 | ast_type: ExprType; 95 | tokens: [Token]; 96 | where: string; 97 | }; 98 | typedef ExprType = 'IdExpr' or 'NumberExpr' or 'StringExpr' or 'BooleanExpr' or 'NilExpr' or 'ExternExpr' or 'BinopExpr' or 'UnopExpr' or 'DotsExpr' or 'CallExpr' or 'TableCallExpr' or 'StringCallExpr' or 'IndexExpr' or 'MemberExpr' or 'LambdaFunctionExpr' or 'ConstructorExpr' or 'ParenthesesExpr' or 'CastExpr'; 99 | typedef ExternExpr = { 100 | ast_type: 'ExternExpr'; 101 | tokens: [Token]; 102 | where: string; 103 | }; 104 | typedef FunctionDeclStatement = { 105 | arguments: [{ name: string; type: Type?; }]; 106 | ast_type: 'FunctionDeclStatement'; 107 | body: Statlist; 108 | is_aggregate: bool; 109 | is_mem_fun: bool; 110 | name_expr: ExprNode; 111 | scope: Scope?; 112 | scoping: 'local' or 'global' or ''; 113 | self_var_type: Type?; 114 | semicolon: bool?; 115 | tokens: [Token]; 116 | vararg: VarArgs?; 117 | where: string; 118 | }; 119 | typedef GenericForStatement = { 120 | ast_type: 'GenericForStatement'; 121 | body: Statlist; 122 | generators: [ExprNode]; 123 | scope: Scope?; 124 | semicolon: bool?; 125 | tokens: [Token]; 126 | var_names: [string]; 127 | where: string; 128 | }; 129 | typedef GotoStatement = { 130 | ast_type: 'GotoStatement'; 131 | label: string; 132 | scope: Scope?; 133 | semicolon: bool?; 134 | tokens: [Token]; 135 | where: string; 136 | }; 137 | typedef IdExpr = { 138 | ast_type: 'IdExpr'; 139 | name: string; 140 | tokens: [Token]; 141 | variable: Variable; 142 | where: string; 143 | }; 144 | typedef IfStatement = { 145 | ast_type: 'IfStatement'; 146 | clauses: [IfStatementClause]; 147 | scope: Scope?; 148 | semicolon: bool?; 149 | tokens: [Token]; 150 | where: string; 151 | }; 152 | typedef IndexExpr = { 153 | ast_type: 'IndexExpr'; 154 | base: ExprNode; 155 | index: ExprNode; 156 | tokens: [Token]; 157 | where: string; 158 | }; 159 | typedef LabelStatement = { 160 | ast_type: 'LabelStatement'; 161 | label: string; 162 | scope: Scope?; 163 | semicolon: bool?; 164 | tokens: [Token]; 165 | where: string; 166 | }; 167 | typedef LambdaFunctionExpr = { 168 | arguments: [{ name: string; type: Type?; }]; 169 | ast_type: 'LambdaFunctionExpr'; 170 | body: Statlist?; 171 | is_mem_fun: bool; 172 | return_types: [Type]?; 173 | tokens: [Token]; 174 | vararg: VarArgs?; 175 | where: string; 176 | }; 177 | typedef MemberExpr = { 178 | ast_type: 'MemberExpr'; 179 | base: ExprNode; 180 | ident: Token; 181 | indexer: string; 182 | tokens: [Token]; 183 | where: string; 184 | }; 185 | typedef NilExpr = { 186 | ast_type: 'NilExpr'; 187 | tokens: [Token]; 188 | where: string; 189 | }; 190 | typedef Node = { 191 | ast_type: NodeType; 192 | tokens: [Token]; 193 | where: string; 194 | }; 195 | typedef NodeType = ExprType or StatType or 'Statlist'; 196 | typedef NumberExpr = { 197 | ast_type: 'NumberExpr'; 198 | tokens: [Token]; 199 | value: string; 200 | where: string; 201 | }; 202 | typedef NumericForStatement = { 203 | ast_type: 'NumericForStatement'; 204 | body: Statlist; 205 | end_: ExprNode; 206 | scope: Scope?; 207 | semicolon: bool?; 208 | start: ExprNode; 209 | step: ExprNode?; 210 | tokens: [Token]; 211 | var_name: string; 212 | where: string; 213 | }; 214 | typedef ParenthesesExpr = { 215 | ast_type: 'ParenthesesExpr'; 216 | inner: ExprNode; 217 | tokens: [Token]; 218 | where: string; 219 | }; 220 | typedef RepeatStatement = { 221 | ast_type: 'RepeatStatement'; 222 | body: Statlist; 223 | condition: ExprNode; 224 | scope: Scope?; 225 | semicolon: bool?; 226 | tokens: [Token]; 227 | where: string; 228 | }; 229 | typedef ReturnStatement = { 230 | arguments: [ExprNode]; 231 | ast_type: 'ReturnStatement'; 232 | scope: Scope?; 233 | semicolon: bool?; 234 | tokens: [Token]; 235 | where: string; 236 | }; 237 | typedef StatNode = { 238 | ast_type: StatType; 239 | scope: Scope?; 240 | semicolon: bool?; 241 | tokens: [Token]; 242 | where: string; 243 | }; 244 | typedef StatType = 'AssignmentStatement' or 'CallStatement' or 'VarDeclareStatement' or 'IfStatement' or 'WhileStatement' or 'DoStatement' or 'RepeatStatement' or 'GenericForStatement' or 'NumericForStatement' or 'ReturnStatement' or 'BreakStatement' or 'LabelStatement' or 'GotoStatement' or 'FunctionDeclStatement' or 'Typedef' or 'ClassDeclStatement' or 'Eof'; 245 | typedef Statlist = { 246 | ast_type: 'Statlist'; 247 | body: [StatNode]; 248 | scope: Scope; 249 | tokens: [Token]; 250 | where: string; 251 | }; 252 | typedef StringCallExpr = { 253 | arguments: [StringExpr]; 254 | ast_type: 'StringCallExpr'; 255 | base: ExprNode; 256 | tokens: [Token]; 257 | where: string; 258 | }; 259 | typedef StringExpr = { 260 | ast_type: 'StringExpr'; 261 | str_contents: string; 262 | str_quoted: string; 263 | tokens: [Token]; 264 | where: string; 265 | }; 266 | typedef TableCallExpr = { 267 | arguments: [ConstructorExpr]; 268 | ast_type: 'TableCallExpr'; 269 | base: ExprNode; 270 | tokens: [Token]; 271 | where: string; 272 | }; 273 | typedef Typedef = { 274 | ast_type: 'Typedef'; 275 | base_types: [Type]?; 276 | is_local: bool; 277 | namespace_name: string?; 278 | scope: Scope?; 279 | semicolon: bool?; 280 | tokens: [Token]; 281 | type: Type?; 282 | type_name: string; 283 | where: string; 284 | }; 285 | typedef UnopExpr = { 286 | ast_type: 'UnopExpr'; 287 | op: string; 288 | rhs: ExprNode; 289 | tokens: [Token]; 290 | where: string; 291 | }; 292 | typedef VarDeclareStatement = { 293 | ast_type: 'VarDeclareStatement'; 294 | init_list: [ExprNode]; 295 | is_local: bool; 296 | name_list: [string]; 297 | scope: Scope?; 298 | scoping: 'local' or 'global' or 'var'; 299 | semicolon: bool?; 300 | tokens: [Token]; 301 | type_list: [Type]?; 302 | where: string; 303 | }; 304 | typedef WhileStatement = { 305 | ast_type: 'WhileStatement'; 306 | body: Statlist; 307 | condition: ExprNode; 308 | scope: Scope?; 309 | semicolon: bool?; 310 | tokens: [Token]; 311 | where: string; 312 | }; 313 | 314 | -- Members: 315 | LUA_SETTINGS: { 316 | function_types: false; 317 | is_sol: false; 318 | issues: {'unused-parameter' or 'unused-loop-variable' or 'unused-variable' or 'unassigned-variable' or 'nil-init' or 'nil-ends-list' or 'nil-in-list' => 'SPAM' or 'WARNING'}; 319 | keywords: {string}; 320 | symbols: {string}; 321 | }; 322 | SOL_SETTINGS: { 323 | function_types: true; 324 | is_sol: true; 325 | issues: {'unused-parameter' or 'unused-loop-variable' or 'unused-variable' or 'unassigned-variable' or 'nil-init' or 'nil-ends-list' or 'nil-in-list' or 'const-should-be-uppercase' => 'WARNING' or 'ERROR'}; 326 | keywords: {string}; 327 | symbols: {string}; 328 | }; 329 | parse_sol: function(src: string, tok, filename: string?, settings, module_scope: Scope) -> bool, Statlist or string; 330 | } -------------------------------------------------------------------------------- /headers/scope.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/scope.sol 2 | 3 | return table -------------------------------------------------------------------------------- /headers/sol_debug.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/sol_debug.sol 2 | 3 | return { 4 | activate: function() -> void; 5 | active: false; 6 | assert: function(bool_expr, fmt: string?, ... : any) -> any; 7 | break_: function() -> void; 8 | error: function(msg: string) -> void; 9 | get_lib: function() -> any; 10 | } -------------------------------------------------------------------------------- /headers/type.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/type.sol 2 | 3 | return { 4 | -- Types: 5 | typedef Any = { 6 | pre_analyzed: bool?; 7 | tag: 'any'; 8 | where: string?; 9 | }; 10 | typedef Extern = { 11 | name: string?; 12 | pre_analyzed: bool?; 13 | tag: 'extern'; 14 | where: string; 15 | }; 16 | typedef False = { 17 | pre_analyzed: bool?; 18 | tag: 'false'; 19 | where: string?; 20 | }; 21 | typedef Function = { 22 | args: [{ name: string?; type: Type?; }]; 23 | intrinsic_name: string?; 24 | name: string; 25 | pre_analyzed: bool?; 26 | rets: [Type]?; 27 | tag: 'function'; 28 | vararg: VarArgs?; 29 | where: string?; 30 | }; 31 | typedef Identifier = { 32 | first_usage: string?; 33 | name: string; 34 | pre_analyzed: bool?; 35 | scope: Scope; 36 | tag: 'identifier'; 37 | type: Type?; 38 | var_name: string?; 39 | where: string; 40 | }; 41 | typedef Int = { 42 | pre_analyzed: bool?; 43 | tag: 'int'; 44 | where: string?; 45 | }; 46 | typedef IntLiteral = { 47 | pre_analyzed: bool?; 48 | tag: 'int_literal'; 49 | value: int; 50 | where: string?; 51 | }; 52 | typedef List = { 53 | pre_analyzed: bool?; 54 | tag: 'list'; 55 | type: Type; 56 | where: string?; 57 | }; 58 | typedef Map = { 59 | key_type: Type; 60 | pre_analyzed: bool?; 61 | tag: 'map'; 62 | value_type: Type; 63 | where: string?; 64 | }; 65 | typedef Nil = { 66 | pre_analyzed: bool?; 67 | tag: 'nil'; 68 | where: string?; 69 | }; 70 | typedef Num = { 71 | pre_analyzed: bool?; 72 | tag: 'number'; 73 | where: string?; 74 | }; 75 | typedef NumLiteral = { 76 | pre_analyzed: bool?; 77 | tag: 'num_literal'; 78 | value: number; 79 | where: string?; 80 | }; 81 | typedef Object = { 82 | class_type: Object?; 83 | derived: [Identifier]?; 84 | instance_type: Object?; 85 | members: {string => Type}; 86 | metatable: Object?; 87 | namespace: {string => Identifier}?; 88 | pre_analyzed: bool?; 89 | tag: 'object'; 90 | where: string?; 91 | }; 92 | typedef String = { 93 | pre_analyzed: bool?; 94 | tag: 'string'; 95 | where: string?; 96 | }; 97 | typedef StringLiteral = { 98 | pre_analyzed: bool?; 99 | str_contents: string; 100 | str_quoted: string; 101 | tag: 'string_literal'; 102 | where: string?; 103 | }; 104 | typedef Table = { 105 | pre_analyzed: bool?; 106 | tag: 'table'; 107 | where: string?; 108 | }; 109 | typedef True = { 110 | pre_analyzed: bool?; 111 | tag: 'true'; 112 | where: string?; 113 | }; 114 | typedef Type = { 115 | pre_analyzed: bool?; 116 | tag: TypeID; 117 | where: string?; 118 | }; 119 | typedef TypeID = 'any' or 'int_literal' or 'num_literal' or 'string_literal' or 'nil' or 'true' or 'false' or 'int' or 'number' or 'string' or 'table' or 'list' or 'map' or 'object' or 'function' or 'variant' or 'identifier' or 'varargs' or 'extern'; 120 | typedef Typelist = [Type]; 121 | typedef VarArgs = { 122 | pre_analyzed: bool?; 123 | tag: 'varargs'; 124 | type: Type; 125 | where: string?; 126 | }; 127 | typedef Variant = { 128 | pre_analyzed: bool?; 129 | tag: 'variant'; 130 | variants: [Type]; 131 | where: string?; 132 | }; 133 | 134 | -- Members: 135 | Any: { tag: 'any'; }; 136 | AnyTypeList: table; 137 | Bool: { 138 | tag: 'variant'; 139 | variants: [{ tag: 'true'; } or { tag: 'false'; }]; 140 | }; 141 | False: { tag: 'false'; }; 142 | Int: { tag: 'int'; }; 143 | List: { tag: 'list'; type: { tag: 'any'; }; }; 144 | Map: { 145 | key_type: { tag: 'any'; }; 146 | tag: 'map'; 147 | value_type: { tag: 'any'; }; 148 | }; 149 | Nil: { tag: 'nil'; }; 150 | Nilable: { tag: 'any'; }; 151 | Num: { tag: 'number'; }; 152 | Object: { members: table; tag: 'object'; }; 153 | String: { tag: 'string'; }; 154 | Table: { tag: 'table'; }; 155 | True: { tag: 'true'; }; 156 | Uint: { tag: 'int'; }; 157 | Void: table; 158 | _empty_table: { tag: 'table'; }; 159 | all_variants: function(typ: Type) -> function() -> Type?; 160 | broaden: function(t: Type?) -> Type?; 161 | broaden_non_nil: function(t: Type) -> Type; 162 | clone_variant: function(v: Variant) -> Variant; 163 | combine_num_int: function(a: Type, b: Type) -> Num or Int; 164 | combine_type_lists: function(a: Typelist?, b: Typelist?, forgiving: bool?) -> Typelist?; 165 | could_be: function(d: Type, b: Type, problem_rope: [string]?) -> bool; 166 | could_be_raw: function(a: Type, b: Type, problem_rope: [string]?) -> bool; 167 | could_be_tl: function(al: Typelist, bl: Typelist, problem_rope: [string]?) -> bool; 168 | could_be_true_false: function(a: Type) -> bool, bool; 169 | create_empty_table: function() -> Type; 170 | extend_variant: function(v: Variant, ... : any) -> Variant; 171 | extend_variant_one: function(v: Variant, e: Type) -> Variant; 172 | find_meta_method: function(t: Type, name: string) -> Type?; 173 | follow_identifiers: function(t: Type, forgiving: bool?) -> Type; 174 | format_type: function(root: Type, verbosity: Verbosity) -> string; 175 | from_num_literal: function(str: string) -> IntLiteral or NumLiteral?; 176 | from_string_literal: function(str: string) -> StringLiteral; 177 | has_tag: function(t: Type, target: string) -> bool; 178 | is_any: function(a: Type) -> bool; 179 | is_atomic: function(t: Type) -> bool; 180 | is_bool: function(a: Type) -> bool; 181 | is_class: function(typ: Object) -> bool; 182 | is_empty_table: function(t: Type) -> bool; 183 | is_instance: function(typ: Object) -> bool; 184 | is_integral: function(str: string) -> bool; 185 | is_nilable: function(a: Type) -> bool; 186 | is_obj_obj: function(d: Object, b: Object, problem_rope: [string]?) -> bool; 187 | is_table: function(t: Type) -> bool; 188 | is_type: function(x) -> bool; 189 | is_type_list: function(list) -> bool; 190 | is_variant: function(t: Type) -> Variant?; 191 | is_void: function(ts: Typelist) -> bool; 192 | isa: function(d: Type, b: Type, problem_rope: [string]?) -> bool; 193 | isa_raw: function(d: Type, b: Type, problem_rope: [string]?) -> bool; 194 | isa_typelists: function(d: [Type]?, b: [Type]?, problem_rope: [string]?) -> bool; 195 | make_nilable: function(a: Type) -> Type; 196 | make_variant: function(... : any) -> Variant; 197 | name: function(typ: Type or [Type]?, verbosity: 'verbose' or 'concise'?) -> string; 198 | names: function(typ: [Type], verbosity: 'verbose' or 'concise'?) -> string; 199 | on_error: function(fmt, ... : any) -> void; 200 | should_extend_in_situ: function(typ: Type) -> bool; 201 | simplify: function(t: Type) -> Type; 202 | table_id: function(t: table) -> string; 203 | variant: function(a: Type?, b: Type?) -> Type?; 204 | variant_has: function(v: Variant, e: Type) -> bool; 205 | variant_remove: function(t: Type, remove_this_type: Type) -> Type; 206 | visit: function(t: Type, lambda: function(: Type) -> void) -> void; 207 | visit_and_combine: function(t: Type, lambda: function(: Type) -> Type?) -> Type?; 208 | } -------------------------------------------------------------------------------- /headers/type_check.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/type_check.sol 2 | 3 | return function(ast, filename: string, on_require: OnRequireT?, settings) -> bool, Typelist or string -------------------------------------------------------------------------------- /headers/util.sol: -------------------------------------------------------------------------------- 1 | -- Compiled from sol/util.sol 2 | 3 | return { 4 | INDENTATION: '\t'; 5 | const: function(table: table) -> object; 6 | count_line_breaks: function(str: string) -> int; 7 | ellipsis: function(msg: string, max_len: int?) -> string; 8 | escape: function(str: string) -> string; 9 | file_exists: function(path: string) -> bool; 10 | indent: function(str: string) -> string; 11 | is_array: function(val) -> bool; 12 | is_constant_name: function(name: string) -> bool; 13 | list_concat: function(a: [any], b: [any]) -> [any]; 14 | list_join: function(out: [any], in_table: [any]) -> void; 15 | make_const: function(table: table) -> void; 16 | pretty: function(arg) -> string; 17 | print_sorted_stats: function(map: {string => number}) -> void; 18 | printf: function(fmt: string, ... : any) -> void; 19 | printf_err: function(fmt: string, ... : any) -> void; 20 | quote_or_indent: function(str: string) -> string; 21 | read_entire_file: function(path: string) -> string?; 22 | read_entire_stdin: function() -> string?; 23 | serialize: function(val, ignore_set: {any}?) -> string; 24 | serialize_to_rope: function(rope: [string], val, ignore_set: {any}?, indent: string?, discovered: {table}?) -> void; 25 | set: function(tb: [string]) -> {string}; 26 | set_join: function(... : {string}) -> {string}; 27 | shallow_clone: function(t: table?) -> table?; 28 | table_clear: function(t: table) -> void; 29 | table_empty: function(t: table) -> bool; 30 | trim: function(str: string) -> string; 31 | unescape: function(str: string) -> string; 32 | write_file: function(path: string, contents: string) -> bool; 33 | write_protect: function(path: string) -> bool; 34 | write_unprotect: function(path: string) -> bool; 35 | } -------------------------------------------------------------------------------- /install/ProFi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ProFi v1.3, by Luke Perkin 2012. MIT Licence http://www.opensource.org/licenses/mit-license.php. 3 | 4 | Example: 5 | ProFi = require 'ProFi' 6 | ProFi:start() 7 | some_function() 8 | another_function() 9 | coroutine.resume( some_coroutine ) 10 | ProFi:stop() 11 | ProFi:writeReport( 'MyProfilingReport.txt' ) 12 | 13 | API: 14 | *Arguments are specified as: type/name/default. 15 | ProFi:start( string/once/nil ) 16 | ProFi:stop() 17 | ProFi:checkMemory( number/interval/0, string/note/'' ) 18 | ProFi:writeReport( string/filename/'ProFi.txt' ) 19 | ProFi:reset() 20 | ProFi:setHookCount( number/hookCount/0 ) 21 | ProFi:setGetTimeMethod( function/getTimeMethod/os.clock ) 22 | ProFi:setInspect( string/methodName, number/levels/1 ) 23 | ]] 24 | 25 | ----------------------- 26 | -- Locals: 27 | ----------------------- 28 | 29 | local ProFi = {} 30 | local onDebugHook, sortByDurationDesc, sortByCallCount, getTime 31 | local DEFAULT_DEBUG_HOOK_COUNT = 0 32 | local FORMAT_HEADER_LINE = "| %-50s: %-40s: %-20s: %-12s: %-12s: %-12s|\n" 33 | local FORMAT_OUTPUT_LINE = "| %s: %-12s: %-12s: %-12s|\n" 34 | local FORMAT_INSPECTION_LINE = "> %s: %-12s\n" 35 | local FORMAT_TOTALTIME_LINE = "| TOTAL TIME = %f\n" 36 | local FORMAT_MEMORY_LINE = "| %-20s: %-16s: %-16s| %s\n" 37 | local FORMAT_HIGH_MEMORY_LINE = "H %-20s: %-16s: %-16sH %s\n" 38 | local FORMAT_LOW_MEMORY_LINE = "L %-20s: %-16s: %-16sL %s\n" 39 | local FORMAT_TITLE = "%-50.50s: %-40.40s: %-20s" 40 | local FORMAT_LINENUM = "%4i" 41 | local FORMAT_TIME = "%04.3f" 42 | local FORMAT_RELATIVE = "%03.2f%%" 43 | local FORMAT_COUNT = "%7i" 44 | local FORMAT_KBYTES = "%7i Kbytes" 45 | local FORMAT_MBYTES = "%7.1f Mbytes" 46 | local FORMAT_MEMORY_HEADER1 = "\n=== HIGH & LOW MEMORY USAGE ===============================\n" 47 | local FORMAT_MEMORY_HEADER2 = "=== MEMORY USAGE ==========================================\n" 48 | local FORMAT_BANNER = [[ 49 | ############################################################################################################### 50 | ##### ProFi, a lua profiler. This profile was generated on: %s 51 | ##### ProFi is created by Luke Perkin 2012 under the MIT Licence, www.locofilm.co.uk 52 | ##### Version 1.3. Get the most recent version at this gist: https://gist.github.com/2838755 53 | ############################################################################################################### 54 | 55 | ]] 56 | 57 | ----------------------- 58 | -- Public Methods: 59 | ----------------------- 60 | 61 | --[[ 62 | Starts profiling any method that is called between this and ProFi:stop(). 63 | Pass the parameter 'once' to so that this methodis only run once. 64 | Example: 65 | ProFi:start( 'once' ) 66 | ]] 67 | function ProFi:start( param ) 68 | if param == 'once' then 69 | if self:shouldReturn() then 70 | return 71 | else 72 | self.should_run_once = true 73 | end 74 | end 75 | self.has_started = true 76 | self.has_finished = false 77 | self:resetReports( self.reports ) 78 | self:startHooks() 79 | self.startTime = getTime() 80 | end 81 | 82 | --[[ 83 | Stops profiling. 84 | ]] 85 | function ProFi:stop() 86 | if self:shouldReturn() then 87 | return 88 | end 89 | self.stopTime = getTime() 90 | self:stopHooks() 91 | self.has_finished = true 92 | end 93 | 94 | function ProFi:checkMemory( interval, note ) 95 | local time = getTime() 96 | interval = interval or 0 97 | if self.lastCheckMemoryTime and time < self.lastCheckMemoryTime + interval then 98 | return 99 | end 100 | self.lastCheckMemoryTime = time 101 | local memoryReport = { 102 | ['time'] = time; 103 | ['memory'] = collectgarbage('count'); 104 | ['note'] = note or ''; 105 | } 106 | table.insert( self.memoryReports, memoryReport ) 107 | self:setHighestMemoryReport( memoryReport ) 108 | self:setLowestMemoryReport( memoryReport ) 109 | end 110 | 111 | --[[ 112 | Writes the profile report to a file. 113 | Param: [filename:string:optional] defaults to 'ProFi.txt' if not specified. 114 | ]] 115 | function ProFi:writeReport( filename ) 116 | if #self.reports > 0 or #self.memoryReports > 0 then 117 | filename = filename or 'ProFi.txt' 118 | self:sortReportsWithSortMethod( self.reports, self.sortMethod ) 119 | self:writeReportsToFilename( filename ) 120 | print( string.format("[ProFi]\t Report written to %s", filename) ) 121 | end 122 | end 123 | 124 | --[[ 125 | Resets any profile information stored. 126 | ]] 127 | function ProFi:reset() 128 | self.reports = {} 129 | self.reportsByTitle = {} 130 | self.memoryReports = {} 131 | self.highestMemoryReport = nil 132 | self.lowestMemoryReport = nil 133 | self.has_started = false 134 | self.has_finished = false 135 | self.should_run_once = false 136 | self.lastCheckMemoryTime = nil 137 | self.hookCount = self.hookCount or DEFAULT_DEBUG_HOOK_COUNT 138 | self.sortMethod = self.sortMethod or sortByDurationDesc 139 | self.inspect = nil 140 | end 141 | 142 | --[[ 143 | Set how often a hook is called. 144 | See http://pgl.yoyo.org/luai/i/debug.sethook for information. 145 | Param: [hookCount:number] if 0 ProFi counts every time a function is called. 146 | if 2 ProFi counts every other 2 function calls. 147 | ]] 148 | function ProFi:setHookCount( hookCount ) 149 | self.hookCount = hookCount 150 | end 151 | 152 | --[[ 153 | Set how the report is sorted when written to file. 154 | Param: [sortType:string] either 'duration' or 'count'. 155 | 'duration' sorts by the time a method took to run. 156 | 'count' sorts by the number of times a method was called. 157 | ]] 158 | function ProFi:setSortMethod( sortType ) 159 | if sortType == 'duration' then 160 | self.sortMethod = sortByDurationDesc 161 | elseif sortType == 'count' then 162 | self.sortMethod = sortByCallCount 163 | end 164 | end 165 | 166 | --[[ 167 | By default the getTime method is os.clock (CPU time), 168 | If you wish to use other time methods pass it to this function. 169 | Param: [getTimeMethod:function] 170 | ]] 171 | function ProFi:setGetTimeMethod( getTimeMethod ) 172 | getTime = getTimeMethod 173 | end 174 | 175 | --[[ 176 | Allows you to inspect a specific method. 177 | Will write to the report a list of methods that 178 | call this method you're inspecting, you can optionally 179 | provide a levels parameter to traceback a number of levels. 180 | Params: [methodName:string] the name of the method you wish to inspect. 181 | [levels:number:optional] the amount of levels you wish to traceback, defaults to 1. 182 | ]] 183 | function ProFi:setInspect( methodName, levels ) 184 | if self.inspect then 185 | self.inspect.methodName = methodName 186 | self.inspect.levels = levels or 1 187 | else 188 | self.inspect = { 189 | ['methodName'] = methodName; 190 | ['levels'] = levels or 1; 191 | } 192 | end 193 | end 194 | 195 | ----------------------- 196 | -- Implementations methods: 197 | ----------------------- 198 | 199 | function ProFi:shouldReturn( ) 200 | return self.should_run_once and self.has_finished 201 | end 202 | 203 | function ProFi:getFuncReport( funcInfo ) 204 | local title = self:getTitleFromFuncInfo( funcInfo ) 205 | local funcReport = self.reportsByTitle[ title ] 206 | if not funcReport then 207 | funcReport = self:createFuncReport( funcInfo ) 208 | self.reportsByTitle[ title ] = funcReport 209 | table.insert( self.reports, funcReport ) 210 | end 211 | return funcReport 212 | end 213 | 214 | function ProFi:getTitleFromFuncInfo( funcInfo ) 215 | local name = funcInfo.name or 'anonymous' 216 | local source = funcInfo.short_src or 'C_FUNC' 217 | local linedefined = funcInfo.linedefined or 0 218 | linedefined = string.format( FORMAT_LINENUM, linedefined ) 219 | return string.format(FORMAT_TITLE, source, name, linedefined) 220 | end 221 | 222 | function ProFi:createFuncReport( funcInfo ) 223 | local name = funcInfo.name or 'anonymous' 224 | local source = funcInfo.source or 'C Func' 225 | local linedefined = funcInfo.linedefined or 0 226 | local funcReport = { 227 | ['title'] = self:getTitleFromFuncInfo( funcInfo ); 228 | ['count'] = 0; 229 | ['timer'] = 0; 230 | } 231 | return funcReport 232 | end 233 | 234 | function ProFi:startHooks() 235 | debug.sethook( onDebugHook, 'cr', self.hookCount ) 236 | end 237 | 238 | function ProFi:stopHooks() 239 | debug.sethook() 240 | end 241 | 242 | function ProFi:sortReportsWithSortMethod( reports, sortMethod ) 243 | if reports then 244 | table.sort( reports, sortMethod ) 245 | end 246 | end 247 | 248 | function ProFi:writeReportsToFilename( filename ) 249 | local file, err = io.open( filename, 'w' ) 250 | assert( file, err ) 251 | self:writeBannerToFile( file ) 252 | if #self.reports > 0 then 253 | self:writeProfilingReportsToFile( self.reports, file ) 254 | end 255 | if #self.memoryReports > 0 then 256 | self:writeMemoryReportsToFile( self.memoryReports, file ) 257 | end 258 | file:close() 259 | end 260 | 261 | function ProFi:writeProfilingReportsToFile( reports, file ) 262 | local totalTime = self.stopTime - self.startTime 263 | local totalTimeOutput = string.format(FORMAT_TOTALTIME_LINE, totalTime) 264 | file:write( totalTimeOutput ) 265 | local header = string.format( FORMAT_HEADER_LINE, "FILE", "FUNCTION", "LINE", "TIME", "RELATIVE", "CALLED" ) 266 | file:write( header ) 267 | for i, funcReport in ipairs( reports ) do 268 | local timer = string.format(FORMAT_TIME, funcReport.timer) 269 | local count = string.format(FORMAT_COUNT, funcReport.count) 270 | local relTime = string.format(FORMAT_RELATIVE, (funcReport.timer / totalTime) * 100 ) 271 | local outputLine = string.format(FORMAT_OUTPUT_LINE, funcReport.title, timer, relTime, count ) 272 | file:write( outputLine ) 273 | if funcReport.inspections then 274 | self:writeInpsectionsToFile( funcReport.inspections, file ) 275 | end 276 | end 277 | end 278 | 279 | function ProFi:writeMemoryReportsToFile( reports, file ) 280 | file:write( FORMAT_MEMORY_HEADER1 ) 281 | self:writeHighestMemoryReportToFile( file ) 282 | self:writeLowestMemoryReportToFile( file ) 283 | file:write( FORMAT_MEMORY_HEADER2 ) 284 | for i, memoryReport in ipairs( reports ) do 285 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_MEMORY_LINE ) 286 | file:write( outputLine ) 287 | end 288 | end 289 | 290 | function ProFi:writeHighestMemoryReportToFile( file ) 291 | local memoryReport = self.highestMemoryReport 292 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_HIGH_MEMORY_LINE ) 293 | file:write( outputLine ) 294 | end 295 | 296 | function ProFi:writeLowestMemoryReportToFile( file ) 297 | local memoryReport = self.lowestMemoryReport 298 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_LOW_MEMORY_LINE ) 299 | file:write( outputLine ) 300 | end 301 | 302 | function ProFi:formatMemoryReportWithFormatter( memoryReport, formatter ) 303 | local time = string.format(FORMAT_TIME, memoryReport.time) 304 | local kbytes = string.format(FORMAT_KBYTES, memoryReport.memory) 305 | local mbytes = string.format(FORMAT_MBYTES, memoryReport.memory/1024) 306 | local outputLine = string.format(formatter, time, kbytes, mbytes, memoryReport.note) 307 | return outputLine 308 | end 309 | 310 | function ProFi:writeBannerToFile( file ) 311 | local banner = string.format(FORMAT_BANNER, os.date()) 312 | file:write( banner ) 313 | end 314 | 315 | function ProFi:writeInpsectionsToFile( inspections, file ) 316 | local inspectionsList = self:sortInspectionsIntoList( inspections ) 317 | file:write('\n==^ INSPECT ^======================================================================================================== COUNT ===\n') 318 | for i, inspection in ipairs( inspectionsList ) do 319 | local line = string.format(FORMAT_LINENUM, inspection.line) 320 | local title = string.format(FORMAT_TITLE, inspection.source, inspection.name, line) 321 | local count = string.format(FORMAT_COUNT, inspection.count) 322 | local outputLine = string.format(FORMAT_INSPECTION_LINE, title, count ) 323 | file:write( outputLine ) 324 | end 325 | file:write('===============================================================================================================================\n\n') 326 | end 327 | 328 | function ProFi:sortInspectionsIntoList( inspections ) 329 | local inspectionsList = {} 330 | for k, inspection in pairs(inspections) do 331 | inspectionsList[#inspectionsList+1] = inspection 332 | end 333 | table.sort( inspectionsList, sortByCallCount ) 334 | return inspectionsList 335 | end 336 | 337 | function ProFi:resetReports( reports ) 338 | for i, report in ipairs( reports ) do 339 | report.timer = 0 340 | report.count = 0 341 | report.inspections = nil 342 | end 343 | end 344 | 345 | function ProFi:shouldInspect( funcInfo ) 346 | return self.inspect and self.inspect.methodName == funcInfo.name 347 | end 348 | 349 | function ProFi:getInspectionsFromReport( funcReport ) 350 | local inspections = funcReport.inspections 351 | if not inspections then 352 | inspections = {} 353 | funcReport.inspections = inspections 354 | end 355 | return inspections 356 | end 357 | 358 | function ProFi:getInspectionWithKeyFromInspections( key, inspections ) 359 | local inspection = inspections[key] 360 | if not inspection then 361 | inspection = { 362 | ['count'] = 0; 363 | } 364 | inspections[key] = inspection 365 | end 366 | return inspection 367 | end 368 | 369 | function ProFi:doInspection( inspect, funcReport ) 370 | local inspections = self:getInspectionsFromReport( funcReport ) 371 | local levels = 5 + inspect.levels 372 | local currentLevel = 5 373 | while currentLevel < levels do 374 | local funcInfo = debug.getinfo( currentLevel, 'nS' ) 375 | if funcInfo then 376 | local source = funcInfo.short_src or '[C]' 377 | local name = funcInfo.name or 'anonymous' 378 | local line = funcInfo.linedefined 379 | local key = source..name..line 380 | local inspection = self:getInspectionWithKeyFromInspections( key, inspections ) 381 | inspection.source = source 382 | inspection.name = name 383 | inspection.line = line 384 | inspection.count = inspection.count + 1 385 | currentLevel = currentLevel + 1 386 | else 387 | break 388 | end 389 | end 390 | end 391 | 392 | function ProFi:onFunctionCall( funcInfo ) 393 | local funcReport = ProFi:getFuncReport( funcInfo ) 394 | funcReport.callTime = getTime() 395 | funcReport.count = funcReport.count + 1 396 | if self:shouldInspect( funcInfo ) then 397 | self:doInspection( self.inspect, funcReport ) 398 | end 399 | end 400 | 401 | function ProFi:onFunctionReturn( funcInfo ) 402 | local funcReport = ProFi:getFuncReport( funcInfo ) 403 | if funcReport.callTime then 404 | funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime) 405 | end 406 | end 407 | 408 | function ProFi:setHighestMemoryReport( memoryReport ) 409 | if not self.highestMemoryReport then 410 | self.highestMemoryReport = memoryReport 411 | else 412 | if memoryReport.memory > self.highestMemoryReport.memory then 413 | self.highestMemoryReport = memoryReport 414 | end 415 | end 416 | end 417 | 418 | function ProFi:setLowestMemoryReport( memoryReport ) 419 | if not self.lowestMemoryReport then 420 | self.lowestMemoryReport = memoryReport 421 | else 422 | if memoryReport.memory < self.lowestMemoryReport.memory then 423 | self.lowestMemoryReport = memoryReport 424 | end 425 | end 426 | end 427 | 428 | ----------------------- 429 | -- Local Functions: 430 | ----------------------- 431 | 432 | getTime = os.clock 433 | 434 | onDebugHook = function( hookType ) 435 | local funcInfo = debug.getinfo( 2, 'nS' ) 436 | if hookType == "call" then 437 | ProFi:onFunctionCall( funcInfo ) 438 | elseif hookType == "return" then 439 | ProFi:onFunctionReturn( funcInfo ) 440 | end 441 | end 442 | 443 | sortByDurationDesc = function( a, b ) 444 | return a.timer > b.timer 445 | end 446 | 447 | sortByCallCount = function( a, b ) 448 | return a.count > b.count 449 | end 450 | 451 | ----------------------- 452 | -- Return Module: 453 | ----------------------- 454 | 455 | ProFi:reset() 456 | return ProFi 457 | -------------------------------------------------------------------------------- /install/ast.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/ast.sol --]] -- aux-functions for ast:s 2 | 3 | local P = require 'parser' --[[SOL OUTPUT--]] -- TODO: make it the other way around 4 | local D = require 'sol_debug' --[[SOL OUTPUT--]] 5 | 6 | local AST = {} --[[SOL OUTPUT--]] 7 | 8 | -- Are two AST:s equal? Good for detecting stupid stuff like a = a 9 | -- Assummes same scope etc 10 | function AST.eq(a, b) 11 | if a == b then return true --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] -- Not sure when this could happen 12 | 13 | if a.ast_type ~= b.ast_type then 14 | return false --[[SOL OUTPUT--]] 15 | end --[[SOL OUTPUT--]] 16 | 17 | local ast_type = a.ast_type --[[SOL OUTPUT--]] 18 | 19 | if ast_type == 'IdExpr' then 20 | return a.name == b.name --[[SOL OUTPUT--]] 21 | 22 | elseif ast_type == 'MemberExpr' then 23 | D.break_() --[[SOL OUTPUT--]] 24 | return a.ident.data == b.ident.data and a.indexer == b.indexer and AST.eq(a.base, b.base) --[[SOL OUTPUT--]] 25 | 26 | -- TODO: more ast nodes than two =) 27 | 28 | else 29 | return false --[[SOL OUTPUT--]] 30 | end --[[SOL OUTPUT--]] 31 | end --[[SOL OUTPUT--]] 32 | 33 | return AST --[[SOL OUTPUT--]] 34 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/class.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/class.sol --]] function sol_class(klass_name, super_name) 2 | assert(klass_name, "You must specify a class name") --[[SOL OUTPUT--]] 3 | 4 | if super_name ~= nil then 5 | assert(rawget(_G, super_name), "Undefined super class " .. super_name) --[[SOL OUTPUT--]] 6 | end --[[SOL OUTPUT--]] 7 | 8 | local klass = rawget(_G, klass_name) --[[SOL OUTPUT--]] 9 | if not klass then 10 | klass = {} --[[SOL OUTPUT--]] 11 | 12 | local instance_meta = { __index = klass } --[[SOL OUTPUT--]] 13 | 14 | local construct = function(instance, ...) 15 | if instance then 16 | -- Clear-out: 17 | for k,_ in pairs(instance) do 18 | instance[k] = nil --[[SOL OUTPUT--]] 19 | end --[[SOL OUTPUT--]] 20 | else 21 | instance = {} --[[SOL OUTPUT--]] 22 | end --[[SOL OUTPUT--]] 23 | 24 | setmetatable(instance, instance_meta) --[[SOL OUTPUT--]] 25 | 26 | if instance.init then instance:init(...) --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 27 | return instance --[[SOL OUTPUT--]] 28 | end --[[SOL OUTPUT--]] 29 | 30 | local klass_meta = { 31 | -- Constructor style call, i.e. ClassName(...) 32 | __call = function(self, ...) 33 | return construct(nil, ...) --[[SOL OUTPUT--]] 34 | end 35 | } --[[SOL OUTPUT--]] 36 | 37 | if super_name ~= nil then 38 | local super = rawget(_G, super_name) --[[SOL OUTPUT--]] 39 | klass_meta.__index = super --[[SOL OUTPUT--]] 40 | klass.base_class = super --[[SOL OUTPUT--]] 41 | end --[[SOL OUTPUT--]] 42 | 43 | setmetatable(klass, klass_meta) --[[SOL OUTPUT--]] 44 | 45 | -- Placement new: 46 | klass._construct = function(instance, ...) 47 | return construct(instance, ...) --[[SOL OUTPUT--]] 48 | end --[[SOL OUTPUT--]] 49 | 50 | klass.class_name = klass_name --[[SOL OUTPUT--]] 51 | 52 | -- if some_obj:isa(Widget) then ... 53 | function klass:isa( some_class ) 54 | local c = klass --[[SOL OUTPUT--]] 55 | repeat 56 | if c == some_class then return true --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 57 | c = c.base_class --[[SOL OUTPUT--]] -- Walk up inheritence chain 58 | until not c --[[SOL OUTPUT--]] 59 | return false --[[SOL OUTPUT--]] 60 | end --[[SOL OUTPUT--]] 61 | 62 | local info = debug.getinfo(2) --[[SOL OUTPUT--]] 63 | klass.declaring_source_file = info.short_src or "No file found" --[[SOL OUTPUT--]] 64 | end --[[SOL OUTPUT--]] 65 | 66 | return klass --[[SOL OUTPUT--]] 67 | end --[[SOL OUTPUT--]] 68 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/debugger.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | TODO: 3 | print short function arguments as part of stack location 4 | bug: sometimes doesn't advance to next line (same line event reported multiple times) 5 | do coroutines work as expected? 6 | ]] 7 | 8 | local function pretty(obj, non_recursive) 9 | if type(obj) == "string" then 10 | return string.format("%q", obj) 11 | elseif type(obj) == "table" and not non_recursive then 12 | local str = "{" 13 | 14 | for k, v in pairs(obj) do 15 | local pair = pretty(k, true).." = "..pretty(v, true) 16 | str = str..(str == "{" and pair or ", "..pair) 17 | end 18 | 19 | return str.."}" 20 | else 21 | return tostring(obj) 22 | end 23 | end 24 | 25 | local help_message = [[ 26 | [return] - re-run last command 27 | c(ontinue) - contiue execution 28 | s(tep) - step forward by one line (into functions) 29 | n(ext) - step forward by one line (skipping over functions) 30 | p(rint) [expression] - execute the expression and print the result 31 | f(inish) - step forward until exiting the current function 32 | u(p) - move up the stack by one frame 33 | d(own) - move down the stack by one frame 34 | t(race) - print the stack trace 35 | l(ocals) - print the function arguments, locals and upvalues. 36 | h(elp) - print this message 37 | ]] 38 | 39 | -- The stack level that cmd_* functions use to access locals or info 40 | local LOCAL_STACK_LEVEL = 6 41 | 42 | -- Extra stack frames to chop off. 43 | -- Used for things like dbgcall() or the overridden assert/error functions 44 | local stack_top = 0 45 | 46 | -- The current stack frame index. 47 | -- Changed using the up/down commands 48 | local stack_offset = 0 49 | 50 | -- Override if you don't want to use stdin 51 | -- Override if you don't want to use stdout. 52 | local function dbg_write(str, ...) 53 | io.write(string.format(str, ...)) 54 | end 55 | 56 | local function dbg_writeln(str, ...) 57 | dbg_write((str or "").."\n", ...) 58 | end 59 | 60 | local function dbg_read(prompt) 61 | dbg_write(prompt) 62 | return io.read() 63 | end 64 | 65 | local function formatStackLocation(info) 66 | local fname = (info.name or string.format("<%s:%d>", info.short_src, info.linedefined)) 67 | return string.format("%s:%d in function '%s'", info.short_src, info.currentline, fname) 68 | end 69 | 70 | local repl 71 | 72 | local function hook_factory(repl_threshold) 73 | return function(offset) 74 | return function(event, line) 75 | local info = debug.getinfo(2) 76 | 77 | if event == "call" and info.linedefined >= 0 then 78 | offset = offset + 1 79 | elseif event == "return" and info.linedefined >= 0 then 80 | if offset <= repl_threshold then 81 | -- TODO this is what causes the duplicated lines 82 | -- Don't remember why this is even here... 83 | --repl() 84 | else 85 | offset = offset - 1 86 | end 87 | elseif event == "line" and offset <= repl_threshold then 88 | repl() 89 | end 90 | end 91 | end 92 | end 93 | 94 | local hook_step = hook_factory(1) 95 | local hook_next = hook_factory(0) 96 | local hook_finish = hook_factory(-1) 97 | 98 | local function table_merge(t1, t2) 99 | local tbl = {} 100 | for k, v in pairs(t1) do tbl[k] = v end 101 | for k, v in pairs(t2) do tbl[k] = v end 102 | 103 | return tbl 104 | end 105 | 106 | local VARARG_SENTINEL = "(*varargs)" 107 | 108 | local function local_bindings(offset, include_globals) 109 | --[[ TODO 110 | Need to figure out how to get varargs with LuaJIT 111 | ]] 112 | 113 | local level = stack_offset + offset + LOCAL_STACK_LEVEL 114 | local func = debug.getinfo(level).func 115 | local bindings = {} 116 | 117 | -- Retrieve the upvalues 118 | do local i = 1; repeat 119 | local name, value = debug.getupvalue(func, i) 120 | if name then bindings[name] = value end 121 | i = i + 1 122 | until name == nil end 123 | 124 | -- Retrieve the locals (overwriting any upvalues) 125 | do local i = 1; repeat 126 | local name, value = debug.getlocal(level, i) 127 | if name then bindings[name] = value end 128 | i = i + 1 129 | until name == nil end 130 | 131 | -- Retrieve the varargs. (only works in Lua 5.2) 132 | local varargs = {} 133 | do local i = -1; repeat 134 | local name, value = debug.getlocal(level, i) 135 | table.insert(varargs, value) 136 | i = i - 1 137 | until name == nil end 138 | bindings[VARARG_SENTINEL] = varargs 139 | 140 | if include_globals then 141 | -- Merge the local bindings over the top of the environment table. 142 | -- In Lua 5.2, you have to get the environment table from the function's locals. 143 | local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV) 144 | 145 | -- Finally, merge the tables and add a lookup for globals. 146 | return setmetatable(table_merge(env, bindings), {__index = _G}) 147 | else 148 | return bindings 149 | end 150 | end 151 | 152 | local function compile_chunk(expr, env) 153 | if _VERSION <= "Lua 5.1" then 154 | local chunk = load("return "..expr, "") 155 | if chunk then setfenv(chunk, env) end 156 | return chunk 157 | else 158 | -- The Lua 5.2 way is a bit cleaner 159 | return load("return "..expr, "", "t", env) 160 | end 161 | end 162 | 163 | local function super_pack(...) 164 | return select("#", ...), {...} 165 | end 166 | 167 | local function cmd_print(expr) 168 | local env = local_bindings(1, true) 169 | local chunk = compile_chunk(expr, env) 170 | if chunk == nil then 171 | dbg_writeln("Error: Could not evaluate expression.") 172 | return false 173 | end 174 | 175 | local count, results = super_pack(pcall(chunk, table.unpack(env[VARARG_SENTINEL]))) 176 | if not results[1] then 177 | dbg_writeln("Error: %s", results[2]) 178 | elseif count == 1 then 179 | dbg_writeln("Error: No expression to execute") 180 | else 181 | local result = "" 182 | for i=2, count do 183 | result = result..(i ~= 2 and ", " or "")..pretty(results[i]) 184 | end 185 | 186 | --dbg_writeln("%s => %s", expr, result) 187 | dbg_writeln("%s => %s", expr, result) 188 | end 189 | 190 | return false 191 | end 192 | 193 | local function cmd_up() 194 | local info = debug.getinfo(stack_offset + LOCAL_STACK_LEVEL + 1) 195 | 196 | if info then 197 | stack_offset = stack_offset + 1 198 | dbg_writeln("Inspecting frame: "..formatStackLocation(info)) 199 | else 200 | dbg_writeln("Error: Already at the top of the stack.") 201 | end 202 | 203 | return false 204 | end 205 | 206 | local function cmd_down() 207 | if stack_offset > stack_top then 208 | stack_offset = stack_offset - 1 209 | 210 | local info = debug.getinfo(stack_offset + LOCAL_STACK_LEVEL) 211 | dbg_writeln("Inspecting frame: "..formatStackLocation(info)) 212 | else 213 | dbg_writeln("Error: Already at the bottom of the stack.") 214 | end 215 | 216 | return false 217 | end 218 | 219 | local function cmd_trace() 220 | local location = formatStackLocation(debug.getinfo(stack_offset + LOCAL_STACK_LEVEL)) 221 | local offset = stack_offset - stack_top 222 | local message = string.format("Inspecting frame: %d - (%s)", offset, location) 223 | dbg_writeln(debug.traceback(message, stack_offset + LOCAL_STACK_LEVEL)) 224 | 225 | return false 226 | end 227 | 228 | local function cmd_locals() 229 | for k, v in pairs(local_bindings(1, false)) do 230 | -- Don't print the Lua 5.2 __ENV local. It's pretty huge and useless to see. 231 | if k ~= "_ENV" then 232 | dbg_writeln("\t%s => %s", k, pretty(v)) 233 | end 234 | end 235 | 236 | return false 237 | end 238 | 239 | local function cmd_help() 240 | dbg_writeln(help_message) 241 | return false 242 | end 243 | 244 | local last_cmd = false 245 | 246 | -- Run a command line 247 | -- Returns true if the REPL should exit and the hook function factory 248 | local function run_command(line) 249 | -- Continue without caching the command if you hit control-d. 250 | if line == nil then 251 | dbg_writeln() 252 | return true 253 | end 254 | 255 | -- Execute the previous command or cache it 256 | if line == "" then 257 | if last_cmd then return table.unpack({run_command(last_cmd)}) else return false end 258 | else 259 | last_cmd = line 260 | end 261 | 262 | local commands = { 263 | ["c"] = function() return true end, 264 | ["s"] = function() return true, hook_step end, 265 | ["n"] = function() return true, hook_next end, 266 | ["f"] = function() return true, hook_finish end, 267 | ["p%s?(.*)"] = cmd_print, 268 | ["u"] = cmd_up, 269 | ["d"] = cmd_down, 270 | ["t"] = cmd_trace, 271 | ["l"] = cmd_locals, 272 | ["h"] = cmd_help, 273 | } 274 | 275 | for cmd, cmd_func in pairs(commands) do 276 | local matches = {string.match(line, "^("..cmd..")$")} 277 | if matches[1] then 278 | return table.unpack({cmd_func(select(2, table.unpack(matches)))}) 279 | end 280 | end 281 | 282 | dbg_writeln("Error: command '%s' not recognized", line) 283 | return false 284 | end 285 | 286 | repl = function() 287 | dbg_writeln(formatStackLocation(debug.getinfo(LOCAL_STACK_LEVEL - 3 + stack_top))) 288 | 289 | repeat 290 | local success, done, hook = pcall(run_command, dbg_read("debugger.lua> ")) 291 | if success then 292 | debug.sethook(hook and hook(0), "crl") 293 | else 294 | local message = string.format("INTERNAL DEBUGGER.LUA ERROR. ABORTING\n: %s", done) 295 | dbg_writeln(message) 296 | error(message) 297 | end 298 | until done 299 | end 300 | 301 | local dbg = setmetatable({}, { 302 | __call = function(self, condition, offset) 303 | if condition then return end 304 | 305 | offset = (offset or 0) 306 | stack_offset = offset 307 | stack_top = offset 308 | 309 | debug.sethook(hook_next(1), "crl") 310 | return 311 | end, 312 | }) 313 | 314 | dbg.write = dbg_write 315 | dbg.writeln = dbg_writeln 316 | dbg.pretty = pretty 317 | 318 | function dbg.error(err, level) 319 | level = level or 1 320 | dbg_writeln("Debugger stopped on error(%s)", pretty(err)) 321 | dbg(false, level) 322 | error(err, level) 323 | end 324 | 325 | function dbg.assert(condition, message) 326 | if not condition then 327 | dbg_writeln("Debugger stopped on assert(..., %s)", message or "nil") 328 | dbg(false, 1) 329 | end 330 | assert(condition, message) 331 | end 332 | 333 | function dbg.call(f, l) 334 | return (xpcall(f, function(err) 335 | dbg_writeln("Debugger stopped on error: "..pretty(err)) 336 | dbg(false, (l or 0) + 1) 337 | return 338 | end)) 339 | end 340 | 341 | local function luajit_load_readline_support() 342 | local ffi = require("ffi") 343 | 344 | ffi.cdef[[ 345 | void free(void *ptr); 346 | 347 | char *readline(const char *); 348 | int add_history(const char *); 349 | ]] 350 | 351 | local readline = ffi.load("readline") 352 | 353 | dbg_read = function(prompt) 354 | local cstr = readline.readline(prompt) 355 | 356 | if cstr ~= nil then 357 | local str = ffi.string(cstr) 358 | 359 | if string.match(str, "[^%s]+") then 360 | readline.add_history(cstr) 361 | end 362 | 363 | ffi.C.free(cstr) 364 | return str 365 | else 366 | return nil 367 | end 368 | end 369 | 370 | dbg_writeln("Readline support loaded.") 371 | end 372 | 373 | if jit and 374 | jit.version == "LuaJIT 2.0.0-beta10" 375 | then 376 | dbg_writeln("debugger.lua loaded for "..jit.version) 377 | pcall(luajit_load_readline_support) 378 | elseif 379 | _VERSION == "Lua 5.2" or 380 | _VERSION == "Lua 5.1" 381 | then 382 | dbg_writeln("debugger.lua loaded for ".._VERSION) 383 | else 384 | dbg_writeln("debugger.lua not tested against ".._VERSION) 385 | dbg_writeln("Please send me feedback!") 386 | end 387 | 388 | return dbg 389 | -------------------------------------------------------------------------------- /install/edit_distance.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/edit_distance.sol --]] -- From http://nayruden.com/?p=115 - https://gist.github.com/Nayruden/427389 2 | -- Translated to Sol by Emil Ernerfeldt in 2013 3 | --[[ 4 | Function: EditDistance 5 | 6 | Finds the edit distance between two strings or tables. Edit distance is the minimum number of 7 | edits needed to transform one string or table into the other. 8 | 9 | Parameters: 10 | 11 | s - A *string* or *table*. 12 | t - Another *string* or *table* to compare against s. 13 | lim - An *optional number* to limit the function to a maximum edit distance. If specified 14 | and the function detects that the edit distance is going to be larger than limit, limit 15 | is returned immediately. 16 | 17 | Returns: 18 | 19 | A *number* specifying the minimum edits it takes to transform s into t or vice versa. Will 20 | not return a higher number than lim, if specified. 21 | 22 | Example: 23 | 24 | :EditDistance( "Tuesday", "Teusday" ) -- One transposition. 25 | :EditDistance( "kitten", "sitting" ) -- Two substitutions and a deletion. 26 | 27 | returns... 28 | 29 | :1 30 | :3 31 | 32 | Notes: 33 | 34 | * Complexity is O( (#t+1) * (#s+1) ) when lim isn't specified. 35 | * This function can be used to compare array-like tables as easily as strings. 36 | * The algorithm used is Damerau–Levenshtein distance, which calculates edit distance based 37 | off number of subsitutions, additions, deletions, and transpositions. 38 | * Source code for this function is based off the Wikipedia article for the algorithm 39 | . 40 | * This function is case sensitive when comparing strings. 41 | * If this function is being used several times a second, you should be taking advantage of 42 | the lim parameter. 43 | * Using this function to compare against a dictionary of 250,000 words took about 0.6 44 | seconds on my machine for the word "Teusday", around 10 seconds for very poorly 45 | spelled words. Both tests used lim. 46 | 47 | Revisions: 48 | 49 | v1.00 - Initial. 50 | ]] 51 | local function edit_distance( s, t, lim ) 52 | local s_len, t_len = #s, #t --[[SOL OUTPUT--]] -- Calculate the sizes of the strings or arrays 53 | if lim and math.abs( s_len - t_len ) >= lim then -- If sizes differ by lim, we can stop here 54 | return lim --[[SOL OUTPUT--]] 55 | end --[[SOL OUTPUT--]] 56 | 57 | -- Convert string arguments to arrays of ints (ASCII values) 58 | if type( s ) == "string" then 59 | s = { string.byte( s, 1, s_len ) } --[[SOL OUTPUT--]] 60 | end --[[SOL OUTPUT--]] 61 | 62 | if type( t ) == "string" then 63 | t = { string.byte( t, 1, t_len ) } --[[SOL OUTPUT--]] 64 | end --[[SOL OUTPUT--]] 65 | 66 | local min = math.min --[[SOL OUTPUT--]] -- Localize for performance 67 | local num_columns = t_len + 1 --[[SOL OUTPUT--]] -- We use this a lot 68 | 69 | local d = {} --[[SOL OUTPUT--]] -- (s_len+1) * (t_len+1) is going to be the size of this array 70 | -- This is technically a 2D array, but we're treating it as 1D. Remember that 2D access in the 71 | -- form my_2d_array[ i, j ] can be converted to my_1d_array[ i * num_columns + j ], where 72 | -- num_columns is the number of columns you had in the 2D array assuming row-major order and 73 | -- that row and column indices start at 0 (we're starting at 0). 74 | 75 | for i=0, s_len do 76 | d[ i * num_columns ] = i --[[SOL OUTPUT--]] -- Initialize cost of deletion 77 | end --[[SOL OUTPUT--]] 78 | for j=0, t_len do 79 | d[ j ] = j --[[SOL OUTPUT--]] -- Initialize cost of insertion 80 | end --[[SOL OUTPUT--]] 81 | 82 | for i=1, s_len do 83 | local i_pos = i * num_columns --[[SOL OUTPUT--]] 84 | local best = lim --[[SOL OUTPUT--]] -- Check to make sure something in this row will be below the limit 85 | for j=1, t_len do 86 | local add_cost = (s[ i ] ~= t[ j ] and 1 or 0) --[[SOL OUTPUT--]] 87 | local val = min( 88 | d[ i_pos - num_columns + j ] + 1, -- Cost of deletion 89 | d[ i_pos + j - 1 ] + 1, -- Cost of insertion 90 | d[ i_pos - num_columns + j - 1 ] + add_cost -- Cost of substitution, it might not cost anything if it's the same 91 | ) --[[SOL OUTPUT--]] 92 | d[ i_pos + j ] = val --[[SOL OUTPUT--]] 93 | 94 | -- is this eligible for tranposition? 95 | if i > 1 and j > 1 and s[ i ] == t[ j - 1 ] and s[ i - 1 ] == t[ j ] then 96 | d[ i_pos + j ] = min( 97 | val, -- Current cost 98 | d[ i_pos - num_columns - num_columns + j - 2 ] + add_cost -- Cost of transposition 99 | ) --[[SOL OUTPUT--]] 100 | end --[[SOL OUTPUT--]] 101 | 102 | if lim and val < best then 103 | best = val --[[SOL OUTPUT--]] 104 | end --[[SOL OUTPUT--]] 105 | end --[[SOL OUTPUT--]] 106 | 107 | if lim and best >= lim then 108 | return lim --[[SOL OUTPUT--]] 109 | end --[[SOL OUTPUT--]] 110 | end --[[SOL OUTPUT--]] 111 | 112 | return d[ #d ] --[[SOL OUTPUT--]] 113 | end --[[SOL OUTPUT--]] 114 | 115 | return edit_distance --[[SOL OUTPUT--]] 116 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/globals.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/globals.sol --]] -- Globals settings - set by solc 2 | 3 | g_local_parse = false --[[SOL OUTPUT--]] -- If true, ignore 'require' 4 | g_spam = false --[[SOL OUTPUT--]] 5 | g_ignore_errors = false --[[SOL OUTPUT--]] 6 | g_break_on_error = false --[[SOL OUTPUT--]] 7 | g_warnings_as_errors = false --[[SOL OUTPUT--]] 8 | g_write_timings = false --[[SOL OUTPUT--]] 9 | g_print_stats = false --[[SOL OUTPUT--]] -- Prints out stats on the popularity of tokens and ast_type:s 10 | g_one_line_errors = false --[[SOL OUTPUT--]] -- Print errors and warnigns on single lines 11 | 12 | -- Output options: 13 | g_align_lines = false --[[SOL OUTPUT--]] -- Align line numbers in output? 14 | g_warn_output = false --[[SOL OUTPUT--]] -- Print --[[SOL OUTPUT--]] on each line in output file? 15 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/lua_intrinsics.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/lua_intrinsics.sol --]] local INTRINSICS = [[ 2 | global assert = extern : function(any, string?, ...) -> ... 3 | global collectgarbage = extern : function("collect" or "stop" or "restart" or "count" or "step" or "setpause" or "setstepmul" or "isrunning", any?) -> ... 4 | global dofile = extern : function(string?) -> ... 5 | global error = extern : function(any, int?) -> ... -- TODO: return never-type 6 | global print = extern : function(...) -> void 7 | global tonumber = extern : function(any, uint?) -> number? 8 | global tostring = extern : function(any, uint?) -> string 9 | global type = extern : function(any) -> "nil" or "number" or "string" or "boolean" or "table" or "function" or "thread" or "userdata" 10 | global unpack = extern : function([any]) -> ... 11 | 12 | global coroutine = { 13 | yield = extern : function(...) -> ...; 14 | wrap = extern : function(...) -> ...; 15 | } 16 | 17 | global debug = { 18 | getinfo = extern : function(...) -> ...; 19 | } 20 | 21 | global math = { 22 | min = extern : function(...: number) -> number; 23 | max = extern : function(...: number) -> number; 24 | abs = extern : function(number) -> number; 25 | ceil = extern : function(number) -> number; 26 | floor = extern : function(number) -> number; 27 | huge = extern : number; 28 | 29 | sqrt = extern : function(number) -> number; 30 | pow = extern : function(number, number) -> number; 31 | 32 | sin = extern : function(number) -> number; 33 | asin = extern : function(number) -> number; 34 | cos = extern : function(number) -> number; 35 | acos = extern : function(number) -> number; 36 | tan = extern : function(number) -> number; 37 | atan = extern : function(number) -> number; 38 | atan2 = extern : function(number, number) -> number; 39 | } 40 | 41 | global io = { 42 | open = extern : function(...) -> ...; 43 | read = extern : function(...) -> ...; 44 | stderr = extern; 45 | } 46 | 47 | global os = { 48 | exit = extern : function(...) -> ...; 49 | date = extern : function(...) -> ...; 50 | getenv = extern : function(...) -> ...; 51 | execute = extern : function(...) -> ...; 52 | 53 | clock = extern : function() -> number; 54 | } 55 | 56 | global package = { 57 | path = extern : string; 58 | } 59 | 60 | global string = { 61 | byte = extern : function(string, int, int?, int?) -> ...; 62 | char = extern : function(string, ... : int) -> string; 63 | format = extern : function(string, ...) -> string; 64 | rep = extern : function(...) -> ...; 65 | 66 | sub = extern : function(string, int, int?) -> string?; 67 | 68 | -- Patterns: 69 | gsub = extern : function(...) -> ...; 70 | find = extern : function(...) -> ...; 71 | match = extern : function(...) -> ...; 72 | gmatch = extern : function(...) -> ...; -- TODO: a generator 73 | } 74 | 75 | global table = { 76 | concat = extern : function(...) -> ...; 77 | insert = extern : function(...) -> ...; 78 | sort = extern : function(...) -> ...; 79 | remove = extern : function(...) -> ...; 80 | } 81 | ]] --[[SOL OUTPUT--]] 82 | 83 | 84 | 85 | ---------------------------------------------- 86 | 87 | 88 | local P = require 'parser' --[[SOL OUTPUT--]] 89 | local L = require 'lexer' --[[SOL OUTPUT--]] 90 | local TypeCheck = require 'type_check' --[[SOL OUTPUT--]] 91 | require 'scope' --[[SOL OUTPUT--]] 92 | 93 | local M = {} --[[SOL OUTPUT--]] 94 | 95 | function M.add_intrinsics_to_global_scope() 96 | local global_scope = Scope.get_global_scope() --[[SOL OUTPUT--]] 97 | local module_scope = Scope.create_module_scope() --[[SOL OUTPUT--]] 98 | local scope = module_scope --[[SOL OUTPUT--]] 99 | 100 | local filename = "INTRINSICS" --[[SOL OUTPUT--]] 101 | local settings = P.SOL_SETTINGS --[[SOL OUTPUT--]] 102 | 103 | local st, tokens = L.lex_sol(INTRINSICS, filename, settings) --[[SOL OUTPUT--]] 104 | assert(st, tokens) --[[SOL OUTPUT--]] 105 | local st, ast = P.parse_sol(INTRINSICS, tokens, filename, settings, scope) --[[SOL OUTPUT--]] 106 | assert(st, ast) --[[SOL OUTPUT--]] 107 | local st, err = TypeCheck(ast, filename, nil, settings) --[[SOL OUTPUT--]] 108 | assert(st, err) --[[SOL OUTPUT--]] 109 | 110 | if not Scope.GLOBALS_IN_TOP_SCOPE then 111 | global_scope.fixed = false --[[SOL OUTPUT--]] 112 | 113 | for _,v in ipairs(module_scope:get_global_vars()) do 114 | global_scope:add_global(v) --[[SOL OUTPUT--]] 115 | end --[[SOL OUTPUT--]] 116 | 117 | for name,type in pairs(module_scope:get_global_typedefs()) do 118 | global_scope:add_global_type( name, type ) --[[SOL OUTPUT--]] 119 | end --[[SOL OUTPUT--]] 120 | 121 | global_scope.fixed = true --[[SOL OUTPUT--]] 122 | end --[[SOL OUTPUT--]] 123 | end --[[SOL OUTPUT--]] 124 | 125 | return M --[[SOL OUTPUT--]] 126 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/scope.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/scope.sol --]] local T = require 'type' --[[SOL OUTPUT--]] 2 | local D = require 'sol_debug' --[[SOL OUTPUT--]] 3 | local U = require 'util' --[[SOL OUTPUT--]] 4 | 5 | 6 | --[[ 7 | Scopes: 8 | 9 | At the top there is the shared and immutable 'global_scope'. This contains the lua-wide globals. 10 | When parsign a module, there is a 'module_scope' whose parent is the 'global_scope'. 11 | 12 | User declared globals goes into the 'module_scope' and are marked as 'global'. 13 | --]] 14 | 15 | --[-[ 16 | Scope = { 17 | -- TODO: static members here, i.e. global_scope 18 | global_scope = nil -- not found in later lookup :T 19 | } --[[SOL OUTPUT--]] 20 | 21 | function Scope.new(where, parent) 22 | --var s = {} : Scope 23 | local s = {} --[[SOL OUTPUT--]] 24 | setmetatable(s, { __index = Scope }) --[[SOL OUTPUT--]] 25 | s:init(where, parent) --[[SOL OUTPUT--]] 26 | return s --[[SOL OUTPUT--]] 27 | end --[[SOL OUTPUT--]] --[[SOL OUTPUT--]] --[[SOL OUTPUT--]] 28 | 29 | 30 | Scope 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | .GLOBALS_IN_TOP_SCOPE = true --[[SOL OUTPUT--]] 59 | 60 | -------------------------------------------------- 61 | 62 | -- Constructor 63 | function Scope:init(where, parent) 64 | self.where = where --[[SOL OUTPUT--]] 65 | self.parent = parent --[[SOL OUTPUT--]] 66 | self.children = {} --[[SOL OUTPUT--]] 67 | self.locals = {} --[[SOL OUTPUT--]] 68 | self.globals = {} --[[SOL OUTPUT--]] -- TODO: string->var map 69 | self.typedefs = {} --[[SOL OUTPUT--]] 70 | self.global_typedefs = {} --[[SOL OUTPUT--]] 71 | self.vararg = nil --[[SOL OUTPUT--]] 72 | self.fixed = false --[[SOL OUTPUT--]] 73 | 74 | if parent then 75 | parent.children [ # parent . children + 1 ] = self --[[SOL OUTPUT--]] 76 | end --[[SOL OUTPUT--]] 77 | end --[[SOL OUTPUT--]] 78 | 79 | 80 | function Scope:is_module_level() 81 | -- parent should be global scope, and so should have no parent 82 | return self.parent and self.parent.parent == nil --[[SOL OUTPUT--]] 83 | end --[[SOL OUTPUT--]] 84 | 85 | 86 | function Scope:declare_type(name, typ, where, is_local) 87 | D.assert(not self.fixed) --[[SOL OUTPUT--]] 88 | D.assert(type(name) == 'string') --[[SOL OUTPUT--]] 89 | D.assert(type(where) == 'string') --[[SOL OUTPUT--]] 90 | D.assert(type(is_local) == 'boolean') --[[SOL OUTPUT--]] 91 | 92 | if is_local then 93 | self.typedefs[name] = typ --[[SOL OUTPUT--]] 94 | else 95 | if Scope.GLOBALS_IN_TOP_SCOPE then 96 | Scope.global_scope.global_typedefs[name] = typ --[[SOL OUTPUT--]] 97 | else 98 | self.global_typedefs[name] = typ --[[SOL OUTPUT--]] 99 | end --[[SOL OUTPUT--]] 100 | end --[[SOL OUTPUT--]] 101 | end --[[SOL OUTPUT--]] 102 | 103 | 104 | function Scope:add_global_type(name, typ) 105 | self.global_typedefs[name] = typ --[[SOL OUTPUT--]] 106 | end --[[SOL OUTPUT--]] 107 | 108 | 109 | function Scope:create_local(name, where) 110 | D.assert(not self.fixed) --[[SOL OUTPUT--]] 111 | 112 | local v = { 113 | scope = self, 114 | name = name, 115 | is_global = false, 116 | is_constant = U.is_constant_name(name), 117 | where = where, 118 | num_reads = 0, 119 | num_writes = 0, 120 | } --[[SOL OUTPUT--]] 121 | 122 | D.assert(not self.locals[name]) --[[SOL OUTPUT--]] 123 | self.locals[name] = v --[[SOL OUTPUT--]] 124 | 125 | return v --[[SOL OUTPUT--]] 126 | end --[[SOL OUTPUT--]] 127 | 128 | 129 | function Scope:add_global(v) 130 | assert(not self.fixed) --[[SOL OUTPUT--]] 131 | self.globals [ # self . globals + 1 ] = v --[[SOL OUTPUT--]] 132 | end --[[SOL OUTPUT--]] 133 | 134 | 135 | function Scope:create_global(name, where, typ) 136 | assert(not self.fixed) --[[SOL OUTPUT--]] 137 | 138 | local v = { 139 | scope = self, 140 | name = name, 141 | is_global = true, 142 | is_constant = U.is_constant_name(name), 143 | where = where, 144 | type = typ, 145 | num_reads = 0, 146 | num_writes = 0, 147 | } --[[SOL OUTPUT--]] 148 | 149 | if Scope.GLOBALS_IN_TOP_SCOPE and self ~= Scope.global_scope then 150 | Scope.global_scope:add_global(v) --[[SOL OUTPUT--]] 151 | else 152 | self:add_global(v) --[[SOL OUTPUT--]] 153 | end --[[SOL OUTPUT--]] 154 | 155 | return v --[[SOL OUTPUT--]] 156 | end --[[SOL OUTPUT--]] 157 | 158 | 159 | function Scope:get_scoped_type(name) 160 | return self.typedefs[name] --[[SOL OUTPUT--]] 161 | end --[[SOL OUTPUT--]] 162 | 163 | 164 | function Scope:get_local_type(name) 165 | local t = self:get_scoped_type(name) --[[SOL OUTPUT--]] 166 | if t then return t --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 167 | if self.parent then return self.parent:get_type(name) --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 168 | return nil --[[SOL OUTPUT--]] 169 | end --[[SOL OUTPUT--]] 170 | 171 | 172 | function Scope:get_type(name) 173 | return self:get_local_type(name) or self:get_global_type(name) --[[SOL OUTPUT--]] 174 | end --[[SOL OUTPUT--]] 175 | 176 | 177 | function Scope:get_global_type(name) 178 | local t = self.global_typedefs[name] --[[SOL OUTPUT--]] 179 | if t then return t --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 180 | 181 | if self.parent then 182 | return self.parent:get_global_type(name) --[[SOL OUTPUT--]] 183 | end --[[SOL OUTPUT--]] 184 | end --[[SOL OUTPUT--]] 185 | 186 | 187 | function Scope:locals_iterator() 188 | return pairs(self.locals) --[[SOL OUTPUT--]] 189 | end --[[SOL OUTPUT--]] 190 | 191 | 192 | function Scope:sorted_locals() 193 | local variables = {} --[[SOL OUTPUT--]] 194 | for _, v in pairs(self.locals) do 195 | variables [ # variables + 1 ] = v --[[SOL OUTPUT--]] 196 | end --[[SOL OUTPUT--]] 197 | table.sort(variables, function(a,b) return a.where < b.where --[[SOL OUTPUT--]] end) --[[SOL OUTPUT--]] 198 | return variables --[[SOL OUTPUT--]] 199 | end --[[SOL OUTPUT--]] 200 | 201 | 202 | -- Will only check local scope 203 | function Scope:get_scoped(name, options) 204 | local v = self.locals[name] --[[SOL OUTPUT--]] 205 | if v then 206 | if not v.forward_declared or options ~= 'ignore_fwd_decl' then 207 | return v --[[SOL OUTPUT--]] 208 | end --[[SOL OUTPUT--]] 209 | end --[[SOL OUTPUT--]] 210 | return nil --[[SOL OUTPUT--]] 211 | end --[[SOL OUTPUT--]] 212 | 213 | 214 | -- Will check locals and parents 215 | function Scope:get_local(name, options) 216 | local v = self:get_scoped(name, options) --[[SOL OUTPUT--]] 217 | if v then return v --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 218 | 219 | if self.parent then 220 | return self.parent:get_local(name, options) --[[SOL OUTPUT--]] 221 | end --[[SOL OUTPUT--]] 222 | end --[[SOL OUTPUT--]] 223 | 224 | 225 | -- Global declared in this scope 226 | function Scope:get_scoped_global(name, options) 227 | for _, v in ipairs(self.globals) do 228 | if v.name == name then 229 | if not v.forward_declared or options ~= 'ignore_fwd_decl' then 230 | return v --[[SOL OUTPUT--]] 231 | end --[[SOL OUTPUT--]] 232 | end --[[SOL OUTPUT--]] 233 | end --[[SOL OUTPUT--]] 234 | return nil --[[SOL OUTPUT--]] 235 | end --[[SOL OUTPUT--]] 236 | 237 | 238 | function Scope:get_global(name, options) 239 | local v = self:get_scoped_global(name, options) --[[SOL OUTPUT--]] 240 | if v then return v --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 241 | 242 | if self.parent then 243 | return self.parent:get_global(name, options) --[[SOL OUTPUT--]] 244 | end --[[SOL OUTPUT--]] 245 | end --[[SOL OUTPUT--]] 246 | 247 | 248 | 249 | -- Var declared in this scope 250 | function Scope:get_scoped_var(name, options) 251 | return self:get_scoped(name, options) or self:get_scoped_global(name, options) --[[SOL OUTPUT--]] 252 | end --[[SOL OUTPUT--]] 253 | 254 | 255 | function Scope:get_var(name, options) 256 | return self:get_local(name, options) or self:get_global(name, options) --[[SOL OUTPUT--]] 257 | end --[[SOL OUTPUT--]] 258 | 259 | 260 | function Scope:get_global_vars(list) 261 | list = list or {} --[[SOL OUTPUT--]] 262 | U.list_join(list, self.globals) --[[SOL OUTPUT--]] 263 | for _,c in ipairs(self.children) do 264 | c:get_global_vars(list) --[[SOL OUTPUT--]] 265 | end --[[SOL OUTPUT--]] 266 | return list --[[SOL OUTPUT--]] 267 | end --[[SOL OUTPUT--]] 268 | 269 | 270 | function Scope:get_global_typedefs() 271 | return U.shallow_clone( self.global_typedefs ) --[[SOL OUTPUT--]] 272 | end --[[SOL OUTPUT--]] 273 | 274 | 275 | -------------------------------------------------- 276 | -- Static members: 277 | 278 | -- Created the global top-level scope 279 | function Scope.get_global_scope() 280 | Scope.global_scope = Scope.global_scope or Scope.create_global_scope() --[[SOL OUTPUT--]] 281 | return Scope.global_scope --[[SOL OUTPUT--]] 282 | end --[[SOL OUTPUT--]] 283 | 284 | 285 | function Scope.create_module_scope() 286 | local top_scope = Scope.get_global_scope() --[[SOL OUTPUT--]] 287 | return Scope.new( "[MODULE_SCOPE]", top_scope ) --[[SOL OUTPUT--]] 288 | end --[[SOL OUTPUT--]] 289 | 290 | 291 | function Scope.create_global_scope() 292 | local s = Scope.new("[GLOBAL_SCOPE]") --[[SOL OUTPUT--]] 293 | Scope.global_scope = s --[[SOL OUTPUT--]] 294 | local where = "[intrinsic]" --[[SOL OUTPUT--]] -- var.where 295 | 296 | 297 | --[[ 298 | -- Print out all globals 299 | for k,v in pairs(_G) do print(k, "\t=\t", v) end 300 | --]] 301 | 302 | -- Ommisions explicitly added in lua_intrinsics.sol 303 | 304 | local tables = { 305 | '_G', 306 | 'jit' -- luaJIT 307 | } --[[SOL OUTPUT--]] 308 | 309 | local functions = { 310 | 'gcinfo', 'getfenv', 'getmetatable', 311 | 'load', 'loadfile', 'loadstring', 312 | 'module', 313 | 'newproxy', 'next', 314 | 'pcall', 315 | 'rawequal', 'rawget', 'rawset', 316 | 'select', 'setfenv', 317 | 'xpcall', 318 | } --[[SOL OUTPUT--]] 319 | 320 | for _,name in ipairs(tables) do 321 | s:create_global( name, where, T.Object ) --[[SOL OUTPUT--]] 322 | end --[[SOL OUTPUT--]] 323 | 324 | for _,name in ipairs(functions) do 325 | local fun_t = { 326 | tag = 'function', 327 | args = { }, 328 | vararg = { tag = 'varargs', type = T.Any }, 329 | rets = T.AnyTypeList, 330 | name = name, 331 | intrinsic_name = name, 332 | } --[[SOL OUTPUT--]] 333 | s:create_global( name, where, fun_t) --[[SOL OUTPUT--]] 334 | end --[[SOL OUTPUT--]] 335 | 336 | s:create_global( '_VERSION', where, T.String ) --[[SOL OUTPUT--]] 337 | s:create_global( 'arg', where, { tag = 'list', type = T.String} ) --[[SOL OUTPUT--]] 338 | 339 | 340 | -- Ensure 'require' is recognized by TypeCheck.sol 341 | local require = s:create_global( 'require', where ) --[[SOL OUTPUT--]] 342 | require.type = { 343 | tag = 'function', 344 | args = { { type = T.String } }, 345 | rets = T.AnyTypeList, 346 | name = "require", 347 | intrinsic_name = "require", 348 | } --[[SOL OUTPUT--]] 349 | 350 | -- Ensure 'pairs' and 'ipairs' are recognized by TypeCheck.sol 351 | local pairs = s:create_global( 'pairs', where ) --[[SOL OUTPUT--]] 352 | pairs.type = { 353 | tag = 'function', 354 | args = { { type = T.Any } }, 355 | rets = T.AnyTypeList, 356 | intrinsic_name = "pairs", 357 | name = "pairs", 358 | } --[[SOL OUTPUT--]] 359 | 360 | local ipairs_ = s:create_global( 'ipairs', where ) --[[SOL OUTPUT--]] 361 | ipairs_.type = { 362 | tag = 'function', 363 | args = { { type = T.List } }, 364 | rets = T.AnyTypeList, 365 | intrinsic_name = "ipairs", 366 | name = "ipairs", 367 | } --[[SOL OUTPUT--]] 368 | 369 | local setmetatable = s:create_global( 'setmetatable', where ) --[[SOL OUTPUT--]] 370 | setmetatable.type = { 371 | tag = 'function', 372 | args = { { type = T.Table }, { type = T.variant(T.Table, T.Nil) } }, 373 | rets = { T.Table }, 374 | intrinsic_name = "setmetatable", 375 | name = "setmetatable", 376 | } --[[SOL OUTPUT--]] 377 | 378 | 379 | local is_local = true --[[SOL OUTPUT--]] 380 | --s:declare_type( 'void', T.Void ) -- Not a valid type, only allowed as a typelist 381 | s:declare_type( 'bool', T.Bool, where, is_local ) --[[SOL OUTPUT--]] 382 | s:declare_type( 'int', T.Int, where, is_local ) --[[SOL OUTPUT--]] 383 | s:declare_type( 'uint', T.Uint, where, is_local ) --[[SOL OUTPUT--]] 384 | s:declare_type( 'number', T.Num, where, is_local ) --[[SOL OUTPUT--]] 385 | s:declare_type( 'string', T.String, where, is_local ) --[[SOL OUTPUT--]] 386 | s:declare_type( 'any', T.Any, where, is_local ) --[[SOL OUTPUT--]] 387 | s:declare_type( 'table', T.Table, where, is_local ) --[[SOL OUTPUT--]] 388 | --s:declare_type( 'list', T.List, where, is_local ) -- use: [any] 389 | --s:declare_type( 'map', T.Map, where, is_local ) -- use: {any => any} 390 | s:declare_type( 'object', T.Object, where, is_local ) --[[SOL OUTPUT--]] 391 | 392 | -- keywords are handles explicitly during parsing 393 | --s:declare_type( 'nil', T.Nil) -- for e.g.: foo or bar or nil 394 | --s:declare_type( 'true', T.True) 395 | --s:declare_type( 'false', T.False) 396 | 397 | if not Scope.GLOBALS_IN_TOP_SCOPE then 398 | -- No more changes - user globals should be declared in module scope (a direct child) 399 | s.fixed = true --[[SOL OUTPUT--]] 400 | end --[[SOL OUTPUT--]] 401 | 402 | return s --[[SOL OUTPUT--]] 403 | end --[[SOL OUTPUT--]] 404 | 405 | ---------------------------------------- 406 | 407 | return {} --[[SOL OUTPUT--]] 408 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/sol.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/sol.sol --]] -- For running a .sol without outputting a .lua to disk 2 | -- TODO: compiler.sol continaing thing common to sol.sol and solc.sol 3 | 4 | require 'globals' --[[SOL OUTPUT--]] 5 | local lfs = require 'lfs' --[[SOL OUTPUT--]] 6 | local path = require 'pl.path' --[[SOL OUTPUT--]] 7 | 8 | ------------------------------------------------ 9 | -- Setup local includes: 10 | -- Without this code 11 | 12 | local sol_dir = path.dirname(arg[0]) --[[SOL OUTPUT--]] 13 | 14 | if sol_dir == "" then 15 | -- OK 16 | elseif path.isabs(sol_dir) then 17 | sol_dir = sol_dir .. ( '/' ) --[[SOL OUTPUT--]] 18 | else 19 | sol_dir = lfs.currentdir() .. '/' .. sol_dir .. '/' --[[SOL OUTPUT--]] 20 | end --[[SOL OUTPUT--]] 21 | 22 | -- Ensure the local includes work: 23 | package.path = sol_dir..'?.lua;' .. package.path --[[SOL OUTPUT--]] 24 | 25 | 26 | ------------------------------------------------ 27 | 28 | local output = require 'output' --[[SOL OUTPUT--]] 29 | local Lexer = require 'lexer' --[[SOL OUTPUT--]] 30 | local Parser = require 'parser' --[[SOL OUTPUT--]] 31 | local _ = require 'scope' --[[SOL OUTPUT--]] 32 | local T = require 'type' --[[SOL OUTPUT--]] 33 | local TypeCheck = require 'type_check' --[[SOL OUTPUT--]] 34 | local U = require 'util' --[[SOL OUTPUT--]] 35 | local printf_err = U.printf_err --[[SOL OUTPUT--]] 36 | 37 | 38 | local function compile_sol(source_text) 39 | local filename = "input" --[[SOL OUTPUT--]] 40 | local settings = Parser.SOL_SETTINGS --[[SOL OUTPUT--]] 41 | 42 | local st, tokens = Lexer.lex_sol(source_text, filename, settings) --[[SOL OUTPUT--]] 43 | if not st then 44 | os.exit(1) --[[SOL OUTPUT--]] 45 | return nil --[[SOL OUTPUT--]] 46 | end --[[SOL OUTPUT--]] 47 | 48 | local module_scope = Scope.create_module_scope() --[[SOL OUTPUT--]] 49 | 50 | local st, ast = Parser.parse_sol(source_text, tokens, filename, settings, module_scope) --[[SOL OUTPUT--]] 51 | if not st then 52 | os.exit(2) --[[SOL OUTPUT--]] 53 | return nil --[[SOL OUTPUT--]] 54 | end --[[SOL OUTPUT--]] 55 | 56 | local on_require = function(_,_) 57 | return T.AnyTypeList --[[SOL OUTPUT--]] 58 | end --[[SOL OUTPUT--]] 59 | 60 | local st, _ = TypeCheck(ast, filename, on_require, settings) --[[SOL OUTPUT--]] 61 | 62 | if not st then 63 | os.exit(3) --[[SOL OUTPUT--]] 64 | return nil --[[SOL OUTPUT--]] 65 | end --[[SOL OUTPUT--]] 66 | 67 | local str = output(ast, filename) --[[SOL OUTPUT--]] 68 | 69 | return str --[[SOL OUTPUT--]] 70 | end --[[SOL OUTPUT--]] 71 | 72 | 73 | local function run_sol(sol) 74 | local lua = compile_sol(sol) --[[SOL OUTPUT--]] 75 | if lua then 76 | local f = load(lua) --[[SOL OUTPUT--]] 77 | if f then 78 | f() --[[SOL OUTPUT--]] 79 | else 80 | printf_err("loadstring returned nil") --[[SOL OUTPUT--]] 81 | end --[[SOL OUTPUT--]] 82 | end --[[SOL OUTPUT--]] 83 | end --[[SOL OUTPUT--]] 84 | 85 | 86 | 87 | local function print_help() 88 | print([[ 89 | NAME: 90 | sol - Sol interpreter 91 | 92 | SYNOPSIS 93 | sol [ options ] [ filenames ] 94 | 95 | EXAMPLE 96 | lua sol.lua file.sol 97 | 98 | DESCRIPTION 99 | sol runs a .sol file 100 | 101 | OPTIONS 102 | -h or --help 103 | print this help text 104 | 105 | -s 106 | Spam mode: Will print extensive trace text (for debugging solc) 107 | 108 | -e0 109 | Ignore all errors and push through 110 | 111 | AUTHOR 112 | Emil Ernerfeldt 113 | ]]) --[[SOL OUTPUT--]] 114 | end --[[SOL OUTPUT--]] 115 | 116 | 117 | if #arg == 0 then 118 | print_help() --[[SOL OUTPUT--]] 119 | os.exit(-1) --[[SOL OUTPUT--]] 120 | else 121 | local ix = 1 --[[SOL OUTPUT--]] 122 | local num_files = 0 --[[SOL OUTPUT--]] 123 | 124 | while ix <= #arg do 125 | local a = arg[ix] --[[SOL OUTPUT--]] 126 | ix = ix + ( 1 ) --[[SOL OUTPUT--]] 127 | 128 | if a == '-h' or a == '--help' then 129 | print_help() --[[SOL OUTPUT--]] 130 | elseif a == '-s' then 131 | g_spam = true --[[SOL OUTPUT--]] 132 | elseif a == '-e0' then 133 | g_ignore_errors = true --[[SOL OUTPUT--]] 134 | else 135 | local path_in = a --[[SOL OUTPUT--]] 136 | if path.extension(path_in) ~= '.sol' then 137 | printf_err( "Input file must have .sol ending" ) --[[SOL OUTPUT--]] 138 | os.exit(-2) --[[SOL OUTPUT--]] 139 | end --[[SOL OUTPUT--]] 140 | 141 | local sol = U.read_entire_file( path_in ) --[[SOL OUTPUT--]] 142 | 143 | if not sol then 144 | printf_err( "Input file not found" ) --[[SOL OUTPUT--]] 145 | os.exit(-3) --[[SOL OUTPUT--]] 146 | end --[[SOL OUTPUT--]] 147 | 148 | run_sol( sol ) --[[SOL OUTPUT--]] 149 | 150 | num_files = num_files + ( 1 ) --[[SOL OUTPUT--]] 151 | end --[[SOL OUTPUT--]] 152 | end --[[SOL OUTPUT--]] 153 | 154 | if num_files == 0 then 155 | printf_err( "No input!" ) --[[SOL OUTPUT--]] 156 | print_help() --[[SOL OUTPUT--]] 157 | os.exit(-1337) --[[SOL OUTPUT--]] 158 | end --[[SOL OUTPUT--]] 159 | end --[[SOL OUTPUT--]] 160 | 161 | os.exit(0) --[[SOL OUTPUT--]] -- Success 162 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/sol_debug.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/sol_debug.sol --]] local D = {} --[[SOL OUTPUT--]] 2 | 3 | D.active = false --[[SOL OUTPUT--]] 4 | 5 | function D.get_lib() 6 | if D.active then 7 | return require("debugger") --[[SOL OUTPUT--]] 8 | else 9 | return nil --[[SOL OUTPUT--]] 10 | end --[[SOL OUTPUT--]] 11 | end --[[SOL OUTPUT--]] 12 | 13 | 14 | function D.activate() 15 | D.active = true --[[SOL OUTPUT--]] 16 | end --[[SOL OUTPUT--]] 17 | 18 | function D.assert(bool_expr, fmt, ...) 19 | --D.active = true 20 | 21 | if bool_expr then 22 | return bool_expr --[[SOL OUTPUT--]] 23 | elseif D.active then 24 | local dbg = D.get_lib() --[[SOL OUTPUT--]] 25 | return dbg.assert(bool_expr, fmt, ...) --[[SOL OUTPUT--]] 26 | else 27 | return assert(bool_expr, fmt, ...) --[[SOL OUTPUT--]] 28 | end --[[SOL OUTPUT--]] 29 | end --[[SOL OUTPUT--]] 30 | 31 | function D.break_() 32 | if D.active then 33 | print("Breaking debugger") --[[SOL OUTPUT--]] 34 | local dbg = D.get_lib() --[[SOL OUTPUT--]] 35 | dbg() --[[SOL OUTPUT--]] 36 | end --[[SOL OUTPUT--]] 37 | end --[[SOL OUTPUT--]] 38 | 39 | function D.error(msg) 40 | print("ERROR: " .. msg) --[[SOL OUTPUT--]] 41 | D.break_() --[[SOL OUTPUT--]] 42 | error(msg) --[[SOL OUTPUT--]] 43 | end --[[SOL OUTPUT--]] 44 | 45 | return D --[[SOL OUTPUT--]] 46 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install/strict.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/strict.sol --]] -- From http://metalua.luaforge.net/src/lib/strict.lua.html 2 | -- 3 | -- strict.lua 4 | -- checks uses of undeclared global variables 5 | -- All global variables must be 'declared' through a regular assignment 6 | -- (even assigning nil will do) in a main chunk before being used 7 | -- anywhere or assigned to inside a function. 8 | -- 9 | 10 | --[[ 11 | local mt = getmetatable(_G) 12 | if mt == nil then 13 | mt = {} 14 | setmetatable(_G, mt) 15 | end 16 | 17 | _G.__STRICT = true 18 | mt.__declared = {} 19 | 20 | mt.__newindex = function (t, n, v) 21 | if _G.__STRICT and not mt.__declared[n] then 22 | local w = debug.getinfo(2, "S").what 23 | if w ~= "main" and w ~= "C" then 24 | error("STRICT: assign to undeclared variable '"..n.."'", 2) 25 | end 26 | mt.__declared[n] = true 27 | end 28 | rawset(t, n, v) 29 | end 30 | 31 | mt.__index = function (t, n) 32 | if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then 33 | error("STRICT: variable '"..n.."' is not declared", 2) 34 | end 35 | return rawget(t, n) 36 | end 37 | --]] 38 | 39 | --[[ 40 | function declare_globals(...) 41 | for _, v in ipairs{...} do mt.__declared[v] = true end 42 | end 43 | --]] -------------------------------------------------------------------------------- /install/util.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM sol/util.sol --]] --[[ 2 | Util.lua 3 | 4 | Provides some common utilities shared throughout the project. 5 | --]] 6 | 7 | require 'globals' --[[SOL OUTPUT--]] 8 | local D = require 'sol_debug' --[[SOL OUTPUT--]] 9 | 10 | ------------------------------------------------ 11 | 12 | local U = {} --[[SOL OUTPUT--]] 13 | 14 | local PLATFORM = os.getenv("windir") and "win" or "unix" --[[SOL OUTPUT--]] 15 | 16 | ------------------------------------------------------ 17 | --[[ 18 | Lua pretty-printing of anything as lua-code. 19 | Only supports DAG:s. 20 | --]] 21 | 22 | 23 | local function is_identifier(key) 24 | return key:match('^[_%a][_%w]*$') --[[SOL OUTPUT--]] 25 | end --[[SOL OUTPUT--]] 26 | 27 | 28 | local function is_keyword(key) 29 | return key == 'and' 30 | or key == 'break' 31 | or key == 'do' 32 | or key == 'else' 33 | or key == 'elseif' 34 | or key == 'end' 35 | or key == 'false' 36 | or key == 'for' 37 | or key == 'function' 38 | or key == 'if' 39 | or key == 'in' 40 | or key == 'local' 41 | or key == 'nil' 42 | or key == 'not' 43 | or key == 'or' 44 | or key == 'repeat' 45 | or key == 'return' 46 | or key == 'then' 47 | or key == 'true' 48 | or key == 'until' 49 | or key == 'while' 50 | 51 | -- Sol: 52 | or key == 'class' 53 | or key == 'global' 54 | or key == 'typedef' 55 | or key == 'var' --[[SOL OUTPUT--]] 56 | end --[[SOL OUTPUT--]] 57 | 58 | 59 | local function is_safe_key(key) 60 | return is_identifier(key) and not is_keyword(key) --[[SOL OUTPUT--]] 61 | end --[[SOL OUTPUT--]] 62 | 63 | 64 | -- val - value to serialize 65 | -- ignore_set - ignore these key:s 66 | -- indent - indent on any _subsequent_ line 67 | -- discovered - set of tables already processed (used to discover loops) 68 | function U.serialize_to_rope(rope, val, ignore_set, indent, discovered) 69 | if val == nil then 70 | rope [ # rope + 1 ] = "nil" --[[SOL OUTPUT--]] 71 | return --[[SOL OUTPUT--]] 72 | end --[[SOL OUTPUT--]] 73 | 74 | ignore_set = ignore_set or {} --[[SOL OUTPUT--]] 75 | indent = indent or "" --[[SOL OUTPUT--]] 76 | discovered = discovered or {} --[[SOL OUTPUT--]] 77 | 78 | if type(val) == "table" then 79 | if discovered[val] then 80 | --error("serialize: loop discovered") 81 | rope [ # rope + 1 ] = 'LOOP' --[[SOL OUTPUT--]] 82 | return --[[SOL OUTPUT--]] 83 | end --[[SOL OUTPUT--]] 84 | discovered[val] = true --[[SOL OUTPUT--]] 85 | 86 | local scope_indent = indent .. " " --[[SOL OUTPUT--]] 87 | rope [ # rope + 1 ] = "{\n" --[[SOL OUTPUT--]] 88 | if U.is_array(val) then 89 | for _,v in ipairs(val) do 90 | rope [ # rope + 1 ] = scope_indent --[[SOL OUTPUT--]] 91 | U.serialize_to_rope(rope, v, ignore_set, scope_indent, discovered) --[[SOL OUTPUT--]] 92 | rope [ # rope + 1 ] = ",\n" --[[SOL OUTPUT--]] 93 | end --[[SOL OUTPUT--]] 94 | else 95 | for k,v in pairs(val) do 96 | --if not ignore_set[k] then 97 | if true then 98 | local key = is_safe_key(k) and k or string.format("[%q]", k) --[[SOL OUTPUT--]] 99 | 100 | rope [ # rope + 1 ] = scope_indent --[[SOL OUTPUT--]] 101 | rope [ # rope + 1 ] = key --[[SOL OUTPUT--]] 102 | rope [ # rope + 1 ] = " = " --[[SOL OUTPUT--]] 103 | 104 | if ignore_set[k] then 105 | rope [ # rope + 1 ] = 'ignored' --[[SOL OUTPUT--]] 106 | else 107 | U.serialize_to_rope(rope, v, ignore_set, scope_indent, discovered) --[[SOL OUTPUT--]] 108 | end --[[SOL OUTPUT--]] 109 | 110 | rope [ # rope + 1 ] = ",\n" --[[SOL OUTPUT--]] 111 | end --[[SOL OUTPUT--]] 112 | end --[[SOL OUTPUT--]] 113 | end --[[SOL OUTPUT--]] 114 | rope [ # rope + 1 ] = indent .. "}" --[[SOL OUTPUT--]] 115 | elseif type(val) == "string" then 116 | rope [ # rope + 1 ] = string.format("%q", val) --[[SOL OUTPUT--]] 117 | elseif type(val) == "number" or type(val) == "boolean" then 118 | rope [ # rope + 1 ] = tostring(val) --[[SOL OUTPUT--]] 119 | else 120 | --error("serialize: Can't serialize something of type " .. type(val)) 121 | rope [ # rope + 1 ] = tostring(val) --[[SOL OUTPUT--]] 122 | end --[[SOL OUTPUT--]] 123 | end --[[SOL OUTPUT--]] 124 | 125 | 126 | function U.serialize(val, ignore_set) 127 | local rope = {} --[[SOL OUTPUT--]] 128 | U.serialize_to_rope(rope, val, ignore_set, nil, nil) --[[SOL OUTPUT--]] 129 | local str = table.concat(rope) --[[SOL OUTPUT--]] 130 | return str --[[SOL OUTPUT--]] 131 | end --[[SOL OUTPUT--]] 132 | 133 | 134 | function U.pretty(arg) 135 | return U.serialize(arg) --[[SOL OUTPUT--]] 136 | end --[[SOL OUTPUT--]] 137 | 138 | local ESCAPE_LOOKUP = { ['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'" } --[[SOL OUTPUT--]] 139 | 140 | function U.escape(str) 141 | if true then 142 | return string.format('%q', str) --[[SOL OUTPUT--]] 143 | else 144 | local ret = '' --[[SOL OUTPUT--]] 145 | for i=1,#str do 146 | local c = str:sub(i,i) --[[SOL OUTPUT--]] -- TODO: var 147 | ret = ret .. ( ESCAPE_LOOKUP[c] or c ) --[[SOL OUTPUT--]] 148 | end --[[SOL OUTPUT--]] 149 | return ret --[[SOL OUTPUT--]] 150 | end --[[SOL OUTPUT--]] 151 | end --[[SOL OUTPUT--]] 152 | 153 | function U.unescape(str) 154 | -- FIXME: unescape is unsafe 155 | return load("return "..str)() --[[SOL OUTPUT--]] 156 | end --[[SOL OUTPUT--]] 157 | 158 | ------------------------------------------------------ 159 | 160 | function U.trim(str) 161 | return str:gsub("^%s*(.-)%s*$", "%1") --[[SOL OUTPUT--]] 162 | end --[[SOL OUTPUT--]] 163 | 164 | -- U.INDENTATION = ' ' 165 | U.INDENTATION = '\t' --[[SOL OUTPUT--]] 166 | 167 | function U.indent(str) 168 | return U.INDENTATION .. str:gsub("\n", "\n" .. U.INDENTATION) --[[SOL OUTPUT--]] 169 | end --[[SOL OUTPUT--]] 170 | 171 | function U.quote_or_indent(str) 172 | str = U.trim(str) --[[SOL OUTPUT--]] 173 | if str:find('\n') then 174 | return '\n\n' .. U.indent( str ) .. '\n\n' --[[SOL OUTPUT--]] 175 | else 176 | return "'"..str.."'" --[[SOL OUTPUT--]] 177 | end --[[SOL OUTPUT--]] 178 | end --[[SOL OUTPUT--]] 179 | 180 | 181 | function U.printf(fmt, ...) 182 | print(string.format(fmt, ...)) --[[SOL OUTPUT--]] 183 | end --[[SOL OUTPUT--]] 184 | 185 | 186 | function U.ellipsis(msg, max_len) 187 | max_len = max_len or 2048 --[[SOL OUTPUT--]] 188 | 189 | if #msg <= max_len then 190 | return msg --[[SOL OUTPUT--]] 191 | else 192 | --return msg:sub(1, max_len/2) .. ' [...] ' .. msg:sub(-max_len/2) 193 | return msg:sub(1, max_len/2) .. '\n[...]\n' .. msg:sub(-max_len/2) --[[SOL OUTPUT--]] 194 | end --[[SOL OUTPUT--]] 195 | end --[[SOL OUTPUT--]] 196 | 197 | 198 | function U.printf_err(fmt, ...) 199 | local msg = string.format(fmt, ...) --[[SOL OUTPUT--]] 200 | 201 | if g_one_line_errors then msg = msg:gsub("\n", " ") --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 202 | --msg = U.ellipsis(msg) 203 | 204 | io.stderr:write( msg .. '\n' ) --[[SOL OUTPUT--]] 205 | D.break_() --[[SOL OUTPUT--]] 206 | 207 | if g_break_on_error then 208 | os.exit(1) --[[SOL OUTPUT--]] 209 | end --[[SOL OUTPUT--]] 210 | end --[[SOL OUTPUT--]] 211 | 212 | 213 | -- Returns the number of line breaks 214 | function U.count_line_breaks(str) 215 | if not str:find('\n') then 216 | -- Early out 217 | return 0 --[[SOL OUTPUT--]] 218 | end --[[SOL OUTPUT--]] 219 | 220 | local n = 0 --[[SOL OUTPUT--]] 221 | for i = 1,#str do 222 | if str:sub(i,i) == '\n' then 223 | n = n + ( 1 ) --[[SOL OUTPUT--]] 224 | end --[[SOL OUTPUT--]] 225 | end --[[SOL OUTPUT--]] 226 | return n --[[SOL OUTPUT--]] 227 | end --[[SOL OUTPUT--]] 228 | 229 | ------------------------------------------------------ 230 | -- Files: 231 | 232 | 233 | function U.file_exists(path) 234 | local f = io.open(path, "rb") --[[SOL OUTPUT--]] 235 | if f then 236 | f:close() --[[SOL OUTPUT--]] 237 | return true --[[SOL OUTPUT--]] 238 | else 239 | return false --[[SOL OUTPUT--]] 240 | end --[[SOL OUTPUT--]] 241 | end --[[SOL OUTPUT--]] 242 | 243 | function U.write_protect(path) 244 | if PLATFORM == "unix" then 245 | return 0 == os.execute("chmod -w " .. path) --[[SOL OUTPUT--]] 246 | else 247 | return 0 == os.execute("attrib +R " .. path) --[[SOL OUTPUT--]] 248 | end --[[SOL OUTPUT--]] 249 | end --[[SOL OUTPUT--]] 250 | 251 | 252 | function U.write_unprotect(path) 253 | if U.file_exists(path) then 254 | if PLATFORM == "unix" then 255 | return 0 == os.execute("chmod +w " .. path) --[[SOL OUTPUT--]] 256 | else 257 | return 0 == os.execute("attrib -R " .. path) --[[SOL OUTPUT--]] 258 | end --[[SOL OUTPUT--]] 259 | end --[[SOL OUTPUT--]] 260 | end --[[SOL OUTPUT--]] 261 | 262 | 263 | function U.read_entire_file(path) 264 | local f = io.open(path, "rb") --[[SOL OUTPUT--]] 265 | if not f then return nil --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 266 | local content = f:read("*all") --[[SOL OUTPUT--]] 267 | f:close() --[[SOL OUTPUT--]] 268 | content = content:gsub('\r', '') --[[SOL OUTPUT--]] -- Fixes sillyness on windows 269 | return content --[[SOL OUTPUT--]] 270 | end --[[SOL OUTPUT--]] 271 | 272 | 273 | function U.read_entire_stdin() 274 | return io.read("*all") --[[SOL OUTPUT--]] 275 | end --[[SOL OUTPUT--]] 276 | 277 | 278 | function U.write_file(path, contents) 279 | local f = io.open(path, "w") --[[SOL OUTPUT--]] 280 | if not f then return false --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 281 | f:write(contents) --[[SOL OUTPUT--]] 282 | f:close() --[[SOL OUTPUT--]] 283 | return true --[[SOL OUTPUT--]] 284 | end --[[SOL OUTPUT--]] 285 | 286 | 287 | ------------------------------------------------------ 288 | -- Tables and arrays etc: 289 | 290 | 291 | function U.is_array(val) 292 | if type(val) ~= "table" then 293 | return false --[[SOL OUTPUT--]] 294 | end --[[SOL OUTPUT--]] 295 | 296 | if getmetatable(val) ~= nil then 297 | return false --[[SOL OUTPUT--]] 298 | end --[[SOL OUTPUT--]] 299 | 300 | local max,n = 0,0 --[[SOL OUTPUT--]] 301 | 302 | for ix, _ in pairs(val) do 303 | if type(ix) ~= "number" or ix <= 0 or math.floor(ix) ~= ix then 304 | return false --[[SOL OUTPUT--]] 305 | end --[[SOL OUTPUT--]] 306 | 307 | max = math.max(max, ix) --[[SOL OUTPUT--]] 308 | n = n + ( 1 ) --[[SOL OUTPUT--]] 309 | end --[[SOL OUTPUT--]] 310 | 311 | return n == max --[[SOL OUTPUT--]] 312 | end --[[SOL OUTPUT--]] 313 | 314 | 315 | function U.set(tb) 316 | local set = {} --[[SOL OUTPUT--]] 317 | for _,v in ipairs(tb) do 318 | set[v] = true --[[SOL OUTPUT--]] 319 | end --[[SOL OUTPUT--]] 320 | return set --[[SOL OUTPUT--]] 321 | end --[[SOL OUTPUT--]] 322 | 323 | 324 | function U.set_join(...) 325 | local ret_set = {} --[[SOL OUTPUT--]] 326 | for _,set in ipairs{...} do 327 | for elem in pairs(set) do 328 | ret_set[elem] = true --[[SOL OUTPUT--]] 329 | end --[[SOL OUTPUT--]] 330 | end --[[SOL OUTPUT--]] 331 | return ret_set --[[SOL OUTPUT--]] 332 | end --[[SOL OUTPUT--]] 333 | 334 | 335 | function U.list_join(out, in_table) 336 | for _,val in ipairs(in_table) do 337 | out [ # out + 1 ] = val --[[SOL OUTPUT--]] 338 | end --[[SOL OUTPUT--]] 339 | end --[[SOL OUTPUT--]] 340 | 341 | 342 | function U.list_concat(a, b) 343 | local ret = {} --[[SOL OUTPUT--]] 344 | for _,v in ipairs(a) do 345 | ret [ # ret + 1 ] = v --[[SOL OUTPUT--]] 346 | end --[[SOL OUTPUT--]] 347 | for _,v in ipairs(b) do 348 | ret [ # ret + 1 ] = v --[[SOL OUTPUT--]] 349 | end --[[SOL OUTPUT--]] 350 | return ret --[[SOL OUTPUT--]] 351 | end --[[SOL OUTPUT--]] 352 | 353 | 354 | function U.table_empty(t) 355 | return next(t) == nil and getmetatable(t) == nil --[[SOL OUTPUT--]] 356 | end --[[SOL OUTPUT--]] 357 | 358 | 359 | function U.shallow_clone(t) 360 | if not t then return t --[[SOL OUTPUT--]] end --[[SOL OUTPUT--]] 361 | local t2 = {} --[[SOL OUTPUT--]] 362 | for k,v in pairs(t) do 363 | t2[k] = v --[[SOL OUTPUT--]] 364 | end --[[SOL OUTPUT--]] 365 | return t2 --[[SOL OUTPUT--]] 366 | end --[[SOL OUTPUT--]] 367 | 368 | function U.table_clear(t) 369 | for k,_ in pairs(t) do 370 | t[k] = nil --[[SOL OUTPUT--]] 371 | end --[[SOL OUTPUT--]] 372 | end --[[SOL OUTPUT--]] 373 | 374 | 375 | ------------------------------------------------------ 376 | 377 | function U.print_sorted_stats(map) 378 | local list = {} --[[SOL OUTPUT--]] 379 | local sum = 0.0 --[[SOL OUTPUT--]] 380 | 381 | for key,value in pairs(map) do 382 | list [ # list + 1 ] = { key = key, value = value } --[[SOL OUTPUT--]] 383 | sum = sum + ( value ) --[[SOL OUTPUT--]] 384 | end --[[SOL OUTPUT--]] 385 | 386 | table.sort(list, function(a,b) return a.value>b.value --[[SOL OUTPUT--]] end) --[[SOL OUTPUT--]] 387 | 388 | for _,v in ipairs(list) do 389 | U.printf("%24s : %5d %4.1f %%", "'"..v.key.."'", v.value, 100*v.value/sum) --[[SOL OUTPUT--]] 390 | end --[[SOL OUTPUT--]] 391 | end --[[SOL OUTPUT--]] 392 | 393 | 394 | ------------------------------------------------------ 395 | -- TODO: only in debug/development 396 | local DEBUG = true --[[SOL OUTPUT--]] 397 | 398 | -- Returns a write-protected version of the input table 399 | function U.const(table) 400 | if DEBUG then 401 | assert(getmetatable(table) == nil) --[[SOL OUTPUT--]] 402 | 403 | return setmetatable({ 404 | __protected = table -- Visible in debugger 405 | }, { 406 | __index = table, 407 | __newindex = function(_,_,_) -- table, key, value 408 | D.error("Attempt to modify read-only table") --[[SOL OUTPUT--]] 409 | end, 410 | __metatable = 'This is a read-only table' -- disallow further meta-tabling 411 | }) --[[SOL OUTPUT--]] 412 | else 413 | return table --[[SOL OUTPUT--]] 414 | end --[[SOL OUTPUT--]] 415 | end --[[SOL OUTPUT--]] 416 | 417 | -- Write-protects existing table against all modification 418 | function U.make_const(table) 419 | if DEBUG then 420 | assert(getmetatable(table) == nil) --[[SOL OUTPUT--]] 421 | 422 | local clone = U.shallow_clone(table) --[[SOL OUTPUT--]] 423 | 424 | U.table_clear(table) --[[SOL OUTPUT--]] 425 | 426 | table.__protected = clone --[[SOL OUTPUT--]] -- Visible in debugger 427 | 428 | setmetatable(table, { 429 | __index = clone, 430 | __newindex = function(_,_,_) -- table, key, value 431 | D.error("Attempt to modify read-only table") --[[SOL OUTPUT--]] 432 | end, 433 | __metatable = 'This is a read-only table' -- disallow further meta-tabling 434 | }) --[[SOL OUTPUT--]] 435 | end --[[SOL OUTPUT--]] 436 | end --[[SOL OUTPUT--]] 437 | 438 | ------------------------------------------------------ 439 | 440 | function U.is_constant_name(name) 441 | return name:match("^[_A-Z][_A-Z0-9]+$") --[[SOL OUTPUT--]] 442 | end --[[SOL OUTPUT--]] 443 | 444 | return U --[[SOL OUTPUT--]] 445 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /install_rocks.sh: -------------------------------------------------------------------------------- 1 | luarocks install luafilesystem 2 | luarocks install penlight 3 | -------------------------------------------------------------------------------- /multiple_throwaway.lua: -------------------------------------------------------------------------------- 1 | --[[ DO NOT MODIFY - COMPILED FROM .\tests\multiple_throwaway.sol --]] local function foo() 2 | return true, 42, "hello", {42} --[[SOL OUTPUT--]] 3 | end --[[SOL OUTPUT--]] 4 | 5 | _,_,_,_ = foo() --[[SOL OUTPUT--]] 6 | --[[SOL OUTPUT--]] -------------------------------------------------------------------------------- /profile.lua: -------------------------------------------------------------------------------- 1 | -- Allow user to pass in things like -s (spam) and -d (debug), -e0 (force build) 2 | local solc_args = '' 3 | for _,a in ipairs(arg) do 4 | solc_args = solc_args .. ' ' .. a 5 | end 6 | 7 | local interpreter = ('"'..arg[-1]..'"' or 'luajit') 8 | 9 | os.execute(interpreter .. " install/solc.lua --profile "..solc_args.." -o build sol/*.sol") 10 | -------------------------------------------------------------------------------- /run_tests.lua: -------------------------------------------------------------------------------- 1 | io.stdout:setvbuf 'no' 2 | 3 | local lfs = require 'lfs' -- luarocks install luafilesystem 4 | local path = require 'pl.path' -- luarocks install penlight 5 | 6 | local rel_path = path.dirname(arg[0]) 7 | local sol_dir = lfs.currentdir() .. '/' 8 | if rel_path ~= "" then 9 | sol_dir = sol_dir .. rel_path .. '/' 10 | end 11 | 12 | ------------------------------------------- 13 | 14 | local PLATFORM = os.getenv("windir") and "win" or "unix" 15 | 16 | local NULL_PIPE 17 | 18 | if PLATFORM == 'unix' then 19 | NULL_PIPE = ' &> /dev/null' 20 | else 21 | NULL_PIPE = ' > nul 2>&1' 22 | end 23 | 24 | 25 | local DIVISION = '\n--------------------------\n' 26 | 27 | local failed_to_pass = 0 28 | local failed_to_fail = 0 29 | local numTested = 0 30 | 31 | --print('arg[0] == ' .. arg[0]) -- ../run_tests.lua 32 | --print('arg[-1] == ' .. arg[-1]) -- lua or luajit 33 | local interpreter = ('"'..arg[-1]..'"' or 'luajit') .. ' ' .. sol_dir .. 'install/solc.lua ' 34 | 35 | --[[ 36 | print("PLATFORM: "..PLATFORM) 37 | print("NULL_PIPE: "..NULL_PIPE) 38 | print("rel_path: "..rel_path) 39 | print("sol_dir: "..sol_dir) 40 | print("interpreter: "..interpreter) 41 | --]] 42 | 43 | local spam = (arg[1] == '-s') 44 | 45 | function os_execute(cmd) 46 | local result = os.execute(cmd) 47 | return result == true or result == 0 48 | end 49 | 50 | local function test_dir(dir) 51 | for file in lfs.dir(dir) do 52 | if path.extension(file) == '.sol' then 53 | numTested = numTested + 1 54 | 55 | --print('Testing: ' .. file .. '...') 56 | local shouldPass = true 57 | if file:find('FAIL') then 58 | shouldPass = false 59 | end 60 | 61 | --lfs.chdir(sol_dir .. 'install/') 62 | local file_path = dir .. '/' .. file 63 | local passed = os_execute( interpreter .. ' -o tests_built ' .. file_path .. NULL_PIPE) 64 | 65 | if shouldPass and not passed then 66 | failed_to_pass = failed_to_pass + 1 67 | print("Test should have passed but didn't: " .. file) 68 | print(DIVISION) 69 | os_execute( interpreter .. ' -o tests_built ' .. file_path ) -- Standard 70 | print(DIVISION) 71 | if spam then 72 | os_execute( interpreter .. ' -d -o tests_built -s ' .. dir .. '/' .. file ) -- Extra spam 73 | print(DIVISION) 74 | end 75 | end 76 | 77 | if not shouldPass and passed then 78 | failed_to_fail = failed_to_fail + 1 79 | print("Test should have failed but passed: " .. file) 80 | print(DIVISION) 81 | if spam then 82 | os_execute( interpreter .. ' -p -s ' .. dir .. '/' .. file ) -- Extra spam 83 | print(DIVISION) 84 | end 85 | end 86 | 87 | --print(DIVISION) 88 | end 89 | end 90 | end 91 | 92 | --print(DIVISION) 93 | test_dir(sol_dir .. 'tests') 94 | 95 | local numBad = failed_to_pass + failed_to_fail 96 | 97 | if numBad == 0 then 98 | print('All ' .. numTested .. ' tests passed') 99 | os.exit(0) 100 | else 101 | print('ERROR: ' .. numBad .. ' / ' .. numTested .. ' tests failed') 102 | os.exit(-1) 103 | end 104 | -------------------------------------------------------------------------------- /sol.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": "sol" 7 | }, 8 | { 9 | "follow_symlinks": true, 10 | "path": "tests" 11 | }, 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /sol/ast.sol: -------------------------------------------------------------------------------- 1 | -- aux-functions for ast:s 2 | 3 | local P = require 'parser' -- TODO: make it the other way around 4 | local D = require 'sol_debug' 5 | 6 | local AST = {} 7 | 8 | -- Are two AST:s equal? Good for detecting stupid stuff like a = a 9 | -- Assummes same scope etc 10 | function AST.eq(a: P.Node, b: P.Node) -> bool 11 | if a == b then return true end -- Not sure when this could happen 12 | 13 | if a.ast_type ~= b.ast_type then 14 | return false 15 | end 16 | 17 | var ast_type = a.ast_type 18 | 19 | if ast_type == 'IdExpr' then 20 | return a.name == b.name 21 | 22 | elseif ast_type == 'MemberExpr' then 23 | D.break_() 24 | return a.ident.data == b.ident.data and a.indexer == b.indexer and AST.eq(a.base, b.base) 25 | 26 | -- TODO: more ast nodes than two =) 27 | 28 | else 29 | return false 30 | end 31 | end 32 | 33 | return AST 34 | -------------------------------------------------------------------------------- /sol/class.sol: -------------------------------------------------------------------------------- 1 | global function sol_class(klass_name, super_name) 2 | assert(klass_name, "You must specify a class name") 3 | 4 | if super_name ~= nil then 5 | assert(rawget(_G, super_name), "Undefined super class " .. super_name) 6 | end 7 | 8 | local klass = rawget(_G, klass_name) 9 | if not klass then 10 | klass = {} 11 | 12 | local instance_meta = { __index = klass } 13 | 14 | local construct = function(instance, ...) 15 | if instance then 16 | -- Clear-out: 17 | for k,_ in pairs(instance) do 18 | instance[k] = nil 19 | end 20 | else 21 | instance = {} 22 | end 23 | 24 | setmetatable(instance, instance_meta) 25 | 26 | if instance.init then instance:init(...) end 27 | return instance 28 | end 29 | 30 | local klass_meta = { 31 | -- Constructor style call, i.e. ClassName(...) 32 | __call = function(self, ...) 33 | return construct(nil, ...) 34 | end 35 | } 36 | 37 | if super_name ~= nil then 38 | local super = rawget(_G, super_name) 39 | klass_meta.__index = super 40 | klass.base_class = super 41 | end 42 | 43 | setmetatable(klass, klass_meta) 44 | 45 | -- Placement new: 46 | klass._construct = function(instance, ...) 47 | return construct(instance, ...) 48 | end 49 | 50 | klass.class_name = klass_name 51 | 52 | -- if some_obj:isa(Widget) then ... 53 | function klass:isa( some_class ) 54 | local c = klass 55 | repeat 56 | if c == some_class then return true end 57 | c = c.base_class -- Walk up inheritence chain 58 | until not c 59 | return false 60 | end 61 | 62 | local info = debug.getinfo(2) 63 | klass.declaring_source_file = info.short_src or "No file found" 64 | end 65 | 66 | return klass 67 | end 68 | -------------------------------------------------------------------------------- /sol/edit_distance.sol: -------------------------------------------------------------------------------- 1 | -- From http://nayruden.com/?p=115 - https://gist.github.com/Nayruden/427389 2 | -- Translated to Sol by Emil Ernerfeldt in 2013 3 | --[[ 4 | Function: EditDistance 5 | 6 | Finds the edit distance between two strings or tables. Edit distance is the minimum number of 7 | edits needed to transform one string or table into the other. 8 | 9 | Parameters: 10 | 11 | s - A *string* or *table*. 12 | t - Another *string* or *table* to compare against s. 13 | lim - An *optional number* to limit the function to a maximum edit distance. If specified 14 | and the function detects that the edit distance is going to be larger than limit, limit 15 | is returned immediately. 16 | 17 | Returns: 18 | 19 | A *number* specifying the minimum edits it takes to transform s into t or vice versa. Will 20 | not return a higher number than lim, if specified. 21 | 22 | Example: 23 | 24 | :EditDistance( "Tuesday", "Teusday" ) -- One transposition. 25 | :EditDistance( "kitten", "sitting" ) -- Two substitutions and a deletion. 26 | 27 | returns... 28 | 29 | :1 30 | :3 31 | 32 | Notes: 33 | 34 | * Complexity is O( (#t+1) * (#s+1) ) when lim isn't specified. 35 | * This function can be used to compare array-like tables as easily as strings. 36 | * The algorithm used is Damerau–Levenshtein distance, which calculates edit distance based 37 | off number of subsitutions, additions, deletions, and transpositions. 38 | * Source code for this function is based off the Wikipedia article for the algorithm 39 | . 40 | * This function is case sensitive when comparing strings. 41 | * If this function is being used several times a second, you should be taking advantage of 42 | the lim parameter. 43 | * Using this function to compare against a dictionary of 250,000 words took about 0.6 44 | seconds on my machine for the word "Teusday", around 10 seconds for very poorly 45 | spelled words. Both tests used lim. 46 | 47 | Revisions: 48 | 49 | v1.00 - Initial. 50 | ]] 51 | local function edit_distance( s: string or [int], t: string or [int], lim: int? ) -> int 52 | var s_len, t_len = #s, #t -- Calculate the sizes of the strings or arrays 53 | if lim and math.abs( s_len - t_len ) >= lim then -- If sizes differ by lim, we can stop here 54 | return lim 55 | end 56 | 57 | -- Convert string arguments to arrays of ints (ASCII values) 58 | if type( s ) == "string" then 59 | s = { string.byte( s, 1, s_len ) } 60 | end 61 | 62 | if type( t ) == "string" then 63 | t = { string.byte( t, 1, t_len ) } 64 | end 65 | 66 | var min = math.min : function(...:int)->int -- Localize for performance 67 | var num_columns = t_len + 1 -- We use this a lot 68 | 69 | var d = {} : [int] -- (s_len+1) * (t_len+1) is going to be the size of this array 70 | -- This is technically a 2D array, but we're treating it as 1D. Remember that 2D access in the 71 | -- form my_2d_array[ i, j ] can be converted to my_1d_array[ i * num_columns + j ], where 72 | -- num_columns is the number of columns you had in the 2D array assuming row-major order and 73 | -- that row and column indices start at 0 (we're starting at 0). 74 | 75 | for i=0, s_len do 76 | d[ i * num_columns ] = i -- Initialize cost of deletion 77 | end 78 | for j=0, t_len do 79 | d[ j ] = j -- Initialize cost of insertion 80 | end 81 | 82 | for i=1, s_len do 83 | var i_pos = i * num_columns 84 | var best = lim -- Check to make sure something in this row will be below the limit 85 | for j=1, t_len do 86 | var add_cost = (s[ i ] ~= t[ j ] and 1 or 0) 87 | var val = min( 88 | d[ i_pos - num_columns + j ] + 1, -- Cost of deletion 89 | d[ i_pos + j - 1 ] + 1, -- Cost of insertion 90 | d[ i_pos - num_columns + j - 1 ] + add_cost -- Cost of substitution, it might not cost anything if it's the same 91 | ) 92 | d[ i_pos + j ] = val 93 | 94 | -- is this eligible for tranposition? 95 | if i > 1 and j > 1 and s[ i ] == t[ j - 1 ] and s[ i - 1 ] == t[ j ] then 96 | d[ i_pos + j ] = min( 97 | val, -- Current cost 98 | d[ i_pos - num_columns - num_columns + j - 2 ] + add_cost -- Cost of transposition 99 | ) 100 | end 101 | 102 | if lim and val < best then 103 | best = val 104 | end 105 | end 106 | 107 | if lim and best >= lim then 108 | return lim 109 | end 110 | end 111 | 112 | return d[ #d ] 113 | end 114 | 115 | return edit_distance 116 | -------------------------------------------------------------------------------- /sol/globals.sol: -------------------------------------------------------------------------------- 1 | -- Globals settings - set by solc 2 | 3 | global g_local_parse = false -- If true, ignore 'require' 4 | global g_spam = false 5 | global g_ignore_errors = false 6 | global g_break_on_error = false 7 | global g_warnings_as_errors = false 8 | global g_write_timings = false 9 | global g_print_stats = false -- Prints out stats on the popularity of tokens and ast_type:s 10 | global g_one_line_errors = false -- Print errors and warnigns on single lines 11 | 12 | -- Output options: 13 | global g_align_lines = false -- Align line numbers in output? 14 | global g_warn_output = false -- Print --[[SOL OUTPUT--]] on each line in output file? 15 | -------------------------------------------------------------------------------- /sol/lexer.sol: -------------------------------------------------------------------------------- 1 | require 'globals' -- g_write_timings 2 | local U = require 'util' 3 | local D = require 'sol_debug' 4 | local set = U.set 5 | 6 | var LOWER_CHARS = set{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 7 | 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 8 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} 9 | var UPPER_CHARS = set{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 10 | 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 11 | 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} 12 | var DIGITS = set{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} 13 | var HEX_DIGITS = set{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 14 | 'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'} 15 | 16 | var IDENT_START_CHARS = U.set_join(LOWER_CHARS, UPPER_CHARS, set{'_'}) 17 | var IDENT_CHARS = U.set_join(IDENT_START_CHARS, DIGITS) 18 | 19 | 20 | -- Stats: 21 | var g_type_to_count = {} : {string => uint} 22 | var g_symbol_to_count = {} : {string => uint} 23 | var g_keyword_to_count = {} : {string => uint} 24 | 25 | 26 | local L = {} 27 | 28 | typedef TokID = 'Keyword' or 'Ident' or 'Number' or 'String' or 'Symbol' or 'Eof' 29 | 30 | typedef Token = { 31 | type : TokID, 32 | data : string?, 33 | line : int?, 34 | char : int?, 35 | leading_white : string? -- TODO: no '?' 36 | } 37 | 38 | typedef L.Token = Token 39 | typedef L.TokenList = [L.Token] 40 | 41 | 42 | local function extract_chars(str: string) -> [string] 43 | var chars = {} : [string] 44 | if true then 45 | -- Fastest 46 | for i = 1, #str do 47 | chars #= str:sub(i,i) 48 | end 49 | elseif true then 50 | str:gsub(".", function(c) 51 | chars #= c 52 | end) 53 | else 54 | for chr in str:gmatch(".") do 55 | chars #= chr 56 | end 57 | end 58 | assert(#chars == #str) 59 | 60 | -- Signal eof: 61 | chars #= '' 62 | chars #= '' 63 | chars #= '' 64 | 65 | return chars 66 | end 67 | 68 | 69 | -- The settings are found in Parser.sol 70 | function L.lex_sol(src: string, filename: string, settings) -> bool, any 71 | local tic = os.clock() 72 | assert(type(src) == 'string') 73 | 74 | local chars = extract_chars(src) 75 | 76 | local symbols = settings.symbols 77 | local keywords = settings.keywords 78 | 79 | --token dump 80 | var tokens = {} : [L.Token] 81 | 82 | local function local_lexer() -> void 83 | --line / char / pointer tracking 84 | local line, char, p = 1,1,1 85 | 86 | --get / peek functions 87 | local function get() -> string 88 | --local c = src:sub(p,p) 89 | local c = chars[p] 90 | if c == '\n' then 91 | char = 1 92 | line += 1 93 | else 94 | char += 1 95 | end 96 | p += 1 97 | return c 98 | end 99 | 100 | local function peek(n: int) -> string 101 | return chars[p+n] 102 | end 103 | 104 | local function consume(any_of_these: string) -> string? 105 | local c = chars[p] 106 | for i = 1, #any_of_these do 107 | if c == any_of_these:sub(i,i) then 108 | return get() 109 | end 110 | end 111 | return nil 112 | end 113 | 114 | --shared stuff 115 | local function report_lexer_error(err: string, level: int?) 116 | D.break_() 117 | level = level or 0 118 | --return error(">> :"..line..":"..char..": "..err, level) 119 | return error(filename..':'..line..': '..err, level) 120 | end 121 | 122 | -- returns content, long 123 | local function try_get_long_string() -> string?, string? 124 | local start = p 125 | if chars[p] == '[' then 126 | local equals_count = 0 127 | local depth = 1 128 | while peek(equals_count+1) == '=' do 129 | equals_count += 1 130 | end 131 | if peek(equals_count+1) == '[' then 132 | --start parsing the string. Strip the starting bit 133 | for _ = 0, equals_count+1 do get() end 134 | 135 | --get the contents 136 | local content_start = p 137 | while true do 138 | --check for eof 139 | if chars[p] == '' then 140 | return report_lexer_error("Expected `]"..string.rep('=', equals_count).."]` near .", 3) 141 | end 142 | 143 | --check for the end 144 | local found_end = true 145 | if chars[p] == ']' then 146 | for i = 1, equals_count do 147 | if peek(i) ~= '=' then found_end = false end 148 | end 149 | if peek(equals_count+1) ~= ']' then 150 | found_end = false 151 | end 152 | else 153 | if chars[p] == '[' then 154 | -- is there an embedded long string? 155 | local embedded = true 156 | for i = 1, equals_count do 157 | if peek(i) ~= '=' then 158 | embedded = false 159 | break 160 | end 161 | end 162 | if peek(equals_count + 1) == '[' and embedded then 163 | -- oh look, there was 164 | depth += 1 165 | for i = 1, (equals_count + 2) do 166 | get() 167 | end 168 | end 169 | end 170 | found_end = false 171 | end 172 | -- 173 | if found_end then 174 | depth -= 1 175 | if depth == 0 then 176 | break 177 | else 178 | for i = 1, equals_count + 2 do 179 | get() 180 | end 181 | end 182 | else 183 | get() 184 | end 185 | end 186 | 187 | --get the interior string 188 | local content_string = src:sub(content_start, p-1) 189 | 190 | --found the end. get rid of the trailing bit 191 | for i = 0, equals_count+1 do get() end 192 | 193 | --get the exterior string 194 | local long_string = src:sub(start, p-1) 195 | 196 | --return the stuff 197 | return content_string, long_string 198 | else 199 | return nil, nil 200 | end 201 | else 202 | return nil, nil 203 | end 204 | end 205 | 206 | 207 | local function get_leading_white() -> string 208 | local start = p 209 | 210 | while true do 211 | local c = chars[p] 212 | 213 | if c == ' ' or c == '\t' or c == '\n' or c == '\r' then 214 | get() 215 | 216 | elseif c == '-' and peek(1) == '-' then 217 | --comment 218 | get() 219 | get() 220 | local _, whole_text = try_get_long_string() 221 | 222 | if not whole_text then 223 | repeat 224 | local n = get() 225 | until n == '\n' or n == '' 226 | end 227 | 228 | elseif line == 1 and c == '#' and peek(1) == '!' then 229 | -- #! shebang 230 | get() 231 | get() 232 | repeat 233 | local n = get() 234 | until n == '\n' or n == '' 235 | 236 | else 237 | break 238 | end 239 | end 240 | 241 | return src:sub(start, p-1) 242 | end 243 | 244 | 245 | --main token emitting loop 246 | while true do 247 | --get leading whitespace. The leading whitespace will include any comments 248 | --preceding the token. This prevents the parser needing to deal with comments 249 | --separately. 250 | --local leading_white, leading_tokens = get_leading_white_old() 251 | local leading_white = get_leading_white() 252 | 253 | --get the initial char 254 | local this_line = line 255 | local this_char = char 256 | local c = chars[p] 257 | 258 | --symbol to emit 259 | var to_emit = nil : Token? 260 | 261 | --branch on type 262 | if c == '' then 263 | --eof 264 | to_emit = { type = 'Eof' } 265 | 266 | elseif IDENT_START_CHARS[c] then 267 | --ident or keyword 268 | local start = p 269 | repeat 270 | get() 271 | c = chars[p] 272 | until not IDENT_CHARS[c] 273 | local dat = src:sub(start, p-1) 274 | if keywords[dat] then 275 | to_emit = {type = 'Keyword', data = dat} 276 | else 277 | to_emit = {type = 'Ident', data = dat} 278 | end 279 | 280 | elseif DIGITS[c] or (chars[p] == '.' and DIGITS[peek(1)]) then 281 | --number const 282 | local start = p 283 | if c == '0' and peek(1) == 'x' then 284 | get() -- 0 285 | get() -- x 286 | while HEX_DIGITS[chars[p]] do get() end 287 | if consume('Pp') then 288 | consume('+-') 289 | while DIGITS[chars[p]] do get() end 290 | end 291 | else 292 | while DIGITS[chars[p]] do get() end 293 | if consume('.') then 294 | while DIGITS[chars[p]] do get() end 295 | end 296 | if consume('Ee') then 297 | consume('+-') 298 | while DIGITS[chars[p]] do get() end 299 | end 300 | end 301 | to_emit = {type = 'Number', data = src:sub(start, p-1)} 302 | 303 | elseif c == '\'' or c == '\"' then 304 | local start = p 305 | --string const 306 | local delim = get() 307 | local content_start = p 308 | while true do 309 | local c = get() 310 | if c == '\\' then 311 | get() --get the escape char 312 | elseif c == delim then 313 | break 314 | elseif c == '' then 315 | return report_lexer_error("Unfinished string near ") 316 | end 317 | end 318 | local content = src:sub(content_start, p-2) 319 | local constant = src:sub(start, p-1) 320 | to_emit = {type = 'String', data = constant, Constant = content} 321 | 322 | elseif c == '[' then 323 | local content, wholetext = try_get_long_string() 324 | if wholetext then 325 | to_emit = {type = 'String', data = wholetext, Constant = content} 326 | else 327 | get() 328 | to_emit = {type = 'Symbol', data = '['} 329 | end 330 | 331 | elseif consume('.') then 332 | if consume('.') then 333 | if consume('.') then 334 | to_emit = {type = 'Symbol', data = '...'} 335 | elseif consume('=') then 336 | to_emit = {type = 'Symbol', data = '..='} 337 | else 338 | to_emit = {type = 'Symbol', data = '..'} 339 | end 340 | else 341 | to_emit = {type = 'Symbol', data = '.'} 342 | end 343 | 344 | elseif symbols[c .. peek(1)] then 345 | -- two-character symbol 346 | var symbol = get() 347 | symbol ..= get() 348 | to_emit = {type = 'Symbol', data = symbol} 349 | 350 | elseif symbols[c] then 351 | get() 352 | to_emit = {type = 'Symbol', data = c} 353 | 354 | else 355 | return report_lexer_error("Unexpected Symbol `"..c.."`.", 2) 356 | end 357 | 358 | --add the emitted symbol, after adding some common data 359 | --to_emit.lading_white_token_list = leading_tokens -- table of leading whitespace/comments 360 | to_emit.leading_white = leading_white 361 | --for k, tok in pairs(leading_tokens) do 362 | -- tokens #= tok 363 | --end 364 | 365 | to_emit.line = this_line 366 | to_emit.char = this_char 367 | tokens #= to_emit 368 | 369 | --halt after eof has been emitted 370 | if to_emit.type == 'Eof' then break end 371 | end 372 | end 373 | 374 | local st, err = pcall( local_lexer ) 375 | 376 | if not st then 377 | U.printf_err( "%s", err ) 378 | return false, err 379 | end 380 | 381 | ---------------------------------------- 382 | 383 | if g_print_stats then 384 | for _, tok in ipairs(tokens) do 385 | g_type_to_count[tok.type] = (g_type_to_count[tok.type] or 0) + 1 386 | if tok.type == 'Symbol' then 387 | g_symbol_to_count[tok.data] = (g_symbol_to_count[tok.data] or 0) + 1 388 | end 389 | if tok.type == 'Keyword' then 390 | g_keyword_to_count[tok.data] = (g_keyword_to_count[tok.data] or 0) + 1 391 | end 392 | end 393 | end 394 | 395 | ---------------------------------------- 396 | 397 | --public interface: 398 | local tok = {} 399 | local p = 1 400 | 401 | function tok:getp() -> int 402 | return p 403 | end 404 | 405 | function tok:setp(n: int) 406 | p = n 407 | end 408 | 409 | function tok:get_token_list() -> L.TokenList 410 | return tokens 411 | end 412 | 413 | --getters 414 | function tok:peek(n: int?) -> L.Token? 415 | if n then 416 | local ix = math.min(#tokens, p+n) 417 | return tokens[ix] 418 | else 419 | --return tokens[math.min(#tokens, p)] 420 | return tokens[p] 421 | end 422 | end 423 | 424 | function tok:get(token_list: L.TokenList?) -> L.Token? 425 | local t = tokens[p] 426 | p = math.min(p + 1, #tokens) 427 | if token_list then 428 | token_list #= t 429 | end 430 | return t 431 | end 432 | 433 | function tok:get_ident(token_list: L.TokenList?) -> string? 434 | if tok:is('Ident') then 435 | return tok:get(token_list).data 436 | else 437 | return nil 438 | end 439 | end 440 | 441 | function tok:is(t: TokID) -> bool 442 | return tokens[p].type == t 443 | end 444 | 445 | -- either cosumes and returns the given symbil if there is one, or nil 446 | function tok:consume_symbol(symb: string, token_list: L.TokenList?) -> L.Token? 447 | local t = self:peek() 448 | if t.type == 'Symbol' then 449 | if t.data == symb then 450 | return self:get(token_list) 451 | else 452 | return nil 453 | end 454 | else 455 | return nil 456 | end 457 | end 458 | 459 | function tok:consume_keyword(kw: string, token_list: L.TokenList?) -> L.Token? 460 | local t = self:peek() 461 | if t.type == 'Keyword' and t.data == kw then 462 | return self:get(token_list) 463 | else 464 | return nil 465 | end 466 | end 467 | 468 | function tok:is_keyword(kw: string) -> bool 469 | local t = tok:peek() 470 | return t.type == 'Keyword' and t.data == kw 471 | end 472 | 473 | function tok:is_symbol(s: string) -> bool 474 | local t = tok:peek() 475 | return t.type == 'Symbol' and t.data == s 476 | end 477 | 478 | function tok:is_eof() -> bool 479 | return tok:peek().type == 'Eof' 480 | end 481 | 482 | local toc = os.clock() 483 | if g_write_timings then 484 | U.printf("Lexing %s: %d tokens in %.1f ms", filename, #tokens, 1000*(toc-tic)) 485 | end 486 | 487 | return true, tok 488 | end 489 | 490 | 491 | function L.print_stats() 492 | U.printf("Token popularity:") 493 | U.print_sorted_stats(g_type_to_count) 494 | 495 | U.printf("Symbol popularity:") 496 | U.print_sorted_stats(g_symbol_to_count) 497 | 498 | U.printf("Keyword popularity:") 499 | U.print_sorted_stats(g_keyword_to_count) 500 | end 501 | 502 | 503 | return L 504 | -------------------------------------------------------------------------------- /sol/lua_intrinsics.sol: -------------------------------------------------------------------------------- 1 | var INTRINSICS = [[ 2 | global assert = extern : function(any, string?, ...) -> ... 3 | global collectgarbage = extern : function("collect" or "stop" or "restart" or "count" or "step" or "setpause" or "setstepmul" or "isrunning", any?) -> ... 4 | global dofile = extern : function(string?) -> ... 5 | global error = extern : function(any, int?) -> ... -- TODO: return never-type 6 | global print = extern : function(...) -> void 7 | global tonumber = extern : function(any, uint?) -> number? 8 | global tostring = extern : function(any, uint?) -> string 9 | global type = extern : function(any) -> "nil" or "number" or "string" or "boolean" or "table" or "function" or "thread" or "userdata" 10 | global unpack = extern : function([any]) -> ... 11 | 12 | global coroutine = { 13 | yield = extern : function(...) -> ...; 14 | wrap = extern : function(...) -> ...; 15 | } 16 | 17 | global debug = { 18 | getinfo = extern : function(...) -> ...; 19 | } 20 | 21 | global math = { 22 | min = extern : function(...: number) -> number; 23 | max = extern : function(...: number) -> number; 24 | abs = extern : function(number) -> number; 25 | ceil = extern : function(number) -> number; 26 | floor = extern : function(number) -> number; 27 | huge = extern : number; 28 | 29 | sqrt = extern : function(number) -> number; 30 | pow = extern : function(number, number) -> number; 31 | 32 | sin = extern : function(number) -> number; 33 | asin = extern : function(number) -> number; 34 | cos = extern : function(number) -> number; 35 | acos = extern : function(number) -> number; 36 | tan = extern : function(number) -> number; 37 | atan = extern : function(number) -> number; 38 | atan2 = extern : function(number, number) -> number; 39 | } 40 | 41 | global io = { 42 | open = extern : function(...) -> ...; 43 | read = extern : function(...) -> ...; 44 | stderr = extern; 45 | } 46 | 47 | global os = { 48 | exit = extern : function(...) -> ...; 49 | date = extern : function(...) -> ...; 50 | getenv = extern : function(...) -> ...; 51 | execute = extern : function(...) -> ...; 52 | 53 | clock = extern : function() -> number; 54 | } 55 | 56 | global package = { 57 | path = extern : string; 58 | } 59 | 60 | global string = { 61 | byte = extern : function(string, int, int?, int?) -> ...; 62 | char = extern : function(string, ... : int) -> string; 63 | format = extern : function(string, ...) -> string; 64 | rep = extern : function(...) -> ...; 65 | 66 | sub = extern : function(string, int, int?) -> string?; 67 | 68 | -- Patterns: 69 | gsub = extern : function(...) -> ...; 70 | find = extern : function(...) -> ...; 71 | match = extern : function(...) -> ...; 72 | gmatch = extern : function(...) -> ...; -- TODO: a generator 73 | } 74 | 75 | global table = { 76 | concat = extern : function(...) -> ...; 77 | insert = extern : function(...) -> ...; 78 | sort = extern : function(...) -> ...; 79 | remove = extern : function(...) -> ...; 80 | } 81 | ]] 82 | 83 | 84 | 85 | ---------------------------------------------- 86 | 87 | 88 | local P = require 'parser' 89 | local L = require 'lexer' 90 | local TypeCheck = require 'type_check' 91 | require 'scope' 92 | 93 | local M = {} 94 | 95 | function M.add_intrinsics_to_global_scope() 96 | var global_scope = Scope.get_global_scope() 97 | var module_scope = Scope.create_module_scope() 98 | var scope = module_scope 99 | 100 | var filename = "INTRINSICS" 101 | var settings = P.SOL_SETTINGS 102 | 103 | local st, tokens = L.lex_sol(INTRINSICS, filename, settings) 104 | assert(st, tokens) 105 | local st, ast = P.parse_sol(INTRINSICS, tokens, filename, settings, scope) 106 | assert(st, ast) 107 | local st, err = TypeCheck(ast, filename, nil, settings) 108 | assert(st, err) 109 | 110 | if not Scope.GLOBALS_IN_TOP_SCOPE then 111 | global_scope.fixed = false 112 | 113 | for _,v in ipairs(module_scope:get_global_vars()) do 114 | global_scope:add_global(v) 115 | end 116 | 117 | for name,type in pairs(module_scope:get_global_typedefs()) do 118 | global_scope:add_global_type( name, type ) 119 | end 120 | 121 | global_scope.fixed = true 122 | end 123 | end 124 | 125 | return M 126 | -------------------------------------------------------------------------------- /sol/output.sol: -------------------------------------------------------------------------------- 1 | require 'parser' 2 | local L = require 'lexer' -- L.Token 3 | local D = require 'sol_debug' 4 | local U = require 'util' 5 | local printf_err = U.printf_err 6 | local count_line_breaks = U.count_line_breaks 7 | 8 | local function debug_printf(...) 9 | --[[ 10 | U.printf(...) 11 | --]] 12 | end 13 | 14 | -- 15 | -- output.lua 16 | -- 17 | -- Returns the exact source code that was used to create an AST, preserving all 18 | -- comments and whitespace. 19 | -- This can be used to get back a Lua source after renaming some variables in 20 | -- an AST. 21 | -- 22 | 23 | 24 | assert(count_line_breaks("hello") == 0) 25 | assert(count_line_breaks("hello\n") == 1) 26 | assert(count_line_breaks("hello\nyou\ntoo") == 2) 27 | 28 | 29 | local function output(ast, filename: string, strip_white_space : bool?) -> string 30 | if strip_white_space == nil then 31 | strip_white_space = false 32 | end 33 | 34 | --[[ 35 | Set of tokens for which we have already written the leadign whitespace. 36 | We need to keep track of this since otherwise we would stuff like: 37 | 38 | 39 | -- Comment 40 | a += 1 41 | 42 | -> 43 | 44 | -- Comment 45 | a = 46 | -- Comment 47 | a + 1 48 | 49 | --]] 50 | var has_written_white = {} : {L.Token} 51 | 52 | local out = { 53 | rope = {}, -- List of strings 54 | line = 1, 55 | 56 | append_str = function(self, str: string) 57 | self.rope #= str 58 | self.line += count_line_breaks(str) 59 | end, 60 | 61 | append_token = function(self, token) 62 | self:append_white(token) 63 | 64 | local str = token.data 65 | 66 | if strip_white_space then 67 | self.rope #= str 68 | self.line += count_line_breaks(str) 69 | else 70 | local lines_in_str = count_line_breaks(str) 71 | 72 | if g_align_lines then 73 | while self.line + lines_in_str < token.line do 74 | --print("Inserting extra line") 75 | self.rope #= '\n' 76 | self.line += 1 77 | end 78 | end 79 | 80 | self.rope #= str 81 | self.line += lines_in_str 82 | end 83 | end, 84 | 85 | append_white = function(self, token) 86 | if not token.leading_white then 87 | return 88 | end 89 | if strip_white_space and #self.rope==0 then 90 | return 91 | end 92 | 93 | if has_written_white[token] then 94 | self:append_str( " " ) 95 | else 96 | self:append_str( token.leading_white ) 97 | has_written_white[token] = true 98 | end 99 | end 100 | } 101 | 102 | local function report_error(fmt: string, ...) 103 | local msg = string.format(fmt, ...) 104 | local error_msg = string.format('%s:%d: %s', filename, out.line, msg) 105 | printf_err( "%s\n%s", table.concat(out.rope), error_msg ) 106 | D.error(error_msg) 107 | end 108 | 109 | 110 | local format_statlist, format_expr 111 | 112 | var COMMA_SEMICOLON = U.set{",", ";"} 113 | var COMMA = U.set{","} 114 | 115 | -- returns a structure for iterating over tokens 116 | local function tokens(expr) 117 | local it = 1 118 | 119 | local t = {} 120 | 121 | function t:append_next_token(str: string?) 122 | --[[ 123 | if not tok then report_error("Missing token") end 124 | if str and tok.data ~= str then 125 | report_error("Expected token '" .. str .. "'. tokens: " .. U.pretty(expr.tokens)) 126 | end 127 | out:append_token( tok ) 128 | it += 1 129 | --]] 130 | if str then 131 | self:append_str(str) 132 | else 133 | local tok = expr.tokens[it] 134 | if not tok then report_error("Missing token") end 135 | if str and tok.data ~= str then 136 | report_error("Expected token '" .. str .. "'. tokens: " .. U.pretty(expr.tokens)) 137 | end 138 | out:append_token( tok ) 139 | it += 1 140 | end 141 | end 142 | function t:append_token(token: L.Token) 143 | out:append_token( token ) 144 | it += 1 145 | end 146 | function t:append_white_for_next_token() 147 | local tok = expr.tokens[it] 148 | if tok then 149 | out:append_white( tok ) 150 | end 151 | end 152 | function t:append_white() 153 | local tok = expr.tokens[it] 154 | if tok then 155 | out:append_white( tok ) 156 | it += 1 157 | else 158 | --report_error("Missing token") 159 | out:append_str(" ") 160 | end 161 | end 162 | function t:skip_next_token() 163 | self:append_white() 164 | end 165 | function t:append_str(str: string) 166 | self:append_white() 167 | out:append_str(str) 168 | end 169 | function t:inject_str(str: string) 170 | out:append_str(str) 171 | end 172 | function t:peek() -> string 173 | if it <= #expr.tokens then 174 | return expr.tokens[it].data 175 | end 176 | end 177 | function t:append_comma(mandatory: bool, seperators: {string}?) -> void 178 | if true then 179 | seperators = seperators or COMMA 180 | local peeked = self:peek() 181 | if not mandatory and not seperators[peeked] then 182 | return 183 | end 184 | if not seperators[peeked] then 185 | report_error("Missing comma or semicolon; next token is: %s, token iterator: %i, tokens: %s", 186 | U.pretty( peeked ), it, U.pretty( expr.tokens )) 187 | end 188 | self:append_next_token() 189 | else 190 | local p = self:peek() 191 | if p == "," or p == ";" then 192 | self:append_next_token() 193 | end 194 | end 195 | end 196 | 197 | function t:on_end() 198 | assert(it == #expr.tokens + 1) 199 | end 200 | 201 | return t 202 | end 203 | 204 | 205 | format_expr = function(expr) -> void 206 | D.assert(expr) 207 | D.assert(expr.ast_type) 208 | 209 | local t = tokens(expr) 210 | --debug_printf("format_expr(%s) at line %i", expr.ast_type, expr.tokens[1] and expr.tokens[1].line or -1) 211 | 212 | if expr.ast_type == 'IdExpr' then 213 | t:append_str( expr.name ) 214 | 215 | elseif expr.ast_type == 'NumberExpr' then 216 | t:append_str( expr.value ) 217 | 218 | elseif expr.ast_type == 'StringExpr' then 219 | t:append_str( expr.str_quoted ) 220 | 221 | elseif expr.ast_type == 'BooleanExpr' then 222 | t:append_next_token( expr.value and "true" or "false" ) 223 | 224 | elseif expr.ast_type == 'NilExpr' then 225 | t:append_next_token( "nil" ) 226 | 227 | elseif expr.ast_type == 'BinopExpr' then 228 | format_expr(expr.lhs) 229 | t:append_str( expr.op ) 230 | format_expr(expr.rhs) 231 | 232 | elseif expr.ast_type == 'UnopExpr' then 233 | t:append_str( expr.op ) 234 | format_expr(expr.rhs) 235 | 236 | elseif expr.ast_type == 'DotsExpr' then 237 | t:append_next_token( "..." ) 238 | 239 | elseif expr.ast_type == 'CallExpr' then 240 | format_expr(expr.base) 241 | t:append_next_token( "(" ) 242 | for i,arg in ipairs( expr.arguments ) do 243 | format_expr(arg) 244 | t:append_comma( i ~= #expr.arguments ) 245 | end 246 | t:append_next_token( ")" ) 247 | 248 | elseif expr.ast_type == 'TableCallExpr' then 249 | format_expr( expr.base ) 250 | format_expr( expr.arguments[1] ) 251 | 252 | elseif expr.ast_type == 'StringCallExpr' then 253 | format_expr(expr.base) 254 | --t:append_token( expr.arguments[1] ) 255 | format_expr( expr.arguments[1] ) 256 | 257 | elseif expr.ast_type == 'IndexExpr' then 258 | format_expr(expr.base) 259 | t:append_next_token( "[" ) 260 | format_expr(expr.index) 261 | t:append_next_token( "]" ) 262 | 263 | elseif expr.ast_type == 'MemberExpr' then 264 | format_expr(expr.base) 265 | t:append_next_token() -- . or : 266 | t:append_token(expr.ident) 267 | 268 | elseif expr.ast_type == 'LambdaFunctionExpr' then 269 | -- anonymous function 270 | t:append_next_token( "function" ) 271 | t:append_next_token( "(" ) 272 | if #expr.arguments > 0 then 273 | for i = 1, #expr.arguments do 274 | t:append_str( expr.arguments[i].name ) 275 | if i ~= #expr.arguments then 276 | t:append_next_token(",") 277 | elseif expr.vararg then 278 | t:append_next_token(",") 279 | t:append_next_token("...") 280 | end 281 | end 282 | elseif expr.vararg then 283 | t:append_next_token("...") 284 | end 285 | t:append_next_token(")") 286 | format_statlist(expr.body) 287 | t:append_next_token("end") 288 | 289 | elseif expr.ast_type == 'ConstructorExpr' then 290 | t:append_next_token( "{" ) 291 | for i = 1, #expr.entry_list do 292 | local entry = expr.entry_list[i] 293 | if entry.type == 'key' then 294 | t:append_next_token( "[" ) 295 | format_expr(entry.key) 296 | t:append_next_token( "]" ) 297 | t:append_next_token( "=" ) 298 | format_expr(entry.value) 299 | elseif entry.type == 'value' then 300 | format_expr(entry.value) 301 | elseif entry.type == 'ident_key' then 302 | t:append_str(entry.key) 303 | t:append_next_token( "=" ) 304 | format_expr(entry.value) 305 | end 306 | t:append_comma( i ~= #expr.entry_list, COMMA_SEMICOLON ) 307 | end 308 | t:append_next_token( "}" ) 309 | 310 | elseif expr.ast_type == 'ParenthesesExpr' then 311 | t:append_next_token( "(" ) 312 | format_expr(expr.inner) 313 | t:append_next_token( ")" ) 314 | 315 | elseif expr.ast_type == 'CastExpr' then 316 | format_expr(expr.expr) 317 | 318 | else 319 | printf_err("Unknown expr AST type: '%s'", expr.ast_type) 320 | end 321 | 322 | t:on_end() 323 | debug_printf("/format_expr") 324 | end 325 | 326 | 327 | local format_statement = function(stat) -> void 328 | local t = tokens(stat) 329 | 330 | --debug_printf("") 331 | --debug_printf(string.format("format_statement(%s) at line %i", stat.ast_type, stat.tokens and stat.tokens[1] and stat.tokens[1].line or -1)) 332 | 333 | 334 | -- Ordered by popularity 335 | if stat.ast_type == 'IfStatement' then 336 | t:append_next_token( "if" ) 337 | format_expr( stat.clauses[1].condition ) 338 | t:append_next_token( "then" ) 339 | format_statlist( stat.clauses[1].body ) 340 | for i = 2, #stat.clauses do 341 | local st = stat.clauses[i] 342 | if st.condition then 343 | t:append_next_token( "elseif" ) 344 | format_expr(st.condition) 345 | t:append_next_token( "then" ) 346 | else 347 | t:append_next_token( "else" ) 348 | end 349 | format_statlist(st.body) 350 | end 351 | t:append_next_token( "end" ) 352 | 353 | elseif stat.ast_type == 'VarDeclareStatement' then 354 | if t:peek() == "local" then 355 | t:append_next_token( "local" ) 356 | elseif t:peek() == "global" then 357 | t:skip_next_token() 358 | elseif t:peek() == "var" then 359 | --t:skip_next_token() 360 | t:append_str('local') 361 | end 362 | 363 | for i = 1, #stat.name_list do 364 | t:append_str( stat.name_list[i] ) 365 | t:append_comma( i ~= #stat.name_list ) 366 | end 367 | if #stat.init_list > 0 then 368 | t:append_next_token( "=" ) 369 | for i = 1, #stat.init_list do 370 | format_expr(stat.init_list[i]) 371 | t:append_comma( i ~= #stat.init_list ) 372 | end 373 | end 374 | 375 | elseif stat.ast_type == 'CallStatement' then 376 | format_expr(stat.expression) 377 | 378 | elseif stat.ast_type == 'AssignmentStatement' then 379 | for i,v in ipairs(stat.lhs) do 380 | format_expr(v) 381 | t:append_comma( i ~= #stat.lhs ) 382 | end 383 | if #stat.rhs > 0 then 384 | t:append_str( "=" ) 385 | for i,v in ipairs(stat.rhs) do 386 | format_expr(v) 387 | t:append_comma( i ~= #stat.rhs ) 388 | end 389 | end 390 | 391 | elseif stat.ast_type == 'ClassDeclStatement' then 392 | if stat.is_local then 393 | t:append_str( "local" ) -- replaces 'class' 394 | else 395 | t:skip_next_token() -- skip 'global' 396 | t:skip_next_token() -- skip 'class' 397 | end 398 | t:append_str( stat.name ) 399 | t:append_next_token( "=" ) 400 | format_expr(stat.rhs) 401 | 402 | elseif stat.ast_type == 'WhileStatement' then 403 | t:append_next_token( "while" ) 404 | format_expr(stat.condition) 405 | t:append_next_token( "do" ) 406 | format_statlist(stat.body) 407 | t:append_next_token( "end" ) 408 | 409 | elseif stat.ast_type == 'DoStatement' then 410 | t:append_next_token( "do" ) 411 | format_statlist(stat.body) 412 | t:append_next_token( "end" ) 413 | 414 | elseif stat.ast_type == 'GenericForStatement' then 415 | t:append_next_token( "for" ) 416 | for i,name in ipairs(stat.var_names) do 417 | t:append_str( name ) 418 | t:append_comma( i ~= #stat.var_names ) 419 | end 420 | t:append_next_token( "in" ) 421 | for i = 1, #stat.generators do 422 | format_expr(stat.generators[i]) 423 | t:append_comma( i ~= #stat.generators ) 424 | end 425 | t:append_next_token( "do" ) 426 | format_statlist(stat.body) 427 | t:append_next_token( "end" ) 428 | 429 | elseif stat.ast_type == 'NumericForStatement' then 430 | t:append_next_token( "for" ) 431 | t:append_str( stat.var_name ) 432 | t:append_next_token( "=" ) 433 | format_expr(stat.start) 434 | t:append_next_token( "," ) 435 | format_expr(stat.end_) 436 | if stat.step then 437 | t:append_next_token( "," ) 438 | format_expr(stat.step) 439 | end 440 | t:append_next_token( "do" ) 441 | format_statlist(stat.body) 442 | t:append_next_token( "end" ) 443 | 444 | elseif stat.ast_type == 'RepeatStatement' then 445 | t:append_next_token( "repeat" ) 446 | format_statlist(stat.body) 447 | t:append_next_token( "until" ) 448 | format_expr(stat.condition) 449 | 450 | elseif stat.ast_type == 'LabelStatement' then 451 | t:append_next_token( "::" ) 452 | t:append_str( stat.label ) 453 | t:append_next_token( "::" ) 454 | 455 | elseif stat.ast_type == 'GotoStatement' then 456 | t:append_next_token( "goto" ) 457 | t:append_str( stat.label ) 458 | 459 | elseif stat.ast_type == 'ReturnStatement' then 460 | t:append_next_token( "return" ) 461 | for i = 1, #stat.arguments do 462 | format_expr(stat.arguments[i]) 463 | t:append_comma( i ~= #stat.arguments ) 464 | end 465 | 466 | elseif stat.ast_type == 'BreakStatement' then 467 | t:append_next_token( "break" ) 468 | 469 | elseif stat.ast_type == 'FunctionDeclStatement' then 470 | if stat.scoping == 'local' then 471 | t:append_next_token( "local" ) 472 | elseif stat.scoping == 'global' then 473 | t:skip_next_token() 474 | elseif not stat.is_aggregate then 475 | -- Move forward before inserting local/global etc: 476 | t:append_white_for_next_token() 477 | t:inject_str('local') -- turn global function into local 478 | end 479 | t:append_next_token( "function" ) 480 | format_expr( stat.name_expr ) 481 | 482 | t:append_next_token( "(" ) 483 | if #stat.arguments > 0 then 484 | for i = 1, #stat.arguments do 485 | t:append_str( stat.arguments[i].name ) 486 | t:append_comma( i ~= #stat.arguments or stat.vararg ) 487 | if i == #stat.arguments and stat.vararg then 488 | t:append_next_token( "..." ) 489 | end 490 | end 491 | elseif stat.vararg then 492 | t:append_next_token( "..." ) 493 | end 494 | t:append_next_token( ")" ) 495 | 496 | format_statlist(stat.body) 497 | t:append_next_token( "end" ) 498 | 499 | elseif stat.ast_type == 'Eof' then 500 | t:append_white() 501 | 502 | elseif stat.ast_type == 'Typedef' then 503 | 504 | else 505 | printf_err("Unknown stat AST type: '%s'", stat.ast_type) 506 | end 507 | 508 | if stat.semicolon then 509 | t:append_next_token(";") 510 | end 511 | 512 | t:on_end() 513 | 514 | if g_warn_output then 515 | -- Ensure the lua code is easily spotted as something you shouldn't modify: 516 | out:append_str(" --[[SOL OUTPUT--]] ") 517 | end 518 | 519 | debug_printf("/format_statment") 520 | end 521 | 522 | 523 | format_statlist = function(stat_list) -> void 524 | for _, stat in ipairs(stat_list.body) do 525 | format_statement(stat) 526 | end 527 | end 528 | 529 | 530 | if U.is_array(ast.body) then 531 | format_statlist(ast) 532 | else 533 | format_expr(ast) 534 | end 535 | 536 | 537 | return table.concat(out.rope) 538 | end 539 | 540 | return output 541 | -------------------------------------------------------------------------------- /sol/scope.sol: -------------------------------------------------------------------------------- 1 | local T = require 'type' 2 | local D = require 'sol_debug' 3 | local U = require 'util' 4 | 5 | 6 | --[[ 7 | Scopes: 8 | 9 | At the top there is the shared and immutable 'global_scope'. This contains the lua-wide globals. 10 | When parsign a module, there is a 'module_scope' whose parent is the 'global_scope'. 11 | 12 | User declared globals goes into the 'module_scope' and are marked as 'global'. 13 | --]] 14 | 15 | --[-[ 16 | global class Scope = { 17 | -- TODO: static members here, i.e. global_scope 18 | global_scope = nil : Scope? -- not found in later lookup :T 19 | } 20 | 21 | function Scope.new(where: string, parent: Scope?) -> Scope 22 | --var s = {} : Scope 23 | local s = {} 24 | setmetatable(s, { __index = Scope }) 25 | s:init(where, parent) 26 | return s 27 | end 28 | --]] 29 | --[[ 30 | require 'class' 31 | global class Scope = sol_class("Scope") 32 | 33 | function Scope.new(where: string, parent: Scope?) -> Scope 34 | return Scope(where, parent) 35 | end 36 | --]] 37 | 38 | global typedef Variable = { 39 | scope : Scope, 40 | name : string, 41 | type : T.Type?, 42 | is_global : bool, 43 | is_constant : bool, 44 | namespace : { string => T.Type } ?, 45 | where : string, 46 | forward_declared : bool?, 47 | 48 | -- Usage statistics: 49 | num_reads : int, 50 | num_writes : int, 51 | var_type : 'Function' or 'Loop variable' or 'Argument' or 'Global variable' or 'Local variable' or nil 52 | } 53 | 54 | 55 | typedef VarOptions = 'ignore_fwd_decl' or nil 56 | 57 | 58 | Scope.GLOBALS_IN_TOP_SCOPE = true 59 | 60 | -------------------------------------------------- 61 | 62 | -- Constructor 63 | function Scope:init(where: string, parent: Scope?) 64 | self.where = where 65 | self.parent = parent 66 | self.children = {} : [Scope] 67 | self.locals = {} : { string => Variable } 68 | self.globals = {} : [Variable] -- TODO: string->var map 69 | self.typedefs = {} : { string => T.Type } 70 | self.global_typedefs = {} : { string => T.Type } 71 | self.vararg = nil : Variable? 72 | self.fixed = false 73 | 74 | if parent then 75 | parent.children #= self 76 | end 77 | end 78 | 79 | 80 | function Scope:is_module_level() -> bool 81 | -- parent should be global scope, and so should have no parent 82 | return self.parent and self.parent.parent == nil 83 | end 84 | 85 | 86 | function Scope:declare_type(name: string, typ: T.Type, where: string, is_local: bool) 87 | D.assert(not self.fixed) 88 | D.assert(type(name) == 'string') 89 | D.assert(type(where) == 'string') 90 | D.assert(type(is_local) == 'boolean') 91 | 92 | if is_local then 93 | self.typedefs[name] = typ 94 | else 95 | if Scope.GLOBALS_IN_TOP_SCOPE then 96 | Scope.global_scope.global_typedefs[name] = typ 97 | else 98 | self.global_typedefs[name] = typ 99 | end 100 | end 101 | end 102 | 103 | 104 | function Scope:add_global_type(name: string, typ: T.Type) 105 | self.global_typedefs[name] = typ 106 | end 107 | 108 | 109 | function Scope:create_local(name: string, where: string) -> Variable 110 | D.assert(not self.fixed) 111 | 112 | local v = { 113 | scope = self, 114 | name = name, 115 | is_global = false, 116 | is_constant = U.is_constant_name(name), 117 | where = where, 118 | num_reads = 0, 119 | num_writes = 0, 120 | } 121 | 122 | D.assert(not self.locals[name]) 123 | self.locals[name] = v 124 | 125 | return v 126 | end 127 | 128 | 129 | function Scope:add_global(v: Variable) 130 | assert(not self.fixed) 131 | self.globals #= v 132 | end 133 | 134 | 135 | function Scope:create_global(name: string, where: string, typ: T.Type?) -> Variable 136 | assert(not self.fixed) 137 | 138 | local v = { 139 | scope = self, 140 | name = name, 141 | is_global = true, 142 | is_constant = U.is_constant_name(name), 143 | where = where, 144 | type = typ, 145 | num_reads = 0, 146 | num_writes = 0, 147 | } 148 | 149 | if Scope.GLOBALS_IN_TOP_SCOPE and self ~= Scope.global_scope then 150 | Scope.global_scope:add_global(v) 151 | else 152 | self:add_global(v) 153 | end 154 | 155 | return v 156 | end 157 | 158 | 159 | function Scope:get_scoped_type(name: string) -> T.Type? 160 | return self.typedefs[name] 161 | end 162 | 163 | 164 | function Scope:get_local_type(name: string) -> T.Type? 165 | local t = self:get_scoped_type(name) 166 | if t then return t end 167 | if self.parent then return self.parent:get_type(name) end 168 | return nil 169 | end 170 | 171 | 172 | function Scope:get_type(name: string) -> T.Type? 173 | return self:get_local_type(name) or self:get_global_type(name) 174 | end 175 | 176 | 177 | function Scope:get_global_type(name: string) -> T.Type ? 178 | local t = self.global_typedefs[name] 179 | if t then return t end 180 | 181 | if self.parent then 182 | return self.parent:get_global_type(name) 183 | end 184 | end 185 | 186 | 187 | function Scope:locals_iterator() -> (function(...) -> string,Variable) 188 | return pairs(self.locals) 189 | end 190 | 191 | 192 | function Scope:sorted_locals() -> [Variable] 193 | local variables = {} : [Variable] 194 | for _, v in pairs(self.locals) do 195 | variables #= v 196 | end 197 | table.sort(variables, function(a,b) return a.where < b.where end) 198 | return variables 199 | end 200 | 201 | 202 | -- Will only check local scope 203 | function Scope:get_scoped(name: string, options: VarOptions?) -> Variable? 204 | var v = self.locals[name] 205 | if v then 206 | if not v.forward_declared or options ~= 'ignore_fwd_decl' then 207 | return v 208 | end 209 | end 210 | return nil 211 | end 212 | 213 | 214 | -- Will check locals and parents 215 | function Scope:get_local(name: string, options: VarOptions?) -> Variable? 216 | local v = self:get_scoped(name, options) 217 | if v then return v end 218 | 219 | if self.parent then 220 | return self.parent:get_local(name, options) 221 | end 222 | end 223 | 224 | 225 | -- Global declared in this scope 226 | function Scope:get_scoped_global(name: string, options: VarOptions?) -> Variable ? 227 | for _, v in ipairs(self.globals) do 228 | if v.name == name then 229 | if not v.forward_declared or options ~= 'ignore_fwd_decl' then 230 | return v 231 | end 232 | end 233 | end 234 | return nil 235 | end 236 | 237 | 238 | function Scope:get_global(name: string, options: VarOptions?) -> Variable ? 239 | local v = self:get_scoped_global(name, options) 240 | if v then return v end 241 | 242 | if self.parent then 243 | return self.parent:get_global(name, options) 244 | end 245 | end 246 | 247 | 248 | 249 | -- Var declared in this scope 250 | function Scope:get_scoped_var(name: string, options: VarOptions?) -> Variable ? 251 | return self:get_scoped(name, options) or self:get_scoped_global(name, options) 252 | end 253 | 254 | 255 | function Scope:get_var(name: string, options: VarOptions?) -> Variable ? 256 | return self:get_local(name, options) or self:get_global(name, options) 257 | end 258 | 259 | 260 | function Scope:get_global_vars(list: [Variable] or nil) -> [Variable] 261 | list = list or {} 262 | U.list_join(list, self.globals) 263 | for _,c in ipairs(self.children) do 264 | c:get_global_vars(list) 265 | end 266 | return list 267 | end 268 | 269 | 270 | function Scope:get_global_typedefs() -> { string => T.Type } 271 | return U.shallow_clone( self.global_typedefs ) 272 | end 273 | 274 | 275 | -------------------------------------------------- 276 | -- Static members: 277 | 278 | -- Created the global top-level scope 279 | function Scope.get_global_scope() -> Scope 280 | Scope.global_scope = Scope.global_scope or Scope.create_global_scope() 281 | return Scope.global_scope 282 | end 283 | 284 | 285 | function Scope.create_module_scope() -> Scope 286 | local top_scope = Scope.get_global_scope() 287 | return Scope.new( "[MODULE_SCOPE]", top_scope ) 288 | end 289 | 290 | 291 | function Scope.create_global_scope() -> Scope 292 | var s = Scope.new("[GLOBAL_SCOPE]") 293 | Scope.global_scope = s 294 | var where = "[intrinsic]" -- var.where 295 | 296 | 297 | --[[ 298 | -- Print out all globals 299 | for k,v in pairs(_G) do print(k, "\t=\t", v) end 300 | --]] 301 | 302 | -- Ommisions explicitly added in lua_intrinsics.sol 303 | 304 | var tables = { 305 | '_G', 306 | 'jit' -- luaJIT 307 | } 308 | 309 | var functions = { 310 | 'gcinfo', 'getfenv', 'getmetatable', 311 | 'load', 'loadfile', 'loadstring', 312 | 'module', 313 | 'newproxy', 'next', 314 | 'pcall', 315 | 'rawequal', 'rawget', 'rawset', 316 | 'select', 'setfenv', 317 | 'xpcall', 318 | } 319 | 320 | for _,name in ipairs(tables) do 321 | s:create_global( name, where, T.Object ) 322 | end 323 | 324 | for _,name in ipairs(functions) do 325 | var fun_t = { 326 | tag = 'function', 327 | args = { }, 328 | vararg = { tag = 'varargs', type = T.Any }, 329 | rets = T.AnyTypeList, 330 | name = name, 331 | intrinsic_name = name, 332 | } 333 | s:create_global( name, where, fun_t) 334 | end 335 | 336 | s:create_global( '_VERSION', where, T.String ) 337 | s:create_global( 'arg', where, { tag = 'list', type = T.String} ) 338 | 339 | 340 | -- Ensure 'require' is recognized by TypeCheck.sol 341 | local require = s:create_global( 'require', where ) 342 | require.type = { 343 | tag = 'function', 344 | args = { { type = T.String } }, 345 | rets = T.AnyTypeList, 346 | name = "require", 347 | intrinsic_name = "require", 348 | } 349 | 350 | -- Ensure 'pairs' and 'ipairs' are recognized by TypeCheck.sol 351 | local pairs = s:create_global( 'pairs', where ) 352 | pairs.type = { 353 | tag = 'function', 354 | args = { { type = T.Any } }, 355 | rets = T.AnyTypeList, 356 | intrinsic_name = "pairs", 357 | name = "pairs", 358 | } 359 | 360 | local ipairs_ = s:create_global( 'ipairs', where ) 361 | ipairs_.type = { 362 | tag = 'function', 363 | args = { { type = T.List } }, 364 | rets = T.AnyTypeList, 365 | intrinsic_name = "ipairs", 366 | name = "ipairs", 367 | } 368 | 369 | local setmetatable = s:create_global( 'setmetatable', where ) 370 | setmetatable.type = { 371 | tag = 'function', 372 | args = { { type = T.Table }, { type = T.variant(T.Table, T.Nil) } }, 373 | rets = { T.Table }, 374 | intrinsic_name = "setmetatable", 375 | name = "setmetatable", 376 | } 377 | 378 | 379 | var is_local = true 380 | --s:declare_type( 'void', T.Void ) -- Not a valid type, only allowed as a typelist 381 | s:declare_type( 'bool', T.Bool, where, is_local ) 382 | s:declare_type( 'int', T.Int, where, is_local ) 383 | s:declare_type( 'uint', T.Uint, where, is_local ) 384 | s:declare_type( 'number', T.Num, where, is_local ) 385 | s:declare_type( 'string', T.String, where, is_local ) 386 | s:declare_type( 'any', T.Any, where, is_local ) 387 | s:declare_type( 'table', T.Table, where, is_local ) 388 | --s:declare_type( 'list', T.List, where, is_local ) -- use: [any] 389 | --s:declare_type( 'map', T.Map, where, is_local ) -- use: {any => any} 390 | s:declare_type( 'object', T.Object, where, is_local ) 391 | 392 | -- keywords are handles explicitly during parsing 393 | --s:declare_type( 'nil', T.Nil) -- for e.g.: foo or bar or nil 394 | --s:declare_type( 'true', T.True) 395 | --s:declare_type( 'false', T.False) 396 | 397 | if not Scope.GLOBALS_IN_TOP_SCOPE then 398 | -- No more changes - user globals should be declared in module scope (a direct child) 399 | s.fixed = true 400 | end 401 | 402 | return s 403 | end 404 | 405 | ---------------------------------------- 406 | 407 | return {} 408 | -------------------------------------------------------------------------------- /sol/sol.sol: -------------------------------------------------------------------------------- 1 | -- For running a .sol without outputting a .lua to disk 2 | -- TODO: compiler.sol continaing thing common to sol.sol and solc.sol 3 | 4 | require 'globals' 5 | local lfs = require 'lfs' 6 | local path = require 'pl.path' 7 | 8 | ------------------------------------------------ 9 | -- Setup local includes: 10 | -- Without this code 11 | 12 | local sol_dir = path.dirname(arg[0]) 13 | 14 | if sol_dir == "" then 15 | -- OK 16 | elseif path.isabs(sol_dir) then 17 | sol_dir ..= '/' 18 | else 19 | sol_dir = lfs.currentdir() .. '/' .. sol_dir .. '/' 20 | end 21 | 22 | -- Ensure the local includes work: 23 | package.path = sol_dir..'?.lua;' .. package.path 24 | 25 | 26 | ------------------------------------------------ 27 | 28 | local output = require 'output' 29 | local Lexer = require 'lexer' 30 | local Parser = require 'parser' 31 | local _ = require 'scope' 32 | local T = require 'type' 33 | local TypeCheck = require 'type_check' 34 | local U = require 'util' 35 | local printf_err = U.printf_err 36 | 37 | 38 | local function compile_sol(source_text: string) -> string or nil 39 | local filename = "input" 40 | local settings = Parser.SOL_SETTINGS 41 | 42 | local st, tokens = Lexer.lex_sol(source_text, filename, settings) 43 | if not st then 44 | os.exit(1) 45 | return nil 46 | end 47 | 48 | var module_scope = Scope.create_module_scope() 49 | 50 | local st, ast = Parser.parse_sol(source_text, tokens, filename, settings, module_scope) 51 | if not st then 52 | os.exit(2) 53 | return nil 54 | end 55 | 56 | local on_require = function(_,_) -> T.Typelist 57 | return T.AnyTypeList 58 | end 59 | 60 | local st, _ = TypeCheck(ast, filename, on_require, settings) 61 | 62 | if not st then 63 | os.exit(3) 64 | return nil 65 | end 66 | 67 | local str = output(ast, filename) 68 | 69 | return str 70 | end 71 | 72 | 73 | local function run_sol(sol: string) 74 | local lua = compile_sol(sol) 75 | if lua then 76 | local f = load(lua) 77 | if f then 78 | f() 79 | else 80 | printf_err("loadstring returned nil") 81 | end 82 | end 83 | end 84 | 85 | 86 | 87 | local function print_help() 88 | print([[ 89 | NAME: 90 | sol - Sol interpreter 91 | 92 | SYNOPSIS 93 | sol [ options ] [ filenames ] 94 | 95 | EXAMPLE 96 | lua sol.lua file.sol 97 | 98 | DESCRIPTION 99 | sol runs a .sol file 100 | 101 | OPTIONS 102 | -h or --help 103 | print this help text 104 | 105 | -s 106 | Spam mode: Will print extensive trace text (for debugging solc) 107 | 108 | -e0 109 | Ignore all errors and push through 110 | 111 | AUTHOR 112 | Emil Ernerfeldt 113 | ]]) 114 | end 115 | 116 | 117 | if #arg == 0 then 118 | print_help() 119 | os.exit(-1) 120 | else 121 | local ix = 1 122 | local num_files = 0 123 | 124 | while ix <= #arg do 125 | local a = arg[ix] 126 | ix += 1 127 | 128 | if a == '-h' or a == '--help' then 129 | print_help() 130 | elseif a == '-s' then 131 | g_spam = true 132 | elseif a == '-e0' then 133 | g_ignore_errors = true 134 | else 135 | local path_in = a 136 | if path.extension(path_in) ~= '.sol' then 137 | printf_err( "Input file must have .sol ending" ) 138 | os.exit(-2) 139 | end 140 | 141 | local sol = U.read_entire_file( path_in ) 142 | 143 | if not sol then 144 | printf_err( "Input file not found" ) 145 | os.exit(-3) 146 | end 147 | 148 | run_sol( sol ) 149 | 150 | num_files += 1 151 | end 152 | end 153 | 154 | if num_files == 0 then 155 | printf_err( "No input!" ) 156 | print_help() 157 | os.exit(-1337) 158 | end 159 | end 160 | 161 | os.exit(0) -- Success 162 | -------------------------------------------------------------------------------- /sol/sol_debug.sol: -------------------------------------------------------------------------------- 1 | local D = {} 2 | 3 | D.active = false 4 | 5 | function D.get_lib() -> any 6 | if D.active then 7 | return require("debugger") 8 | else 9 | return nil 10 | end 11 | end 12 | 13 | 14 | function D.activate() 15 | D.active = true 16 | end 17 | 18 | function D.assert(bool_expr: any, fmt: string?, ...) -> any 19 | --D.active = true 20 | 21 | if bool_expr then 22 | return bool_expr 23 | elseif D.active then 24 | local dbg = D.get_lib() 25 | return dbg.assert(bool_expr, fmt, ...) 26 | else 27 | return assert(bool_expr, fmt, ...) 28 | end 29 | end 30 | 31 | function D.break_() 32 | if D.active then 33 | print("Breaking debugger") 34 | local dbg = D.get_lib() 35 | dbg() 36 | end 37 | end 38 | 39 | function D.error(msg: string) 40 | print("ERROR: " .. msg) 41 | D.break_() 42 | error(msg) 43 | end 44 | 45 | return D 46 | -------------------------------------------------------------------------------- /sol/util.sol: -------------------------------------------------------------------------------- 1 | --[[ 2 | Util.lua 3 | 4 | Provides some common utilities shared throughout the project. 5 | --]] 6 | 7 | require 'globals' 8 | local D = require 'sol_debug' 9 | 10 | ------------------------------------------------ 11 | 12 | local U = {} 13 | 14 | local PLATFORM = os.getenv("windir") and "win" or "unix" 15 | 16 | ------------------------------------------------------ 17 | --[[ 18 | Lua pretty-printing of anything as lua-code. 19 | Only supports DAG:s. 20 | --]] 21 | 22 | 23 | local function is_identifier(key: string) -> bool 24 | return key:match('^[_%a][_%w]*$') 25 | end 26 | 27 | 28 | local function is_keyword(key: string) -> bool 29 | return key == 'and' 30 | or key == 'break' 31 | or key == 'do' 32 | or key == 'else' 33 | or key == 'elseif' 34 | or key == 'end' 35 | or key == 'false' 36 | or key == 'for' 37 | or key == 'function' 38 | or key == 'if' 39 | or key == 'in' 40 | or key == 'local' 41 | or key == 'nil' 42 | or key == 'not' 43 | or key == 'or' 44 | or key == 'repeat' 45 | or key == 'return' 46 | or key == 'then' 47 | or key == 'true' 48 | or key == 'until' 49 | or key == 'while' 50 | 51 | -- Sol: 52 | or key == 'class' 53 | or key == 'global' 54 | or key == 'typedef' 55 | or key == 'var' 56 | end 57 | 58 | 59 | local function is_safe_key(key: string) -> bool 60 | return is_identifier(key) and not is_keyword(key) 61 | end 62 | 63 | 64 | -- val - value to serialize 65 | -- ignore_set - ignore these key:s 66 | -- indent - indent on any _subsequent_ line 67 | -- discovered - set of tables already processed (used to discover loops) 68 | function U.serialize_to_rope(rope: [string], val: any, ignore_set: {any}?, indent: string?, discovered: {table}?) -> void 69 | if val == nil then 70 | rope #= "nil" 71 | return 72 | end 73 | 74 | ignore_set = ignore_set or {} 75 | indent = indent or "" 76 | discovered = discovered or {} 77 | 78 | if type(val) == "table" then 79 | if discovered[val] then 80 | --error("serialize: loop discovered") 81 | rope #= 'LOOP' 82 | return 83 | end 84 | discovered[val] = true 85 | 86 | local scope_indent = indent .. " " 87 | rope #= "{\n" 88 | if U.is_array(val) then 89 | for _,v in ipairs(val) do 90 | rope #= scope_indent 91 | U.serialize_to_rope(rope, v, ignore_set, scope_indent, discovered) 92 | rope #= ",\n" 93 | end 94 | else 95 | for k,v in pairs(val) do 96 | --if not ignore_set[k] then 97 | if true then 98 | local key = is_safe_key(k) and k or string.format("[%q]", k) 99 | 100 | rope #= scope_indent 101 | rope #= key 102 | rope #= " = " 103 | 104 | if ignore_set[k] then 105 | rope #= 'ignored' 106 | else 107 | U.serialize_to_rope(rope, v, ignore_set, scope_indent, discovered) 108 | end 109 | 110 | rope #= ",\n" 111 | end 112 | end 113 | end 114 | rope #= indent .. "}" 115 | elseif type(val) == "string" then 116 | rope #= string.format("%q", val) 117 | elseif type(val) == "number" or type(val) == "boolean" then 118 | rope #= tostring(val) 119 | else 120 | --error("serialize: Can't serialize something of type " .. type(val)) 121 | rope #= tostring(val) 122 | end 123 | end 124 | 125 | 126 | function U.serialize(val: any, ignore_set: {any}?) -> string 127 | local rope = {} 128 | U.serialize_to_rope(rope, val, ignore_set, nil, nil) 129 | local str = table.concat(rope) 130 | return str 131 | end 132 | 133 | 134 | function U.pretty(arg: any) -> string 135 | return U.serialize(arg) 136 | end 137 | 138 | var ESCAPE_LOOKUP = { ['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'" } 139 | 140 | function U.escape(str: string) -> string 141 | if true then 142 | return string.format('%q', str) 143 | else 144 | var ret = '' 145 | for i=1,#str do 146 | local c = str:sub(i,i) -- TODO: var 147 | ret ..= ESCAPE_LOOKUP[c] or c 148 | end 149 | return ret 150 | end 151 | end 152 | 153 | function U.unescape(str: string) -> string 154 | -- FIXME: unescape is unsafe 155 | return load("return "..str)() 156 | end 157 | 158 | ------------------------------------------------------ 159 | 160 | function U.trim(str: string) -> string 161 | return str:gsub("^%s*(.-)%s*$", "%1") 162 | end 163 | 164 | -- U.INDENTATION = ' ' 165 | U.INDENTATION = '\t' 166 | 167 | function U.indent(str: string) -> string 168 | return U.INDENTATION .. str:gsub("\n", "\n" .. U.INDENTATION) 169 | end 170 | 171 | function U.quote_or_indent(str: string) -> string 172 | str = U.trim(str) 173 | if str:find('\n') then 174 | return '\n\n' .. U.indent( str ) .. '\n\n' 175 | else 176 | return "'"..str.."'" 177 | end 178 | end 179 | 180 | 181 | function U.printf(fmt: string, ...) 182 | print(string.format(fmt, ...)) 183 | end 184 | 185 | 186 | function U.ellipsis(msg: string, max_len: int?) -> string 187 | max_len = max_len or 2048 188 | 189 | if #msg <= max_len then 190 | return msg 191 | else 192 | --return msg:sub(1, max_len/2) .. ' [...] ' .. msg:sub(-max_len/2) 193 | return msg:sub(1, max_len/2) .. '\n[...]\n' .. msg:sub(-max_len/2) 194 | end 195 | end 196 | 197 | 198 | function U.printf_err(fmt: string, ...) 199 | local msg = string.format(fmt, ...) 200 | 201 | if g_one_line_errors then msg = msg:gsub("\n", " ") end 202 | --msg = U.ellipsis(msg) 203 | 204 | io.stderr:write( msg .. '\n' ) 205 | D.break_() 206 | 207 | if g_break_on_error then 208 | os.exit(1) 209 | end 210 | end 211 | 212 | 213 | -- Returns the number of line breaks 214 | function U.count_line_breaks(str: string) -> int 215 | if not str:find('\n') then 216 | -- Early out 217 | return 0 218 | end 219 | 220 | local n = 0 221 | for i = 1,#str do 222 | if str:sub(i,i) == '\n' then 223 | n += 1 224 | end 225 | end 226 | return n 227 | end 228 | 229 | ------------------------------------------------------ 230 | -- Files: 231 | 232 | 233 | function U.file_exists(path: string) -> bool 234 | local f = io.open(path, "rb") 235 | if f then 236 | f:close() 237 | return true 238 | else 239 | return false 240 | end 241 | end 242 | 243 | function U.write_protect(path: string) -> bool 244 | if PLATFORM == "unix" then 245 | return 0 == os.execute("chmod -w " .. path) 246 | else 247 | return 0 == os.execute("attrib +R " .. path) 248 | end 249 | end 250 | 251 | 252 | function U.write_unprotect(path: string) -> bool 253 | if U.file_exists(path) then 254 | if PLATFORM == "unix" then 255 | return 0 == os.execute("chmod +w " .. path) 256 | else 257 | return 0 == os.execute("attrib -R " .. path) 258 | end 259 | end 260 | end 261 | 262 | 263 | function U.read_entire_file(path: string) -> string? 264 | local f = io.open(path, "rb") 265 | if not f then return nil end 266 | local content = f:read("*all") 267 | f:close() 268 | content = content:gsub('\r', '') -- Fixes sillyness on windows 269 | return content 270 | end 271 | 272 | 273 | function U.read_entire_stdin() -> string? 274 | return io.read("*all") 275 | end 276 | 277 | 278 | function U.write_file(path: string, contents: string) -> bool 279 | local f = io.open(path, "w") 280 | if not f then return false end 281 | f:write(contents) 282 | f:close() 283 | return true 284 | end 285 | 286 | 287 | ------------------------------------------------------ 288 | -- Tables and arrays etc: 289 | 290 | 291 | function U.is_array(val: any) -> bool 292 | if type(val) ~= "table" then 293 | return false 294 | end 295 | 296 | if getmetatable(val) ~= nil then 297 | return false 298 | end 299 | 300 | var max,n = 0,0 301 | 302 | for ix, _ in pairs(val) do 303 | if type(ix) ~= "number" or ix <= 0 or math.floor(ix) ~= ix then 304 | return false 305 | end 306 | 307 | max = math.max(max, ix) 308 | n += 1 309 | end 310 | 311 | return n == max 312 | end 313 | 314 | 315 | function U.set(tb: [string]) -> {string} 316 | var set = {} : {string} 317 | for _,v in ipairs(tb) do 318 | set[v] = true 319 | end 320 | return set 321 | end 322 | 323 | 324 | function U.set_join(... : {string}) -> {string} 325 | var ret_set = {} : {string} 326 | for _,set in ipairs{...} do 327 | for elem in pairs(set) do 328 | ret_set[elem] = true 329 | end 330 | end 331 | return ret_set 332 | end 333 | 334 | 335 | function U.list_join(out: [any], in_table: [any]) 336 | for _,val in ipairs(in_table) do 337 | out #= val 338 | end 339 | end 340 | 341 | 342 | function U.list_concat(a: [any], b: [any]) -> [any] 343 | var ret = {} : [any] 344 | for _,v in ipairs(a) do 345 | ret #= v 346 | end 347 | for _,v in ipairs(b) do 348 | ret #= v 349 | end 350 | return ret 351 | end 352 | 353 | 354 | function U.table_empty(t: table) -> bool 355 | return next(t) == nil and getmetatable(t) == nil 356 | end 357 | 358 | 359 | function U.shallow_clone(t: table?) -> table? 360 | if not t then return t end 361 | var t2 = {} : table 362 | for k,v in pairs(t) do 363 | t2[k] = v 364 | end 365 | return t2 366 | end 367 | 368 | function U.table_clear(t: table) 369 | for k,_ in pairs(t) do 370 | t[k] = nil 371 | end 372 | end 373 | 374 | 375 | ------------------------------------------------------ 376 | 377 | function U.print_sorted_stats(map: {string => number}) 378 | var list = {} : [{key: string, value: number}] 379 | var sum = 0.0 380 | 381 | for key,value in pairs(map) do 382 | list #= { key = key, value = value } 383 | sum += value 384 | end 385 | 386 | table.sort(list, function(a,b) return a.value>b.value end) 387 | 388 | for _,v in ipairs(list) do 389 | U.printf("%24s : %5d %4.1f %%", "'"..v.key.."'", v.value, 100*v.value/sum) 390 | end 391 | end 392 | 393 | 394 | ------------------------------------------------------ 395 | -- TODO: only in debug/development 396 | local DEBUG = true 397 | 398 | -- Returns a write-protected version of the input table 399 | function U.const(table: table) -> object 400 | if DEBUG then 401 | assert(getmetatable(table) == nil) 402 | 403 | return setmetatable({ 404 | __protected = table -- Visible in debugger 405 | }, { 406 | __index = table, 407 | __newindex = function(_,_,_) -- table, key, value 408 | D.error("Attempt to modify read-only table") 409 | end, 410 | __metatable = 'This is a read-only table' -- disallow further meta-tabling 411 | }) 412 | else 413 | return table 414 | end 415 | end 416 | 417 | -- Write-protects existing table against all modification 418 | function U.make_const(table: table) -> void 419 | if DEBUG then 420 | assert(getmetatable(table) == nil) 421 | 422 | local clone = U.shallow_clone(table) 423 | 424 | U.table_clear(table) 425 | 426 | table.__protected = clone -- Visible in debugger 427 | 428 | setmetatable(table, { 429 | __index = clone, 430 | __newindex = function(_,_,_) -- table, key, value 431 | D.error("Attempt to modify read-only table") 432 | end, 433 | __metatable = 'This is a read-only table' -- disallow further meta-tabling 434 | }) 435 | end 436 | end 437 | 438 | ------------------------------------------------------ 439 | 440 | function U.is_constant_name(name: string) -> bool 441 | return name:match("^[_A-Z][_A-Z0-9]+$") 442 | end 443 | 444 | return U 445 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "Sol", 16 | "children": 17 | [ 18 | { 19 | "command": "open_file", "args": 20 | { 21 | "file": "${packages}/Sol/Sol.sublime-settings" 22 | }, 23 | "caption": "Settings – Default" 24 | }, 25 | { 26 | "command": "open_file", "args": 27 | { 28 | "file": "${packages}/User/Sol.sublime-settings" 29 | }, 30 | "caption": "Settings – User" 31 | }, 32 | { "caption": "-" } 33 | ] 34 | } 35 | ] 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/README.md: -------------------------------------------------------------------------------- 1 | SolSublime 2 | ========== -------------------------------------------------------------------------------- /sublime_plugin/Sol/Sol.JSON-tmLanguage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilk/sol/1969c6d3531cfa9ced8611580f0dfe224b269bdd/sublime_plugin/Sol/Sol.JSON-tmLanguage -------------------------------------------------------------------------------- /sublime_plugin/Sol/Sol.sublime-build: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": ["lua", "/Users/emilk/Sol/install/sol.lua", "$file"], 3 | "file_regex": "^(?:(?:\t)|(?:.+: ))(.+):([0-9]+): (.*)$", 4 | "selector": "source.sol" 5 | } 6 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/Sol.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "live_parser" : true, 3 | "solc_path": "lua /Users/emilk/Sol/install/solc.lua" 4 | } 5 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/package-metadata.json: -------------------------------------------------------------------------------- 1 | {"url": "https://github.com/emilk/SolSublime", "version": "2013.07.09.21.00.00", "description": "Various support files for making developing Sol using Sublime Text 2 more pleasant."} -------------------------------------------------------------------------------- /sublime_plugin/Sol/parse_sol.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import sublime 3 | import sublime_plugin 4 | import re 5 | import os 6 | from subprocess import Popen, PIPE 7 | 8 | 9 | #solc_path = 'solc' # TODO FIXME 10 | #solc_path = 'luajit /Users/emilk/Sol/install/solc.lua' 11 | #solc_path = 'lua /Users/emilk/Sol/install/solc.lua' 12 | solc_path = os.environ.get('SOLC') # SOLC should be an environment variable on the form 'luajit /path/to/solc.lua' 13 | if not solc_path: 14 | print("Failed to find environment variable SOLC - it should be on the form 'luajit /path/to/solc.lua'") 15 | os.exit() 16 | 17 | 18 | def get_setting(key, default=None): 19 | window = sublime.active_window() 20 | if window != None: 21 | project_data = window.project_data() 22 | if project_data != None and "settings" in project_data: 23 | settings = project_data["settings"] 24 | if key in settings: 25 | return settings[key] 26 | 27 | us = sublime.load_settings("sublime_tools (User).sublime-settings") 28 | val = us.get(key, None) 29 | if val == None: 30 | ds = sublime.load_settings("sublime_tools.sublime-settings") 31 | val = ds.get(key, default) 32 | 33 | return val 34 | 35 | 36 | # bytes to string 37 | def decode(bytes): 38 | str = bytes.decode('utf-8') 39 | #str = bytes.decode(encoding='UTF-8') 40 | str = str.replace("\r", "") # Damn windows 41 | return str 42 | 43 | 44 | 45 | #settings = sublime.load_settings("Sol.sublime-settings") 46 | 47 | 48 | 49 | class ParseSolCommand(sublime_plugin.EventListener): 50 | # Wait this many ms after last change before parsing, 51 | # so the user can finish typing the keyword before getting a warning. 52 | TIMEOUT_MS = 200 53 | 54 | 55 | def __init__(self): 56 | self.pending = 0 # pending change-timeouts 57 | self.is_parsing = False 58 | self.is_dirty = False # if parsing, this is set true to signal re-parse after the current parse is done 59 | 60 | 61 | def on_load(self, view): 62 | #print("parse_sol.py: on_modified") 63 | self.on_modified(view) 64 | 65 | def on_modified(self, view): 66 | #print("parse_sol.py: on_modified") 67 | self.pending = self.pending + 1 68 | sublime.set_timeout_async(lambda: self.needs_parse(view), self.TIMEOUT_MS) 69 | #self.needs_parse(view) 70 | 71 | 72 | def needs_parse(self, view): 73 | #print("parse_sol.py: needs_parse") 74 | 75 | # Don't bother parsing if there's another parse command pending 76 | self.pending = self.pending - 1 77 | if self.pending > 0: 78 | return 79 | 80 | # no change for TIMEOUT_MS - start a parse! 81 | 82 | filename = view.file_name() 83 | if not filename: 84 | return 85 | 86 | if not filename.endswith('.sol') and not filename.endswith('.lua'): 87 | sublime.status_message("not sol or lua") 88 | return 89 | 90 | if self.is_parsing: 91 | self.is_dirty = True # re-parse when the current parse finishes 92 | else: 93 | self.is_parsing = True 94 | self.is_dirty = False 95 | file_path = view.file_name() 96 | text = view.substr(sublime.Region(0, view.size())) 97 | 98 | #print('--------------------------------------------') 99 | #print(text) 100 | #print('--------------------------------------------') 101 | 102 | # Do the parse in a background thread to keep sublime from hanging while we recurse on 'require':s etc: 103 | sublime.set_timeout_async(lambda: self.parse(view, file_path, text), 0) 104 | #sublime.set_timeout(lambda: self.parse(view, file_path, text), 0) 105 | 106 | 107 | def parse(self, view, file_path, text): 108 | #print("parse_sol.py: parse") 109 | #print('--------------------------------------------') 110 | #print(text) 111 | #print('--------------------------------------------') 112 | 113 | try: 114 | # Often projects will use module paths that are relative to the root of the project_path 115 | # This allows solc to understand where to look for modules 116 | root_mod_path = get_setting("project_path", ".") + '/' 117 | 118 | # Run solc with the parse option 119 | cmd = solc_path + ' -m ' + root_mod_path + ' -p' 120 | 121 | sol_core = get_setting("sol_core") 122 | if sol_core: 123 | cmd += ' -l ' + sol_core 124 | 125 | cmd += ' --check ' + file_path 126 | print("cmd: " + cmd) 127 | p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True) 128 | 129 | # Extract output: 130 | solc_out = p.communicate(text.encode('utf-8')) 131 | warnings = decode( solc_out[0] ) # stdout 132 | errors = decode( solc_out[1] ) # stderr 133 | result = p.wait() 134 | 135 | print("solc stdout: " + warnings) 136 | 137 | sublime.set_timeout(lambda: self.show_errors(view, warnings, errors), 0) 138 | 139 | except Exception as e: 140 | msg = str(e) + ' ' + traceback.format_exc() 141 | msg = msg.replace('\n', ' ') 142 | self.show_errors(view, "parse_sol.py: " + msg) 143 | 144 | 145 | def show_errors(self, view, warnings, errors): 146 | print("parse_sol.py: show_errors") 147 | 148 | try: 149 | #sublime.status_message("parse_sol.py: show_errors") 150 | 151 | # Clear out any old region markers 152 | view.erase_regions('sol_warnings') 153 | view.erase_regions('sol_serrors') 154 | 155 | filename = view.file_name() 156 | pattern = re.compile(r'(\w+.\w+):([0-9]+):') 157 | 158 | if warnings != "": 159 | print("sol warnings: \n" + warnings) 160 | 161 | # Add regions 162 | regions = [] 163 | 164 | for file,line in pattern.findall(warnings): 165 | if filename.find( file ) != -1: 166 | region = view.full_line(view.text_point(int(line) - 1, 0)) 167 | regions.append( region ) 168 | 169 | view.add_regions('sol_warnings', regions, 'invalid', 'dot', sublime.HIDDEN) 170 | 171 | 172 | if errors != "": 173 | print("sol errors: \n" + errors) 174 | 175 | # Add regions and place the error message in the status bar 176 | sublime.status_message("solc: " + errors.replace('\n', ' ')) 177 | 178 | regions = [] 179 | 180 | for file,line in pattern.findall(errors): 181 | if filename.find( file ) != -1: 182 | region = view.full_line(view.text_point(int(line) - 1, 0)) 183 | regions.append( region ) 184 | 185 | view.add_regions('sol_serrors', regions, 'invalid', 'circle', sublime.HIDDEN) 186 | 187 | 188 | except Exception as e: 189 | msg = str(e) + ' ' + traceback.format_exc() 190 | msg = msg.replace('\n', ' ') 191 | sublime.status_message("parse_sol.py: " + msg) 192 | 193 | 194 | self.is_parsing = False 195 | 196 | if self.is_dirty: 197 | self.needs_parse(view) 198 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/sol_errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import re 5 | import sublime 6 | import sublime_plugin 7 | from subprocess import Popen, PIPE 8 | 9 | solc_path = os.environ.get('SOLC') # SOLC should be an environment variable on the form 'luajit /path/to/solc.lua' 10 | if not solc_path: 11 | print("Failed to find environment variable SOLC - it should be on the form 'luajit /path/to/solc.lua'") 12 | os.exit() 13 | 14 | # bytes to string 15 | def decode(bytes): 16 | str = bytes.decode('utf-8') 17 | #str = bytes.decode(encoding='UTF-8') 18 | str = str.replace("\r", "") # Damn windows 19 | return str 20 | 21 | class SolErrorsCommand(sublime_plugin.TextCommand): 22 | ''' 23 | def __init__(self, view): 24 | super(SolErrorsCommand, self).__init__(view) 25 | ''' 26 | 27 | def run(self, edit): 28 | view = self.view 29 | window = view.window() 30 | filename = view.file_name() 31 | selection = view.sel() 32 | 33 | view.run_command("save") 34 | 35 | reconstruction_root = window.folders()[0] + "/" 36 | cmd = solc_path + ' -p ' + filename 37 | print("cmd: " + cmd) 38 | p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) 39 | 40 | # Extract output: 41 | all_code = view.substr(sublime.Region(0, view.size())) 42 | solc_out = p.communicate(all_code.encode('utf-8')) 43 | warnings = decode( solc_out[0] ) # stdout 44 | errors = decode( solc_out[1] ) # stderr 45 | result = p.wait() 46 | 47 | # print("solc stdout: " + warnings) 48 | # print("solc stderr: " + errors) 49 | 50 | output = errors + '\n' + warnings 51 | 52 | # view.erase_regions('volumental_lint_errors') 53 | pattern = re.compile(r'(\w+.\w+):([0-9]+): (.*)') 54 | 55 | ansi_escape = re.compile(r'\x1b[^m]*m') 56 | 57 | regions = [] 58 | items = [] 59 | 60 | for line in output.split('\n'): 61 | line = ansi_escape.sub('', line) 62 | for file, line_nr, info in pattern.findall(line): 63 | if filename.find( file ) != -1: 64 | region = view.full_line(view.text_point(int(line_nr) - 1, 0)) 65 | regions.append( region ) 66 | items.append(info) 67 | 68 | if len(items) == 0: 69 | print("sol: no errors or warnings") 70 | sublime.status_message("sol: no errors or warnings") 71 | else: 72 | # print("sol output: \n" + output) 73 | sublime.status_message("sol: {} errors/warnings".format(len(items))) 74 | 75 | def on_highlighted(item_index): 76 | region = regions[item_index] 77 | view.show_at_center(region) 78 | selection.clear() 79 | selection.add(region) 80 | 81 | def on_done(item_index): 82 | pass 83 | 84 | window.show_quick_panel(items, on_done, 0, 0, on_highlighted) 85 | 86 | # view.add_regions('volumental_lint_errors', regions, 'invalid', 'circle', sublime.HIDDEN) 87 | -------------------------------------------------------------------------------- /sublime_plugin/Sol/solc-solc.sublime-build: -------------------------------------------------------------------------------- 1 | { 2 | "shell_cmd": "/Users/emilk/Sol/build.sh" 3 | } 4 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | x = x or {} 2 | -------------------------------------------------------------------------------- /test.sol: -------------------------------------------------------------------------------- 1 | local x = 42 2 | x = x 3 | 4 | if x == x then 5 | print("always") 6 | end 7 | 8 | local y = { a = true } 9 | 10 | if y.a or y.a then 11 | print("always") 12 | end 13 | -------------------------------------------------------------------------------- /tests/_README.txt: -------------------------------------------------------------------------------- 1 | .sol files ending with _FAIL should fail to compile 2 | .sol files ending with _ERROR should trigger a runtime error 3 | other files should both compile and run without error 4 | 5 | var<[int], string> l, s 6 | var<[int], string> l, s -------------------------------------------------------------------------------- /tests/__call.sol: -------------------------------------------------------------------------------- 1 | local functor = {} 2 | 3 | setmetatable(functor, { 4 | __call = function(self, x: int) 5 | assert(self == functor) 6 | return 2*x 7 | end 8 | }) 9 | 10 | local d = functor(42) 11 | assert(d == 84) 12 | -------------------------------------------------------------------------------- /tests/__index.sol: -------------------------------------------------------------------------------- 1 | var Class = {} 2 | 3 | function Class:get() 4 | return 42 5 | end 6 | 7 | var obj = {} 8 | var mt = { __index = Class } 9 | setmetatable(obj, mt) 10 | var x = obj:get() 11 | assert(x == 42) 12 | -------------------------------------------------------------------------------- /tests/__index_2.sol: -------------------------------------------------------------------------------- 1 | local Singleton = { list = {} } 2 | 3 | local receiver_meta = { 4 | __index = function(_, _) 5 | return function(_ : 'argument') 6 | end 7 | end, 8 | } 9 | setmetatable(Singleton, receiver_meta) 10 | 11 | function Singleton.fun() 12 | end 13 | 14 | Singleton.list['key'] = nil 15 | -------------------------------------------------------------------------------- /tests/_lengthy.sol: -------------------------------------------------------------------------------- 1 | var v = nil : int? 2 | if not v then 3 | v = 42 4 | end 5 | if not v then 6 | v = 42 7 | end 8 | -------------------------------------------------------------------------------- /tests/arg_count.sol: -------------------------------------------------------------------------------- 1 | local function foo(a0, a1, a2) 2 | end 3 | 4 | local st,res = pcall(function() 5 | foo(1,2,3) 6 | end) 7 | 8 | print("wee") 9 | -------------------------------------------------------------------------------- /tests/arg_count_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo(arg) 2 | end 3 | 4 | pcall(function() 5 | foo('too', 'many') -- FAIL 6 | end) 7 | 8 | pcall(foo, 'too', 'many') -- TODO: FAIL (doesn't yet) 9 | -------------------------------------------------------------------------------- /tests/arg_type.sol: -------------------------------------------------------------------------------- 1 | local function foo(x : int, y : string) 2 | 3 | end 4 | 5 | foo(42, 'hello') 6 | -------------------------------------------------------------------------------- /tests/arg_type_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo(x : int, y : string) 2 | 3 | end 4 | 5 | foo('hello', 42) 6 | -------------------------------------------------------------------------------- /tests/bool.sol: -------------------------------------------------------------------------------- 1 | local key_type = unpack({}) -- Get unknown type 2 | local comma = unpack({}) 3 | 4 | if not (key_type and comma) then 5 | 6 | end 7 | -------------------------------------------------------------------------------- /tests/build_demo.sh: -------------------------------------------------------------------------------- 1 | luajit ../install/solc.lua -e0 demo.sol 2 | -------------------------------------------------------------------------------- /tests/class.sol: -------------------------------------------------------------------------------- 1 | class Klass = {} 2 | 3 | function Klass.new() -> Klass 4 | local obj = {} 5 | setmetatable(obj, { __index = Klass }) 6 | obj:init() 7 | return obj 8 | end 9 | 10 | function Klass:init() 11 | self.foo = 42 12 | end 13 | -------------------------------------------------------------------------------- /tests/compare_eq_FAIL.sol: -------------------------------------------------------------------------------- 1 | local x = 42 2 | x = x 3 | 4 | if x == x then 5 | print("always") 6 | end 7 | 8 | local y = { a = true } 9 | 10 | if y.a or y.a then 11 | print("always") 12 | end 13 | -------------------------------------------------------------------------------- /tests/constant_1_FAIL.sol: -------------------------------------------------------------------------------- 1 | local CONST = 42 2 | CONST = 13 -- SHOULD FAIL 3 | -------------------------------------------------------------------------------- /tests/constant_2_FAIL.sol: -------------------------------------------------------------------------------- 1 | local ALPHA = { 2 | beta = 32 3 | } 4 | ALPHA.beta = 13 -- SHOULD FAIL 5 | -------------------------------------------------------------------------------- /tests/constant_3_FAIL.sol: -------------------------------------------------------------------------------- 1 | local alpha = { 2 | BETA = 42 3 | } 4 | alpha.BETA = 1337 -- SHOULD FAIL 5 | -------------------------------------------------------------------------------- /tests/constant_4_FAIL.sol: -------------------------------------------------------------------------------- 1 | local alpha = { 2 | BETA = { 3 | charlie = 42 4 | } 5 | } 6 | alpha.BETA.charlie = 1337 -- SHOULD FAIL 7 | -------------------------------------------------------------------------------- /tests/constant_5.sol: -------------------------------------------------------------------------------- 1 | local foo = {} 2 | foo.CONSTANT = 42 -- declaration 3 | -------------------------------------------------------------------------------- /tests/constant_6.sol: -------------------------------------------------------------------------------- 1 | local FOO = {} 2 | FOO.bar = 32 3 | -------------------------------------------------------------------------------- /tests/duplicate_obj_members_FAIL.sol: -------------------------------------------------------------------------------- 1 | local obj = { 2 | foo = 42; 3 | foo = "fortytwo"; 4 | } 5 | -------------------------------------------------------------------------------- /tests/empty_list.sol: -------------------------------------------------------------------------------- 1 | var list = {} : [int] -------------------------------------------------------------------------------- /tests/enum.sol: -------------------------------------------------------------------------------- 1 | typedef enum = 'a' or 'b' or 'c' 2 | var v = 'a' : enum -------------------------------------------------------------------------------- /tests/enum_FAIL.sol: -------------------------------------------------------------------------------- 1 | typedef enum = 'a' or 'b' or 'c' 2 | var v = 'd' : enum 3 | -------------------------------------------------------------------------------- /tests/enum_arg.sol: -------------------------------------------------------------------------------- 1 | local function foo(style : 'shallow' or 'deep') 2 | 3 | end 4 | 5 | foo('deep') 6 | -------------------------------------------------------------------------------- /tests/enum_arg_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo(style : 'shallow' or 'deep') 2 | 3 | end 4 | 5 | foo('djup') 6 | -------------------------------------------------------------------------------- /tests/enum_var_FAIL.sol: -------------------------------------------------------------------------------- 1 | 2 | typedef Tribool = 'yes' or 'no' or 'maybe' 3 | 4 | var foo = 'yes' : Tribool 5 | var foos = 'wrong' : Tribool -------------------------------------------------------------------------------- /tests/euler_1.sol: -------------------------------------------------------------------------------- 1 | -- http://projecteuler.net/problem=1 2 | 3 | local function sum_3_5(upTo: uint) -> uint 4 | local sum = 0 5 | 6 | for i = 1, upTo-1 do 7 | if i % 3 == 0 or i % 5 == 0 then 8 | --sum += i -- TODO 9 | sum = sum + i 10 | end 11 | end 12 | 13 | return sum 14 | end 15 | 16 | print( sum_3_5(10) ) 17 | print( sum_3_5(1000) ) 18 | -------------------------------------------------------------------------------- /tests/euler_2.sol: -------------------------------------------------------------------------------- 1 | -- http://projecteuler.net/problem=2 2 | 3 | local a,b = 1,2 4 | local sum = 0 5 | 6 | while b <= 4000000 do 7 | if b % 2 == 0 then 8 | sum = sum + b 9 | end 10 | a,b = b,a+b 11 | end 12 | 13 | print(sum) 14 | -------------------------------------------------------------------------------- /tests/functions.sol: -------------------------------------------------------------------------------- 1 | function foo() 2 | end 3 | function bar(arg) 4 | end 5 | -------------------------------------------------------------------------------- /tests/fwd_declare.sol: -------------------------------------------------------------------------------- 1 | typedef A; 2 | 3 | typedef B = { 4 | a : A 5 | } 6 | 7 | typedef A = { 8 | b : B 9 | } 10 | 11 | local function a2b(a: A) -> B 12 | return { a = a } 13 | end 14 | 15 | local function b2a(b: B) -> A 16 | return { b = b } 17 | end 18 | -------------------------------------------------------------------------------- /tests/fwd_declare_1_FAIL.sol: -------------------------------------------------------------------------------- 1 | local foo, bar; 2 | 3 | foo = function() 4 | bar(42) -- FAIL: Not a string 5 | end 6 | 7 | bar = function(arg: string) 8 | 9 | end 10 | -------------------------------------------------------------------------------- /tests/fwd_declare_2_FAIL.sol: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.foo() 4 | M.bar(42) -- FAIL: Not a string 5 | end 6 | 7 | function M.bar(arg: string) 8 | 9 | end 10 | -------------------------------------------------------------------------------- /tests/fwd_declare_3_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo() 2 | bar(42) -- Implicit global! 3 | end 4 | 5 | local function bar(arg) 6 | 7 | end 8 | -------------------------------------------------------------------------------- /tests/generators_ipairs_map_FAIL.sol: -------------------------------------------------------------------------------- 1 | local map = { ['one'] = 1, ['two'] = 2, ['three'] = 3 } 2 | for k,v in ipairs(map) do -- FAIL: Should be 'pairs' 3 | end -------------------------------------------------------------------------------- /tests/generators_ipairs_obj_FAIL.sol: -------------------------------------------------------------------------------- 1 | local obj = { one = 1, two = 2, three = 3 } 2 | for k,v in ipairs(obj) do -- FAIL: Should be 'pairs' 3 | end 4 | -------------------------------------------------------------------------------- /tests/generators_pairs_FAIL.sol: -------------------------------------------------------------------------------- 1 | local list = { 1,2,3 } 2 | for i,v in pairs(list) do -- FAIL: Should be ipairs 3 | end 4 | -------------------------------------------------------------------------------- /tests/global_FAIL.sol: -------------------------------------------------------------------------------- 1 | local x = 42 2 | x = x + y 3 | -------------------------------------------------------------------------------- /tests/global_function.sol: -------------------------------------------------------------------------------- 1 | global function foo() 2 | 3 | end 4 | -------------------------------------------------------------------------------- /tests/global_not_top_level_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo() 2 | global x = 32 -- Not OK - must be in global scope 3 | end 4 | -------------------------------------------------------------------------------- /tests/if_nilable.sol: -------------------------------------------------------------------------------- 1 | local function foo() -> bool? 2 | if true then 3 | return false 4 | else 5 | return nil 6 | end 7 | end 8 | 9 | if foo() then 10 | 11 | end -------------------------------------------------------------------------------- /tests/if_non_nilable_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function f(arg: int) 2 | if arg then 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /tests/inherit.sol: -------------------------------------------------------------------------------- 1 | typedef Base = { tag : string } 2 | typedef Child : Base = { tag : 'ChildID' } 3 | -------------------------------------------------------------------------------- /tests/inherit_FAIL.sol: -------------------------------------------------------------------------------- 1 | typedef Base = { tag : string } 2 | typedef Child : Base = { tag : 42 } 3 | -------------------------------------------------------------------------------- /tests/int_literal_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function only_int(foo : int) 2 | 3 | end 4 | 5 | only_int( 42.0 ) -- Should fail -------------------------------------------------------------------------------- /tests/literals.sol: -------------------------------------------------------------------------------- 1 | local function only_int(foo : int) 2 | 3 | end 4 | 5 | only_int( 42 ) 6 | only_int( 0xDEAD ) -------------------------------------------------------------------------------- /tests/local_type_single_FAIL.sol: -------------------------------------------------------------------------------- 1 | var ai = 42 : string -------------------------------------------------------------------------------- /tests/local_with_types.sol: -------------------------------------------------------------------------------- 1 | var ai = 42 2 | var bi,ci,di = 1,2,3 3 | var ei,fs = 42,'fortytwo' 4 | -------------------------------------------------------------------------------- /tests/member.sol: -------------------------------------------------------------------------------- 1 | local alpha = { 2 | beta = { 3 | gamma = 42 4 | } 5 | } 6 | local test = alpha.beta.gamma + 16 7 | alpha.beta.gamma = test 8 | -------------------------------------------------------------------------------- /tests/metatable.sol: -------------------------------------------------------------------------------- 1 | local Class = {} 2 | --[[ 3 | function Class:init() 4 | end 5 | --]] 6 | function Class:get() 7 | return 42 8 | end 9 | 10 | 11 | local obj={} 12 | local mt = { __index = Class } 13 | setmetatable(obj, mt) 14 | --obj:init() 15 | var x = obj:get() 16 | -------------------------------------------------------------------------------- /tests/missing_definition_FAIL.sol: -------------------------------------------------------------------------------- 1 | var i -- FAIL: Missing definition of non-nilable type -------------------------------------------------------------------------------- /tests/multiple_throwaway.sol: -------------------------------------------------------------------------------- 1 | local function foo() -> bool, int, string, [int] 2 | return true, 42, "hello", {42} 3 | end 4 | 5 | _,_,_,_ = foo() 6 | -------------------------------------------------------------------------------- /tests/obj.sol: -------------------------------------------------------------------------------- 1 | typedef Token = { 2 | tag : 'string' or 'num', 3 | value : string 4 | } 5 | 6 | local function foo(t: Token) 7 | print(t.tag .. t.value) 8 | end 9 | 10 | foo( { 11 | tag = 'num', 12 | value = '42' 13 | } ) 14 | -------------------------------------------------------------------------------- /tests/obj_FAIL.sol: -------------------------------------------------------------------------------- 1 | typedef Token = { 2 | tag : 'string' or 'num', 3 | value : string 4 | } 5 | 6 | local function foo(t: Token) 7 | print(t.tag .. t.value) 8 | end 9 | 10 | -- OK: 11 | foo( { 12 | tag = 'string', 13 | value = '42' 14 | } ) 15 | 16 | 17 | -- FAIL: 18 | foo( { 19 | tag = 'wrong', 20 | value = '42' 21 | } ) 22 | -------------------------------------------------------------------------------- /tests/obj_extended.sol: -------------------------------------------------------------------------------- 1 | typedef Type = { AstType : 'StatList' } 2 | 3 | local function foo() -> Type 4 | local obj = {} 5 | obj.AstType = 'StatList' 6 | return obj 7 | end 8 | -------------------------------------------------------------------------------- /tests/obj_medium.sol: -------------------------------------------------------------------------------- 1 | local P = {} 2 | P.ShouldNotShow = 1337 3 | typedef P.StatList = int 4 | var test = 42 : P.StatList 5 | -------------------------------------------------------------------------------- /tests/pack.sol: -------------------------------------------------------------------------------- 1 | local function ret_2_3() 2 | return 2,3 3 | end 4 | 5 | local x = { 1, ret_2_3() } 6 | -------------------------------------------------------------------------------- /tests/pack_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function ret_2_3() 2 | return 2,3 3 | end 4 | 5 | local x = { ret_2_3(), 4 } 6 | -------------------------------------------------------------------------------- /tests/recursion.sol: -------------------------------------------------------------------------------- 1 | local M = {} 2 | function M.fun() -> string 3 | return 'hello' .. M.fun() 4 | end 5 | -------------------------------------------------------------------------------- /tests/ret_type.sol: -------------------------------------------------------------------------------- 1 | local function foo() -> int, string 2 | return 42, "fortytwo" 3 | end -------------------------------------------------------------------------------- /tests/ret_type_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo() -> int, string 2 | return "fortytwo", 42 3 | --return 42, "fortytwo" 4 | end 5 | -------------------------------------------------------------------------------- /tests/scope_else_FAIL.sol: -------------------------------------------------------------------------------- 1 | if true then 2 | else 3 | local x = 0 4 | end 5 | print(x) -- FAIL -------------------------------------------------------------------------------- /tests/scope_elseif_FAIL.sol: -------------------------------------------------------------------------------- 1 | if true then 2 | elseif true then 3 | local x = 0 4 | end 5 | print(x) -- FAIL -------------------------------------------------------------------------------- /tests/scope_if_FAIL.sol: -------------------------------------------------------------------------------- 1 | if true then 2 | local x = 0 3 | end 4 | print(x) -- FAIL -------------------------------------------------------------------------------- /tests/scope_repeat_FAIL.sol: -------------------------------------------------------------------------------- 1 | repeat 2 | local x = 0 3 | until true 4 | print(x) -- FAIL -------------------------------------------------------------------------------- /tests/scope_while_FAIL.sol: -------------------------------------------------------------------------------- 1 | while true then 2 | local x = 0 3 | end 4 | print(x) -- FAIL -------------------------------------------------------------------------------- /tests/self_FAIL.sol: -------------------------------------------------------------------------------- 1 | local Singleton = {} 2 | 3 | function Singleton:member() -> void 4 | end 5 | 6 | function Singleton.static() -> void 7 | end 8 | 9 | function Singleton:bar() -> void 10 | self.member() -- FAIL 11 | Singleton.member() -- FAIL 12 | self:member() -- OK 13 | Singleton:member() -- OK 14 | 15 | self.static() -- OK 16 | Singleton.static() -- OK 17 | self:static() -- FAIL 18 | Singleton:static() -- FAIL 19 | 20 | self.member(self) -- OK (but ugly) 21 | Singleton.member(Singleton) -- OK (but ugly) 22 | self:member(self) -- FAIL 23 | Singleton:member(Singleton) -- FAIL 24 | 25 | self.static(self) -- FAIL 26 | Singleton.static(Singleton) -- FAIL 27 | self:static(self) -- FAIL 28 | Singleton:static(Singleton) -- FAIL 29 | 30 | local other = { 31 | wrong_type = true 32 | } 33 | 34 | Singleton.member(other) -- FAIL 35 | Singleton.member(other) -- FAIL 36 | Singleton.static(other) -- FAIL 37 | Singleton.static(other) -- FAIL 38 | end -------------------------------------------------------------------------------- /tests/self_ref_declare_FAIL.sol: -------------------------------------------------------------------------------- 1 | local x = x -------------------------------------------------------------------------------- /tests/set.sol: -------------------------------------------------------------------------------- 1 | var set = {} : {int} 2 | set[42] = true 3 | var _ = set[42] 4 | -------------------------------------------------------------------------------- /tests/shadowing_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo(arg) 2 | local arg 3 | end 4 | -------------------------------------------------------------------------------- /tests/singleton.sol: -------------------------------------------------------------------------------- 1 | local Singleton = { list = {} } 2 | 3 | function Singleton.fun() 4 | end 5 | -------------------------------------------------------------------------------- /tests/type_comp_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function foo(a: string or int or nil, b: [string] or [int] or nil) 2 | if a == b then 3 | 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /tests/type_export_1.sol: -------------------------------------------------------------------------------- 1 | local TE1 = {} 2 | typedef TE1.Type = { tag : string } 3 | var test = { tag = "test" } : TE1.Type 4 | return TE1 -------------------------------------------------------------------------------- /tests/type_function.sol: -------------------------------------------------------------------------------- 1 | return type(true) == "boolean" 2 | -------------------------------------------------------------------------------- /tests/type_function_FAIL.sol: -------------------------------------------------------------------------------- 1 | return type(true) == "not_a_type_name" 2 | -------------------------------------------------------------------------------- /tests/type_import_1.sol: -------------------------------------------------------------------------------- 1 | local TI1 = require 'type_export_1' 2 | var t = { tag = "hello" } : TI1.Type 3 | -------------------------------------------------------------------------------- /tests/typedef.sol: -------------------------------------------------------------------------------- 1 | typedef Num = int 2 | 3 | local function foo(n : Num) 4 | end 5 | 6 | foo(32) -------------------------------------------------------------------------------- /tests/typedef_namespaced_bad_FAIL.sol: -------------------------------------------------------------------------------- 1 | local M = {} 2 | var i = 42 : M.NoSuchType 3 | -------------------------------------------------------------------------------- /tests/typedef_namespaced_premature_FAIL.sol: -------------------------------------------------------------------------------- 1 | local M = {} 2 | var i = 0 : M.T 3 | typedef M.T int -------------------------------------------------------------------------------- /tests/typedef_premature_FAIL.sol: -------------------------------------------------------------------------------- 1 | var i = 0 : T 2 | typedef T = int -------------------------------------------------------------------------------- /tests/underscore_read.sol: -------------------------------------------------------------------------------- 1 | local _A = unpack({}) -- Discard result 2 | local foo = _A -- OK 3 | -------------------------------------------------------------------------------- /tests/underscore_read_FAIL.sol: -------------------------------------------------------------------------------- 1 | local _ = unpack({}) -- Discard result 2 | local foo = _ -- FAIL 3 | -------------------------------------------------------------------------------- /tests/use_before_declare_FAIL.sol: -------------------------------------------------------------------------------- 1 | print(x) 2 | local x = 0 -------------------------------------------------------------------------------- /tests/var_undeducable_FAIL.sol: -------------------------------------------------------------------------------- 1 | local some_any = require 'you_wont_find_this' 2 | var x = some_any -- FAIL: 'var' must have decuable type, or explicit 'any' 3 | -------------------------------------------------------------------------------- /tests/var_uninitialized_FAIL.sol: -------------------------------------------------------------------------------- 1 | var x 2 | -------------------------------------------------------------------------------- /tests/varargs.sol: -------------------------------------------------------------------------------- 1 | local function ints(... : number) 2 | 3 | end 4 | 5 | local function strings(... : int) 6 | ints( ... ) 7 | end 8 | -------------------------------------------------------------------------------- /tests/varargs_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function ints(... : int) 2 | 3 | end 4 | 5 | local function strings(... : string) 6 | ints( ... ) 7 | end 8 | -------------------------------------------------------------------------------- /tests/varargs_recursion.sol: -------------------------------------------------------------------------------- 1 | local function print_all(head : int?, ... : int) 2 | if head then 3 | print(head) 4 | print_all(...) -- Recurse on tail 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /tests/varargs_recursion_FAIL.sol: -------------------------------------------------------------------------------- 1 | local function print_all(head : string?, ... : int) 2 | if head then 3 | print(head) 4 | print_all(...) -- Recurse on tail - but oh - head is of the wrong type! 5 | end 6 | end 7 | --------------------------------------------------------------------------------