├── .luacheckrc ├── runTest.lua ├── .settings └── org.eclipse.ldt.prefs ├── translateTest.lua ├── .github └── workflows │ └── luacheck.yml ├── .project ├── init.lua ├── LICENSE ├── test.venus ├── testout └── test.lua ├── README.md ├── vc_util.lua └── LuaVenusCompiler.lua /.luacheckrc: -------------------------------------------------------------------------------- 1 | globals = {"LuaVenusCompiler"} 2 | exclude_files = {"testout"} 3 | -------------------------------------------------------------------------------- /runTest.lua: -------------------------------------------------------------------------------- 1 | local vc = dofile("init.lua")("") 2 | 3 | vc.dovenus("test.venus") 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.ldt.prefs: -------------------------------------------------------------------------------- 1 | Grammar__default_id=lua-5.2 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /translateTest.lua: -------------------------------------------------------------------------------- 1 | local vc = dofile("init.lua")("") 2 | local o = "testout/test.lua" 3 | 4 | vc.convert_venus_file("test.venus",o) 5 | 6 | dofile(o) 7 | -------------------------------------------------------------------------------- /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: luacheck 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2.0.0 10 | - name: run luacheck 11 | uses: Roang-zero1/factorio-mod-luacheck@master 12 | with: 13 | LUACHECKRC_URL: https://raw.githubusercontent.com/${{github.repository}}/${{github.sha}}/.luacheckrc 14 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | LuaVenusCompiler 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.dltk.core.scriptbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.ldt.nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | if rawget(_G,"LuaVenusCompiler") then 2 | print("LuaVenusCompiler warning: already initialized") 3 | else 4 | LuaVenusCompiler = {} 5 | end 6 | 7 | function LuaVenusCompiler.loadFromPath(path) 8 | LuaVenusCompiler.path = path 9 | local ret = dofile(path.."LuaVenusCompiler.lua") 10 | for i,v in pairs(ret) do 11 | LuaVenusCompiler[i] = v 12 | end 13 | return ret 14 | end 15 | 16 | return LuaVenusCompiler.loadFromPath 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 theFox6 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.venus: -------------------------------------------------------------------------------- 1 | print("running venus test script") 2 | 3 | local vp_util = dofile("vc_util.lua") 4 | 5 | local function for_range_test() 6 | local a = 0 7 | 8 | for i = 0,5 do 9 | a = a + i 10 | end 11 | 12 | assert(a == 15) 13 | end 14 | 15 | for_range_test() 16 | 17 | local function for_in_test() 18 | local testt = { 19 | venus = "awesome", 20 | "lots of test",1,2, 21 | test2 = "hi" 22 | } 23 | 24 | local reft = {} 25 | for i,el in pairs(testt) do 26 | reft[i] = el 27 | end 28 | assert(vp_util.dftc(reft, testt)) 29 | 30 | reft = {} 31 | for _,el in pairs(testt) do 32 | table.insert(reft,el) 33 | end 34 | 35 | local reft2 = {} 36 | foreach el in testt do 37 | table.insert(reft2,el) 38 | end 39 | assert(vp_util.dftc(reft, reft2)) 40 | end 41 | 42 | for_in_test() 43 | 44 | -- comments 45 | ## yay a comment 46 | ##comment 47 | assert("##"=="#".."#") 48 | -- another comment 49 | assert([[ 50 | ##]]=="#".."#","comment within [[string]] falsely detected") 51 | 52 | assert([[ 53 | fn]]=="f".."n") 54 | 55 | local function shadow_test() 56 | local fn a() 57 | return "function" 58 | end 59 | assert(a()=="function") 60 | 61 | local reft = {} 62 | do 63 | (fn(...) 64 | local a = {...} 65 | foreach a in a do 66 | table.insert(reft,a) 67 | end 68 | end)("a","still a","also a") 69 | end 70 | assert(vp_util.dftc(reft, {"a","still a","also a"})) 71 | 72 | local n 73 | do { 74 | local a = 12 75 | n = a 76 | } 77 | assert(n == 12) 78 | 79 | assert(a()=="function") 80 | end 81 | 82 | shadow_test() 83 | 84 | local function t() { 85 | return "hi" 86 | } 87 | assert(t()=="hi") 88 | 89 | local fn t2() { 90 | return "also hi" 91 | } 92 | assert(type(t2)=="function") 93 | assert(t2()=="also hi") 94 | 95 | local b = true 96 | if (true) { 97 | b = "weewoo" 98 | } 99 | assert(b == "weewoo") 100 | 101 | local reft = {} 102 | for i = 0, 10 { 103 | table.insert(reft,i) 104 | } 105 | assert(vp_util.dftc(reft,{0,1,2,3,4,5,6,7,8,9,10})) 106 | 107 | local reft2 = {} 108 | foreach el in {"lot's of test",2,"3",1} { 109 | table.insert(reft2,el) 110 | } 111 | assert(vp_util.dftc(reft2,{"lot's of test",2,"3",1})) 112 | 113 | do { 114 | local reft = {} 115 | local i = 0 116 | while i < 10 { 117 | i = i + 1 118 | if i%3 == 0 { 119 | table.insert(reft,i) 120 | } elseif i%4 == 0 { 121 | table.insert(reft,i/4) 122 | } else {} 123 | } 124 | assert(vp_util.dftc(reft,{3,1,6,2,9})) 125 | } 126 | 127 | local function callit(fun,t1,t2) 128 | return fun(t1,t2) 129 | end 130 | 131 | assert( 132 | callit(() => { 133 | return "testing" 134 | }) 135 | == "testing") 136 | 137 | assert( 138 | callit((k,v) => { 139 | return k.." = "..v 140 | }, "this test", "more test") 141 | == "this test = more test" 142 | ) 143 | 144 | assert( 145 | callit((a , b) => { 146 | return (a-b)*4 147 | }, 10, 6) == 16 148 | ) 149 | 150 | assert(callit(()=>{},false)==nil) 151 | 152 | --- 153 | --comment 154 | -- 155 | 156 | local i = 0 157 | local j = 0 158 | 159 | i = i + 1 160 | j = j + 2 161 | 162 | local function decj() 163 | j-- 164 | return j-- not a decrement, only returns n, this is a comment 165 | end 166 | assert(decj()==1) 167 | assert(j == 1) 168 | 169 | local fn reti() 170 | -- this only returns i the -- is a comment 171 | return i-- 172 | end 173 | 174 | i++ 175 | assert(reti() == 2) 176 | 177 | -- () => {} 178 | 179 | j+= 3 180 | assert(j == 4) 181 | j *=-8 182 | assert(j ==-32) 183 | j /= -4 184 | assert(j== 8) 185 | j ^= 2 186 | assert(j == 64) 187 | j-= 32 188 | assert(j ==32) 189 | j .=" test" 190 | assert(j == "32 test") 191 | 192 | local tt = { 193 | {"hello", "there"}, 194 | {"venus", "test"} 195 | } 196 | 197 | local fn concatsub(t) { 198 | local ret = {} 199 | foreach el in t { 200 | table.insert(ret,table.concat(el," ")) 201 | } 202 | return ret 203 | } 204 | assert(vp_util.dftc(concatsub(tt),{"hello there", "venus test"})) 205 | 206 | assert(not (() => {if(true){return}return true})()) 207 | 208 | local ctt = {test = "hello"} 209 | ctt.test .= " world" 210 | assert(ctt.test == "hello world") 211 | 212 | print("venus test end") 213 | -------------------------------------------------------------------------------- /testout/test.lua: -------------------------------------------------------------------------------- 1 | print("running venus test script") 2 | 3 | local vp_util = dofile("vc_util.lua") 4 | 5 | local function for_range_test() 6 | local a = 0 7 | 8 | for i = 0,5 do 9 | a = a + i 10 | end 11 | 12 | assert(a == 15) 13 | end 14 | 15 | for_range_test() 16 | 17 | local function for_in_test() 18 | local testt = { 19 | venus = "awesome", 20 | "lots of test",1,2, 21 | test2 = "hi" 22 | } 23 | 24 | local reft = {} 25 | for i,el in pairs(testt) do 26 | reft[i] = el 27 | end 28 | assert(vp_util.dftc(reft, testt) ) 29 | 30 | reft = {} 31 | for _,el in pairs(testt) do 32 | table.insert(reft,el) 33 | end 34 | 35 | local reft2 = {} 36 | for _, el in pairs(testt ) do 37 | table.insert(reft2,el) 38 | end 39 | assert(vp_util.dftc(reft, reft2) ) 40 | end 41 | 42 | for_in_test() 43 | 44 | -- comments 45 | -- yay a comment 46 | --comment 47 | assert("##"=="#".."#") 48 | -- another comment 49 | assert([[ 50 | ##]]=="#".."#","comment within [[string]] falsely detected") 51 | 52 | assert([[ 53 | fn]]=="f".."n") 54 | 55 | local function shadow_test() 56 | local function a() 57 | return "function" 58 | end 59 | assert(a() =="function") 60 | 61 | local reft = {} 62 | do 63 | (function(...) 64 | local a = {...} 65 | for _, a in pairs(a ) do 66 | table.insert(reft,a) 67 | end 68 | end)("a","still a","also a") 69 | end 70 | assert(vp_util.dftc(reft, {"a","still a","also a"})) 71 | 72 | local n 73 | do 74 | local a = 12 75 | n = a 76 | end 77 | assert(n == 12) 78 | 79 | assert(a() =="function") 80 | end 81 | 82 | shadow_test() 83 | 84 | local function t() 85 | return "hi" 86 | end 87 | assert(t() =="hi") 88 | 89 | local function t2() 90 | return "also hi" 91 | end 92 | assert(type(t2) =="function") 93 | assert(t2() =="also hi") 94 | 95 | local b = true 96 | if (true) then 97 | b = "weewoo" 98 | end 99 | assert(b == "weewoo") 100 | 101 | local reft = {} 102 | for i = 0, 10 do 103 | table.insert(reft,i) 104 | end 105 | assert(vp_util.dftc(reft,{0,1,2,3,4,5,6,7,8,9,10})) 106 | 107 | local reft2 = {} 108 | for _, el in pairs({"lot's of test",2,"3",1} ) do 109 | table.insert(reft2,el) 110 | end 111 | assert(vp_util.dftc(reft2,{"lot's of test",2,"3",1})) 112 | 113 | do 114 | local reft = {} 115 | local i = 0 116 | while i < 10 do 117 | i = i + 1 118 | if i%3 == 0 then 119 | table.insert(reft,i) 120 | elseif i%4 == 0 then 121 | table.insert(reft,i/4) 122 | else end 123 | end 124 | assert(vp_util.dftc(reft,{3,1,6,2,9})) 125 | end 126 | 127 | local function callit(fun,t1,t2) 128 | return fun(t1,t2) 129 | end 130 | 131 | assert( 132 | callit(function() 133 | return "testing" 134 | end) 135 | == "testing") 136 | 137 | assert( 138 | callit(function(k,v) 139 | return k.." = "..v 140 | end, "this test", "more test") 141 | == "this test = more test" 142 | ) 143 | 144 | assert( 145 | callit(function(a , b) 146 | return (a-b)*4 147 | end, 10, 6) == 16 148 | ) 149 | 150 | assert(callit(function() end,false)==nil) 151 | 152 | --- 153 | --comment 154 | 155 | 156 | local i = 0 157 | local j = 0 158 | 159 | i = i + 1 160 | j = j + 2 161 | 162 | local function decj() 163 | j = j - 1 164 | return j-- not a decrement, only returns n, this is a comment 165 | end 166 | assert(decj() ==1) 167 | assert(j == 1) 168 | 169 | local function reti() 170 | -- this only returns i the -- is a comment 171 | return i-- 172 | end 173 | 174 | i = i + 1 175 | assert(reti() == 2) 176 | 177 | -- () => {} 178 | 179 | j= j+ 3 180 | assert(j == 4) 181 | j = j *-8 182 | assert(j ==-32) 183 | j = j / -4 184 | assert(j== 8) 185 | j = j ^ 2 186 | assert(j == 64) 187 | j= j- 32 188 | assert(j ==32) 189 | j = j .." test" 190 | assert(j == "32 test") 191 | 192 | local tt = { 193 | {"hello", "there"}, 194 | {"venus", "test"} 195 | } 196 | 197 | local function concatsub(t) 198 | local ret = {} 199 | for _, el in pairs(t ) do 200 | table.insert(ret,table.concat(el," ")) 201 | end 202 | return ret 203 | end 204 | assert(vp_util.dftc(concatsub(tt) ,{"hello there", "venus test"})) 205 | 206 | assert(not (function() if(true) then return end return true end)() ) 207 | 208 | local ctt = {test = "hello"} 209 | ctt.test = ctt.test .. " world" 210 | assert(ctt.test == "hello world") 211 | 212 | print("venus test end") 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LuaVenusCompiler 2 | [![luacheck][luacheck badge]][luacheck workflow] 3 | A compiler that translates Venus files into Lua. Written in Lua. 4 | The compiler reads a Venus file and replaces Venus syntax by Lua syntax. 5 | It can also load and run the result. 6 | 7 | ## Features: 8 | ### "foreach" loop 9 | The `foreach` statement will geneate a `pairs` statement. 10 | ```lua 11 | local table = {2,1,3,"test"} 12 | 13 | foreach el in table { 14 | print(el) 15 | } 16 | ``` 17 | will generate: 18 | ```lua 19 | local table = {2,1,3,"test"} 20 | 21 | for _, el in table do 22 | print(el) 23 | end 24 | ``` 25 | 26 | ### Comments 27 | For comments `--` and `##` can be used 28 | If something follows a `--` it will always be treated as comment 29 | 30 | 31 | ### Functions 32 | `fn` can be used instead of `function`. 33 | ```lua 34 | fn test() { 35 | print("hi") 36 | } 37 | ``` 38 | will generate 39 | ```lua 40 | function test() 41 | print("hi") 42 | end 43 | ``` 44 | 45 | ### Curly braces based syntax 46 | The `do`,`then` and `end` statements can be replaced by curly braces syntax. 47 | They can be used in functions, loops, conditions, etcetera. 48 | For example: 49 | ```lua 50 | do { 51 | local table = {2,1,3,"test","test2",3} 52 | 53 | fn findTest(t) { 54 | repeat { 55 | local found = false 56 | local el = table.remove(t) 57 | if el == "test" { 58 | found = true 59 | } else { 60 | print(el) 61 | } 62 | } until found 63 | } 64 | } 65 | ``` 66 | will generate: 67 | ```lua 68 | do 69 | local table = {2,1,3,"test","test2",3} 70 | 71 | function findTest(t) 72 | repeat 73 | local found = false 74 | local el = table.remove(t) 75 | if el == "test" then 76 | found = true 77 | else 78 | print(el) 79 | end 80 | until found 81 | end 82 | end 83 | ``` 84 | 85 | ### Lambdas / Anonymous functions 86 | Lambda syntax `(args) => {...}` can be used to create anonymous functions. 87 | ```lua 88 | local result 89 | fn store_it(f) { 90 | result = f(10,6) 91 | } 92 | 93 | store_it((a,b) => { 94 | return (a - b) * 2 95 | }) 96 | ``` 97 | will generate: 98 | ```lua 99 | local result 100 | function store_it(f) 101 | result = f(10,6) 102 | end 103 | 104 | store_it(function(a,b) 105 | return (a - b) * 2 106 | end) 107 | ``` 108 | 109 | ### Increment and Decrement 110 | `++` and `--` can be used to add/sub by 1 111 | ```lua 112 | local i = 0 113 | local j = 0 114 | 115 | i++ 116 | j-- 117 | ``` 118 | will generate: 119 | ```lua 120 | local i = 0 121 | local j = 0 122 | 123 | i = i + 1 124 | j = j - 1 125 | ``` 126 | 127 | ### assignments 128 | Assignment operators `+=`, `-=`, `*=`, `/=`, `^=` and `.=` can be used for math on variables. 129 | ```lua 130 | local a = 0 131 | -- Increased by 132 | a += 2 133 | ## Decreased by 134 | a -= 1 135 | ## Multiplied by 136 | a *= 8 137 | -- Divided by 138 | a /= 2 139 | -- Powered by 140 | a ^= 3 141 | ## Concatenate string 142 | a .= " str" 143 | ``` 144 | will generate 145 | ```lua 146 | local a = 0 147 | -- Increased by 148 | a = a + 2 149 | -- Decreased by 150 | a = a - 1 151 | -- Multiplied by 152 | a = a * 8 153 | -- Divided by 154 | a = a / 2 155 | -- Powered by 156 | a = a ^ 3 157 | -- Concatenate string 158 | a = a .. " str" 159 | ``` 160 | 161 | ## Working with the compiler 162 | ### Loading 163 | The init.lua returns a function for loading the compiler. 164 | You have to call it with the path to the script itself as argument. 165 | In case you have the LuaVenusCompiler directory within your project's 166 | ways of loding it may be: 167 | ```lua 168 | --in case your project is run within it's own folder 169 | local vc = dofile("LuaVenusCompiler/init.lua")("LuaVenusCompiler/") 170 | --in case you have a variable called project_loc containing the path to your projects folder 171 | local vc = dofile(project_loc.."/LuaVenusCompiler/init.lua")(project_loc.."/LuaVenusCompiler/") 172 | --using require 173 | local vc = require("LuaVenusCompiler")("LuaVenusCompiler/") 174 | ``` 175 | When it is loaded it can also be accessed with the global called "LuaVenusCompiler". 176 | 177 | ### Running Venus files 178 | `vc.dovenus(file)` works like `dofile(file)` 179 | It's argument can be a relative or absolute path to the file that should be run. 180 | 181 | ### Loading Venus files 182 | `vc.loadvenus(file)` works like `loadfile(file)` 183 | It's argument can be a relative or absolute path to the file that should be loaded. 184 | It returns a function that runs the generated lua. 185 | 186 | ### Generating Lua code 187 | `vc.tl_venus_file(file)` returns the lua generated from the files contents 188 | It's argument can be a relative or absolute path to the file that should be translated. 189 | It returns the generated lua as string. 190 | 191 | `vc.tl_venus_string(str)` returns the lua generated from the given string 192 | It returns the generated lua as string. 193 | 194 | ### Generating Lua files 195 | `vc.convert_venus_file(venus_file_in,lua_file_out)` generates a lua file 196 | It's arguments can be relative or absolute paths. 197 | The venus_file_in will be converted to lua and written to lua_file_out. 198 | 199 | [luacheck badge]: https://github.com/theFox6/LuaVenusCompiler/workflows/luacheck/badge.svg 200 | [luacheck workflow]: https://github.com/theFox6/LuaVenusCompiler/actions?query=workflow%3Aluacheck 201 | -------------------------------------------------------------------------------- /vc_util.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --A module with lots of helpers for the LuaVenusCompiler. 3 | -- 4 | --@module vc_util 5 | local vc_util = {} 6 | 7 | --- 8 | --Find the first match of a set of patterns within a string. 9 | -- 10 | --The pattern, that can be found at the earliest position within the string is used to match. 11 | --If two patterns are at the same position the shorter match is returned. 12 | -- 13 | --@function [parent=#vc_util] find_min_match 14 | --@param #string str the string to be searched for the patterns 15 | --@param patterns the pattern or patterns that are searched within the string 16 | --@return the starting and the end position of the match 17 | function vc_util.find_min_match(str,patterns) 18 | local pats 19 | local patt = type(patterns) 20 | if patt == "string" then 21 | pats = {patterns} 22 | elseif patt == "table" then 23 | pats = patterns 24 | else 25 | error(("bad argument #2 to optmatch, expected string or table got %s"):format(patterns),2) 26 | end 27 | local minspos 28 | local matchepos 29 | for _, pat in pairs(pats) do 30 | local spos,epos = str:find(pat) 31 | if spos then 32 | if not minspos then 33 | minspos = spos 34 | matchepos = epos 35 | elseif spos < minspos then 36 | minspos = spos 37 | matchepos = epos 38 | elseif spos == minspos then 39 | if epos < matchepos then 40 | minspos = spos 41 | matchepos = epos 42 | end 43 | end 44 | end 45 | end 46 | return minspos, matchepos 47 | end 48 | 49 | --- 50 | --A generator to iterate over a string splitting it wherever matches of a pattern can be found. 51 | -- 52 | --A single or multiple patterns are searched within a string. 53 | --If the string starts with a matching sequence the sequence itself is given to the iteration. 54 | --If a matching sequence is found further within the string the sequence before the match is given to the iteration. 55 | --Every sequence given to the iteration is removed from the iterator. 56 | --Like this it will traverse the string splitting it into the matches and non-matches. 57 | -- 58 | --@function [parent=#vc_util] optmatch 59 | --@param #string str the string to search for matches 60 | --@param patterns the pattern or patterns to split by 61 | --@return #function the iterator for a loop 62 | function vc_util.optmatch(str,patterns) 63 | local cutstr = str 64 | return function() 65 | if not cutstr then return end 66 | local spos, epos = vc_util.find_min_match(cutstr,patterns) 67 | local match 68 | local found = (spos == 1) 69 | if found then 70 | match = cutstr:sub(1,epos) 71 | cutstr = cutstr:sub(epos+1) 72 | --print("f",match,cutstr,pat) 73 | elseif not spos then 74 | if cutstr == "" then 75 | match = nil 76 | else 77 | match = cutstr 78 | end 79 | cutstr = nil 80 | --print("n",match,pat) 81 | else 82 | match = cutstr:sub(1,spos-1) 83 | cutstr = cutstr:sub(spos) 84 | --print("p",match,cutstr,pat) 85 | end 86 | return match, found 87 | end 88 | end 89 | 90 | --- 91 | --A function generating a table from an iterator. 92 | -- 93 | --@function [parent=#vc_util] gen_table 94 | --@param #function it the iterator to use in a for loop 95 | --@return #table the table containing the returns of the iterator 96 | function vc_util.gen_table(it) 97 | local tab = {} 98 | for el in it do 99 | table.insert(tab,el) 100 | end 101 | return tab 102 | end 103 | 104 | --- 105 | --**double flat table compare** 106 | -- 107 | --A function comparing the contents of two tables. 108 | -- 109 | --It iterates over both tables checking if the other contains the same elements. 110 | -- 111 | --@function [parent=#vc_util] dftc 112 | --@param #table t1 the table to compare with t2 113 | --@param #table t2 the table to compare with t1 114 | --@return #boolean whether the tables contents are the same 115 | function vc_util.dftc(t1,t2) 116 | for i,el in pairs(t1) do 117 | if t2[i] ~= el then 118 | return false 119 | end 120 | end 121 | for i,el in pairs(t2) do 122 | if t1[i] ~= el then 123 | return false 124 | end 125 | end 126 | return true 127 | end 128 | 129 | --- 130 | --concatenate strings 131 | --if one string is nil the other is returned 132 | -- 133 | --@function [parent=#vc_util] concat_optnil 134 | --@param #string fstr the first string to be concatenated 135 | --@param #string lstr the second string to be concatenated 136 | --@param #string sep the seperator to be added between the strings if both are present 137 | --@param #string retstr The string returned if both strings are empty. 138 | -- Can be true to return an empty string. 139 | function vc_util.concat_optnil(fstr,lstr,sep,retstr) 140 | if fstr then 141 | if lstr then 142 | if sep then 143 | if fstr == "" or lstr == "" then 144 | return fstr..lstr 145 | else 146 | return fstr..sep..lstr 147 | end 148 | else 149 | return fstr..lstr 150 | end 151 | else 152 | return fstr 153 | end 154 | else 155 | if lstr then 156 | return lstr 157 | else 158 | if retstr == true then 159 | return "" 160 | elseif retstr then 161 | return retstr 162 | else 163 | return nil 164 | end 165 | end 166 | end 167 | end 168 | 169 | --- 170 | --The unit tests for the vc utilities. 171 | local function tests() 172 | assert(vc_util.dftc({},{})) 173 | assert(vc_util.dftc({1},{1})) 174 | assert(not vc_util.dftc({1},{2})) 175 | assert(vc_util.dftc({1,"2",true},{1,"2",true})) 176 | assert(not vc_util.dftc({true,"1",1},{1,1,1})) 177 | 178 | assert(vc_util.dftc(vc_util.gen_table(vc_util.optmatch("123","123")),{"123"})) 179 | assert(vc_util.dftc(vc_util.gen_table(vc_util.optmatch("123","321")),{"123"})) 180 | assert(vc_util.dftc(vc_util.gen_table(vc_util.optmatch("123", "1")), {"1","23"})) 181 | assert(vc_util.dftc(vc_util.gen_table(vc_util.optmatch("123", "2")), {"1","2","3"})) 182 | end 183 | 184 | tests() 185 | 186 | return vc_util 187 | -------------------------------------------------------------------------------- /LuaVenusCompiler.lua: -------------------------------------------------------------------------------- 1 | local local_path = LuaVenusCompiler.path or "" 2 | local vc_util = dofile(local_path.."vc_util.lua") 3 | 4 | local compiler = {} 5 | 6 | local elements = { 7 | names = "^(%a%w*)$", 8 | spaces = "(%s+)", 9 | special = "[%%%(%)%{%}%;%,]", 10 | strings = "[\"']", 11 | special_combined = "([%+%-%*/%^#=~<>%[%]:%.][%+%-/#=>%[%]:%.]?[%.]?)", 12 | lambda_args = "[,%(%)]" 13 | } 14 | 15 | local non_space_elements = {elements.special_combined,elements.special,elements.strings} 16 | 17 | function compiler.warn(msg) 18 | print("LuaVenusCompiler warning: " .. msg) 19 | end 20 | 21 | --TODO: check if spaces before a curly brace were already added 22 | 23 | --TODO: make some functions handling each group of commands 24 | local function parse_element(el,pc) 25 | if el == "" then 26 | return el 27 | end 28 | local prefix 29 | local precurlch = false 30 | if el == "elseif" then 31 | if pc.ifend then 32 | pc.ifend = false 33 | end 34 | pc.curlyopt = "if" 35 | elseif el == "else" then 36 | pc.ifend = false 37 | pc.curlyopt = true 38 | pc.precurly = el 39 | precurlch = true 40 | elseif pc.ifend then 41 | if pc.linestart then 42 | prefix = pc.ifend 43 | else 44 | prefix = " "..pc.ifend 45 | end 46 | pc.ifend = false 47 | end 48 | 49 | if pc.deccheck then 50 | local cpos = pc.deccheck 51 | pc.deccheck = false 52 | pc.optassign = false 53 | if cpos == true then 54 | pc.slcomm = true 55 | return "--"..el 56 | else 57 | pc.slcomm = true 58 | return "--"..cpos..el 59 | end 60 | end 61 | 62 | if el == "=>" then 63 | if not pc.lambargs then 64 | compiler.warn(("invalid lambda in line %i"):format(pc.line)) 65 | return el 66 | end 67 | local larg = pc.lambargs 68 | pc.lambargs = false 69 | pc.lambend = false 70 | pc.precurly = "function" 71 | pc.curlyopt = true 72 | return "function" .. larg .. " " 73 | elseif pc.lambend then 74 | if prefix then 75 | compiler.warn(("end statement and lambda match end may be mixed in line %i"):format(pc.line)) 76 | prefix = pc.lambargs .. prefix 77 | else 78 | prefix = pc.lambargs 79 | end 80 | pc.lambargs = false 81 | pc.lambend = false 82 | end 83 | 84 | if el == '"' or el == "'" then 85 | if not pc.instring then 86 | pc.instring = el 87 | elseif pc.instring == el then 88 | pc.instring = false 89 | end 90 | elseif el == "[[" then 91 | if not pc.instring then 92 | pc.instring = el 93 | end 94 | elseif el == "]]" then 95 | if pc.instring == "[[" then 96 | pc.instring = false 97 | end 98 | elseif pc.instring then 99 | return el,prefix 100 | end 101 | 102 | if pc.foreach == 2 then 103 | pc.foreach = 3 104 | if el == "{" then 105 | table.insert(pc.opencurly, "table") 106 | end 107 | return "pairs("..el,prefix 108 | elseif el == "{" then 109 | if pc.foreach == 3 then 110 | pc.foreach = 0 111 | table.insert(pc.opencurly, "for") 112 | pc.curlyopt = false 113 | return ") do ",prefix 114 | elseif not pc.curlyopt then 115 | table.insert(pc.opencurly, "table") 116 | return el,prefix 117 | elseif pc.curlyopt == true then 118 | if pc.precurly == "function" or pc.precurly == "repeat" or 119 | pc.precurly == "else" or pc.precurly == "do" then 120 | table.insert(pc.opencurly, pc.precurly) 121 | pc.precurly = false 122 | pc.curlyopt = false 123 | return "",prefix 124 | end 125 | elseif pc.curlyopt == "for" or pc.curlyopt == "while" then 126 | table.insert(pc.opencurly, pc.curlyopt) 127 | pc.curlyopt = false 128 | return " do ",prefix 129 | elseif pc.curlyopt == "if" then 130 | table.insert(pc.opencurly, pc.curlyopt) 131 | pc.curlyopt = false 132 | return " then ",prefix 133 | end 134 | elseif pc.precurly and not precurlch then 135 | pc.precurly = false 136 | pc.curlyopt = false 137 | end 138 | 139 | if el == "}" then 140 | local closecurly = table.remove(pc.opencurly) 141 | if closecurly == "table" then 142 | return el,prefix 143 | elseif closecurly == "repeat" then 144 | return "",prefix 145 | elseif closecurly == "for" or closecurly == "while" or 146 | closecurly == "function" or closecurly == "repeat" or 147 | closecurly == "do" or closecurly == "else" then 148 | if pc.linestart then 149 | return "end",prefix 150 | else 151 | return " end",prefix 152 | end 153 | elseif closecurly == "if" then 154 | pc.ifend = "end" 155 | return "",prefix 156 | else 157 | compiler.warn(("closing curly bracket in line %i could not be matched to an opening one"):format(pc.line)) 158 | return el,prefix 159 | end 160 | elseif el == "foreach" then 161 | pc.curlyopt = "for" 162 | pc.foreach = 1 163 | return "for _,",prefix 164 | elseif el == "for" then 165 | pc.curlyopt = el 166 | pc.foreach = 0 167 | elseif el == "in" then 168 | if pc.foreach == 1 then 169 | pc.foreach = 2 170 | end 171 | elseif el == "do" then 172 | pc.curlyopt = true 173 | pc.precurly = "do" 174 | if pc.foreach == 3 then 175 | pc.foreach = 0 176 | return ") " .. el,prefix 177 | end 178 | elseif el == "while" then 179 | pc.curlyopt = el 180 | elseif el == "repeat" then 181 | pc.precurly = el 182 | pc.curlyopt = true 183 | elseif el == "if" then 184 | pc.curlyopt = el 185 | elseif el == "then" then 186 | pc.curlyopt = false 187 | elseif el == "fn" then 188 | pc.curlyopt = "function" 189 | return "function",prefix 190 | elseif el == "function" then 191 | pc.curlyopt = el 192 | elseif el == "(" then 193 | pc.newlamb = el 194 | pc.lambend = false 195 | return "",prefix 196 | elseif el == ")" then 197 | if pc.curlyopt == "function" then 198 | pc.precurly = pc.curlyopt 199 | pc.curlyopt = true 200 | end 201 | if pc.lambargs then 202 | pc.lambend = true 203 | end 204 | elseif el=="##" then 205 | if not pc.instring then 206 | pc.slcomm = true 207 | return "--",prefix 208 | end 209 | elseif el == "--" then 210 | if pc.optassign then 211 | pc.deccheck = true 212 | return "",prefix 213 | else 214 | pc.slcomm = true 215 | return el,prefix 216 | end 217 | elseif el == "++" then 218 | if pc.optassign then 219 | local nam = pc.optassign 220 | pc.optassign = false 221 | return " = " .. nam .. " + 1" 222 | else 223 | compiler.warn(("empty increment in line %i"):format(pc.line)) 224 | return el, prefix 225 | end 226 | elseif el == "+=" then 227 | if pc.optassign then 228 | local nam = pc.optassign 229 | pc.optassign = false 230 | return "= " .. nam .. "+" 231 | else 232 | compiler.warn(("empty increment assignment in line %i"):format(pc.line)) 233 | end 234 | elseif el == "-=" then 235 | if pc.optassign then 236 | local nam = pc.optassign 237 | pc.optassign = false 238 | return "= " .. nam .. "-" 239 | else 240 | compiler.warn(("empty decrement assignment in line %i"):format(pc.line)) 241 | end 242 | elseif el == "*=" then 243 | if pc.optassign then 244 | local nam = pc.optassign 245 | pc.optassign = false 246 | return "= " .. nam .. "*" 247 | else 248 | compiler.warn(("empty multiply assignment in line %i"):format(pc.line)) 249 | end 250 | elseif el == "/=" then 251 | if pc.optassign then 252 | local nam = pc.optassign 253 | pc.optassign = false 254 | return "= " .. nam .. "/" 255 | else 256 | compiler.warn(("empty divide assignment in line %i"):format(pc.line)) 257 | end 258 | elseif el == "^=" then 259 | if pc.optassign then 260 | local nam = pc.optassign 261 | pc.optassign = false 262 | return "= " .. nam .. "^" 263 | else 264 | compiler.warn(("empty power assignment in line %i"):format(pc.line)) 265 | end 266 | elseif el == ".=" then 267 | if pc.optassign then 268 | local nam = pc.optassign 269 | pc.optassign = false 270 | return "= " .. nam .. ".." 271 | else 272 | compiler.warn(("empty concatenation assignment in line %i"):format(pc.line)) 273 | end 274 | end 275 | --print(el,pc.instring and "in string" or "") 276 | return el, prefix 277 | end 278 | 279 | local function store_space(sp,pc) 280 | if pc.optassign then 281 | if pc.optassign ~= true then 282 | pc.optassign = pc.optassign .. sp 283 | end 284 | end 285 | if pc.lambargs then 286 | pc.lambargs = pc.lambargs .. sp 287 | return false 288 | elseif pc.deccheck then 289 | if pc.deccheck == true then 290 | pc.deccheck = sp 291 | return false 292 | else 293 | pc.deccheck = pc.deccheck .. sp 294 | return false 295 | end 296 | else 297 | return true 298 | end 299 | end 300 | 301 | local function handle_prefix(el,p) 302 | local pre = p 303 | local lpre 304 | if pre then 305 | while pre:match("\n") do 306 | if lpre then 307 | lpre = lpre .. pre:sub(1,pre:find("\n")) 308 | else 309 | lpre = pre:sub(1,pre:find("\n")) 310 | end 311 | pre = pre:sub(pre:find("\n")+1) 312 | end 313 | local pres = pre:match("^%s*") or "" 314 | if lpre then 315 | lpre = lpre .. pres 316 | else 317 | lpre = pres 318 | end 319 | pre = pre:sub(#pres+1) 320 | --[[ 321 | if (pre ~= "") then 322 | print("pre:".. pre..":") 323 | else 324 | print("prel:" .. el) 325 | end 326 | --]] 327 | return vc_util.concat_optnil(pre,el," "),lpre 328 | end 329 | return el 330 | end 331 | 332 | local function store_lambargs(e,pc) 333 | local el = e 334 | if pc.newlamb then 335 | if pc.lambargs then 336 | el = pc.lambargs .. el 337 | end 338 | pc.lambargs = pc.newlamb 339 | pc.newlamb = false 340 | --print("newl:", pc.lambargs, el) 341 | elseif pc.lambargs then 342 | if el:match(elements.names) or el:match(elements.lambda_args) then 343 | pc.lambargs = pc.lambargs .. el 344 | el = "" 345 | elseif el ~= "" then 346 | el = pc.lambargs .. el 347 | pc.lambargs = false 348 | pc.lambend = false 349 | --print("notl:", el) 350 | end 351 | end 352 | return el 353 | end 354 | 355 | local function store_optassign(el,pc) 356 | if pc.optassign and el ~= "" then 357 | if (pc.linestart and el:match(elements.names)) or 358 | (pc.optassign and el == ".") or 359 | (pc.optassign and pc.optassign ~= true and 360 | pc.optassign:sub(#pc.optassign) == "." and el:match(elements.names)) then 361 | if pc.optassign == true then 362 | pc.optassign = el 363 | else 364 | pc.optassign = pc.optassign .. el 365 | end 366 | elseif el ~= "--" then 367 | pc.optassign = false 368 | end 369 | end 370 | end 371 | 372 | local function parse_line(l,pc) 373 | local pl = "" 374 | for sp,s in vc_util.optmatch(l,elements.spaces) do 375 | if s then 376 | if store_space(sp,pc) then 377 | pl = pl .. sp 378 | end 379 | else 380 | for st in vc_util.optmatch(sp,non_space_elements) do 381 | if pc.slcomm then 382 | pl = pl .. st 383 | else 384 | local el,lpre = handle_prefix(parse_element(st,pc)) 385 | el = store_lambargs(el,pc) 386 | store_optassign(el,pc) 387 | if lpre then 388 | pl = pl .. lpre .. el 389 | else 390 | pl = pl .. el 391 | end 392 | if pc.linestart and el ~= "" then 393 | pc.linestart = false 394 | end 395 | end 396 | end 397 | end 398 | end 399 | return pl 400 | end 401 | 402 | local function handle_linestart(pc) 403 | pc.line = pc.line + 1 404 | pc.linestart = true 405 | pc.optassign = true 406 | end 407 | 408 | local function handle_lineend_curly(pc) 409 | if pc.ifend then 410 | local ret 411 | if pc.linestart then 412 | ret = pc.ifend 413 | else 414 | ret = " " .. pc.ifend 415 | end 416 | pc.ifend = false 417 | return ret 418 | end 419 | return "" 420 | end 421 | 422 | local function handle_lineend_decrement(pc) 423 | if pc.deccheck then 424 | if pc.optassign == false then 425 | pc.deccheck = false 426 | elseif pc.optassign == true then 427 | pc.deccheck = false 428 | else 429 | local ret = " = " .. pc.optassign .. " - 1" 430 | pc.deccheck = false 431 | pc.optassign = false 432 | return ret 433 | end 434 | end 435 | return "" 436 | end 437 | 438 | local function handle_lineend_lambargs(pc) 439 | if pc.lambargs then 440 | pc.lambargs = pc.lambargs .. "\n" 441 | else 442 | return "\n" 443 | end 444 | return "" 445 | end 446 | 447 | function compiler.tl_venus_string(str) 448 | local fc = "" 449 | local pc = {instring = false, opencurly = {}, line = 0} 450 | for l,e in vc_util.optmatch(str,"\n") do 451 | if e then 452 | if pc.slcomm then 453 | pc.slcomm = false 454 | fc = fc .. "\n" 455 | else 456 | fc = fc .. handle_lineend_curly(pc) 457 | fc = fc .. handle_lineend_decrement(pc) 458 | fc = fc .. handle_lineend_lambargs(pc) 459 | end 460 | else 461 | handle_linestart(pc) 462 | fc = fc .. parse_line(l,pc) 463 | end 464 | end 465 | if (#pc.opencurly > 0) then 466 | compiler.warn("not all curly brackets were closed") 467 | end 468 | return fc 469 | end 470 | 471 | function compiler.tl_venus_file(file) 472 | local f = io.open(file) 473 | local ret = compiler.tl_venus_string(f:read("*a")) 474 | f:close() 475 | return ret 476 | end 477 | 478 | function compiler.loadvenus(file,env) 479 | local fc = compiler.tl_venus_file(file) 480 | if env then 481 | return loadstring(fc,"@"..file,"t",env) 482 | else 483 | return loadstring(fc,"@"..file) 484 | end 485 | end 486 | 487 | function compiler.dovenus(file) 488 | local ff, err = compiler.loadvenus(file) 489 | if ff == nil then 490 | error(err,2) 491 | end 492 | return ff() 493 | end 494 | 495 | function compiler.convert_venus_file(venus_file_in,lua_file_out) 496 | local s = compiler.tl_venus_file(venus_file_in) 497 | local f = io.open(lua_file_out,"w") 498 | f:write(s) 499 | f:close() 500 | end 501 | 502 | return compiler 503 | --------------------------------------------------------------------------------