From daca0d974d4bfa36561dc419d8b1b9a84bfe53e7 Mon Sep 17 00:00:00 2001 From: Robert Jelic Date: Sun, 9 Feb 2025 15:18:23 +0100 Subject: [PATCH] Test custom minifier --- .github/workflows/minify.yml | 55 +- minify.lua | 391 ++++++++++ release/project.lua | 1315 ---------------------------------- src/main.lua | 1 - 4 files changed, 412 insertions(+), 1350 deletions(-) create mode 100644 minify.lua delete mode 100644 release/project.lua diff --git a/.github/workflows/minify.yml b/.github/workflows/minify.yml index ed004b8..c964cfb 100644 --- a/.github/workflows/minify.yml +++ b/.github/workflows/minify.yml @@ -25,44 +25,31 @@ jobs: sudo apt-get update sudo apt-get install -y lua5.3 - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '16' - - - name: Install Lua minifier - run: | - npm install luamin - echo "Testing luamin installation:" - npx luamin --version || echo "luamin version check failed" - - - name: Combine and Minify Lua files + - name: Minify Lua files run: | mkdir -p release - echo "Checking Lua syntax first..." - if lua -e "loadfile('src/main.lua')" ; then - echo "Lua syntax is valid, proceeding with minification..." - - echo "Content of main.lua:" - cat src/main.lua - - echo "Attempting minification with direct input..." - # Versuche direkten Input statt Datei - lua_content=$(cat src/main.lua) - if echo "$lua_content" | npx luamin > release/main.min.lua; then - echo "Main.lua minification successful" - echo "Minified content:" - cat release/main.min.lua - else - echo "Minification failed. Error code: $?" - echo "luamin path: $(which npx luamin)" - echo "node version: $(node -v)" - echo "npx version: $(npx -v)" - exit 1 - fi + echo "Creating minification script..." + cat > minify_script.lua << 'EOL' + local minify = loadfile("minify.lua")() + local f = io.open("src/main.lua", "r") + local content = f:read("*all") + f:close() + + local minified = minify(content) + + local out = io.open("release/main.min.lua", "w") + out:write(minified) + out:close() + EOL + + echo "Running minification..." + if lua minify_script.lua; then + echo "Minification successful" + echo "Minified content:" + cat release/main.min.lua else - echo "Lua syntax error detected in main.lua" + echo "Minification failed" exit 1 fi diff --git a/minify.lua b/minify.lua new file mode 100644 index 0000000..2451875 --- /dev/null +++ b/minify.lua @@ -0,0 +1,391 @@ +function lookupify(cd)for dd,__a in pairs(cd)do cd[__a]=true end;return cd end +function CountTable(cd)local dd=0;for __a in pairs(cd)do dd=dd+1 end;return dd end +function PrintTable(cd,dd)if cd.Print then return cd.Print()end;dd=dd or 0 +local __a=(CountTable(cd)>1)local a_a=string.rep(' ',dd+1) +local b_a="{".. (__a and'\n'or'') +for c_a,d_a in pairs(cd)do +if type(d_a)~='function'then +b_a=b_a.. (__a and a_a or'') +if type(c_a)=='number'then elseif type(c_a)=='string'and +c_a:match("^[A-Za-z_][A-Za-z0-9_]*$")then b_a=b_a..c_a.." = "elseif +type(c_a)=='string'then b_a=b_a.."[\""..c_a.."\"] = "else b_a=b_a.."[".. +tostring(c_a).."] = "end +if type(d_a)=='string'then b_a=b_a.."\""..d_a.."\""elseif type(d_a)== +'number'then b_a=b_a..d_a elseif type(d_a)=='table'then b_a=b_a.. +PrintTable(d_a,dd+ (__a and 1 or 0))else +b_a=b_a..tostring(d_a)end;if next(cd,c_a)then b_a=b_a..","end;if __a then b_a=b_a..'\n'end end end;b_a=b_a.. +(__a and string.rep(' ',dd)or'').."}"return b_a end;local bb=lookupify{' ','\n','\t','\r'} +local cb={['\r']='\\r',['\n']='\\n',['\t']='\\t',['"']='\\"',["'"]="\\'"} +local db=lookupify{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'} +local _c=lookupify{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} +local ac=lookupify{'0','1','2','3','4','5','6','7','8','9'} +local bc=lookupify{'0','1','2','3','4','5','6','7','8','9','A','a','B','b','C','c','D','d','E','e','F','f'} +local cc=lookupify{'+','-','*','/','^','%',',','{','}','[',']','(',')',';','#'} +local dc=lookupify{'and','break','do','else','elseif','end','false','for','function','goto','if','in','local','nil','not','or','repeat','return','then','true','until','while'} +function LexLua(cd)local dd={} +local __a,a_a=pcall(function()local _aa=1;local aaa=1;local baa=1 +local function caa()local cba=cd:sub(_aa,_aa)if cba=='\n'then baa=1 +aaa=aaa+1 else baa=baa+1 end;_aa=_aa+1;return cba end +local function daa(cba)cba=cba or 0;return cd:sub(_aa+cba,_aa+cba)end;local function _ba(cba)local dba=daa() +for i=1,#cba do if dba==cba:sub(i,i)then return caa()end end end;local function aba(cba) +return error(">> :"..aaa..":".. +baa..": "..cba,0)end +local function bba()local cba=_aa +if daa()=='['then local dba=0;while +daa(dba+1)=='='do dba=dba+1 end +if daa(dba+1)=='['then for _=0,dba+1 do caa()end +local _ca=_aa +while true do if daa()==''then +aba("Expected `]"..string.rep('=',dba).."]` near .",3)end;local cca=true;if daa()==']'then for i=1,dba do if daa(i)~='='then +cca=false end end +if daa(dba+1)~=']'then cca=false end else cca=false end;if cca then break else +caa()end end;local aca=cd:sub(_ca,_aa-1)for i=0,dba+1 do caa()end +local bca=cd:sub(cba,_aa-1)return aca,bca else return nil end else return nil end end +while true do local cba='' +while true do local dca=daa() +if bb[dca]then cba=cba..caa()elseif +dca=='-'and daa(1)=='-'then caa()caa()cba=cba..'--'local _da,ada=bba() +if ada then cba=cba..ada else while daa()~='\n'and +daa()~=''do cba=cba..caa()end end else break end end;local dba=aaa;local _ca=baa +local aca=":"..aaa..":"..baa..":> "local bca=daa()local cca=nil +if bca==''then cca={Type='Eof'}elseif +_c[bca]or db[bca]or bca=='_'then local dca=_aa;repeat caa()bca=daa()until not +(_c[bca]or db[bca]or ac[bca]or bca=='_') +local _da=cd:sub(dca,_aa-1) +if dc[_da]then cca={Type='Keyword',Data=_da}else cca={Type='Ident',Data=_da}end elseif ac[bca]or(daa()=='.'and ac[daa(1)])then local dca=_aa +if bca=='0'and +daa(1)=='x'then caa()caa()while bc[daa()]do caa()end;if _ba('Pp')then +_ba('+-')while ac[daa()]do caa()end end else +while ac[daa()]do caa()end;if _ba('.')then while ac[daa()]do caa()end end;if _ba('Ee')then +_ba('+-')while ac[daa()]do caa()end end end;cca={Type='Number',Data=cd:sub(dca,_aa-1)}elseif bca=='\''or bca== +'\"'then local dca=_aa;local _da=caa()local ada=_aa;while true do local dda=caa() +if dda=='\\'then caa()elseif +dda==_da then break elseif dda==''then aba("Unfinished string near ")end end;local bda=cd:sub(ada, +_aa-2)local cda=cd:sub(dca,_aa-1) +cca={Type='String',Data=cda,Constant=bda}elseif bca=='['then local dca,_da=bba() +if _da then cca={Type='String',Data=_da,Constant=dca}else +caa()cca={Type='Symbol',Data='['}end elseif _ba('>=<')then if _ba('=')then cca={Type='Symbol',Data=bca..'='}else +cca={Type='Symbol',Data=bca}end elseif _ba('~')then +if _ba('=')then +cca={Type='Symbol',Data='~='}else aba("Unexpected symbol `~` in source.",2)end elseif _ba('.')then +if _ba('.')then if _ba('.')then cca={Type='Symbol',Data='...'}else +cca={Type='Symbol',Data='..'}end else cca={Type='Symbol',Data='.'}end elseif _ba(':')then if _ba(':')then cca={Type='Symbol',Data='::'}else +cca={Type='Symbol',Data=':'}end elseif cc[bca]then caa() +cca={Type='Symbol',Data=bca}else local dca,_da=bba()if dca then cca={Type='String',Data=_da,Constant=dca}else +aba("Unexpected Symbol `".. +bca.."` in source.",2)end end;cca.LeadingWhite=cba;cca.Line=dba;cca.Char=_ca +cca.Print=function() +return"<".. (cca.Type..string.rep(' ',7 -# +cca.Type)).. +" ".. (cca.Data or'').." >"end;dd[#dd+1]=cca;if cca.Type=='Eof'then break end end end)if not __a then return false,a_a end;local b_a={}local c_a={}local d_a=1 +function b_a:Peek(_aa)_aa=_aa or 0;return dd[math.min( +#dd,d_a+_aa)]end +function b_a:Get()local _aa=dd[d_a]d_a=math.min(d_a+1,#dd)return _aa end;function b_a:Is(_aa)return b_a:Peek().Type==_aa end;function b_a:Save()c_a[ +#c_a+1]=d_a end +function b_a:Commit()c_a[#c_a]=nil end;function b_a:Restore()d_a=c_a[#c_a]c_a[#c_a]=nil end +function b_a:ConsumeSymbol(_aa) +local aaa=self:Peek() +if aaa.Type=='Symbol'then if _aa then +if aaa.Data==_aa then self:Get()return true else return nil end else self:Get()return aaa end else return +nil end end +function b_a:ConsumeKeyword(_aa)local aaa=self:Peek()if +aaa.Type=='Keyword'and aaa.Data==_aa then self:Get()return true else return nil end end;function b_a:IsKeyword(_aa)local aaa=b_a:Peek()return +aaa.Type=='Keyword'and aaa.Data==_aa end +function b_a:IsSymbol(_aa) +local aaa=b_a:Peek()return aaa.Type=='Symbol'and aaa.Data==_aa end +function b_a:IsEof()return b_a:Peek().Type=='Eof'end;return true,b_a end +function ParseLua(cd)local dd,__a=LexLua(cd)if not dd then return false,__a end +local function a_a(ada)local bda=">> :".. + +__a:Peek().Line..":"..__a:Peek().Char..": "..ada.."\n"local cda=0 +for dda in +cd:gmatch("[^\n]*\n?")do if dda:sub(-1,-1)=='\n'then dda=dda:sub(1,-2)end;cda= +cda+1 +if cda==__a:Peek().Line then bda=bda..">> `".. +dda:gsub('\t',' ').."`\n"for i=1,__a:Peek().Char +do local __b=dda:sub(i,i) +if __b=='\t'then bda=bda..' 'else bda=bda..' 'end end +bda=bda.." ^---"break end end;return bda end;local b_a=0;local c_a={}local d_a={'_','a','b','c','d'} +local function _aa(ada)local bda={}bda.Parent=ada +bda.LocalList={}bda.LocalMap={} +function bda:RenameVars() +for cda,dda in pairs(bda.LocalList)do local __b;b_a=0 +repeat b_a=b_a+1;local a_b=b_a +__b=''while a_b>0 do local b_b=a_b%#d_a;a_b=(a_b-b_b)/#d_a +__b=__b..d_a[b_b+1]end until +not c_a[__b]and +not ada:GetLocal(__b)and not bda.LocalMap[__b]dda.Name=__b;bda.LocalMap[__b]=dda end end +function bda:GetLocal(cda)local dda=bda.LocalMap[cda]if dda then return dda end;if bda.Parent then +local __b=bda.Parent:GetLocal(cda)if __b then return __b end end;return nil end +function bda:CreateLocal(cda)local dda={}dda.Scope=bda;dda.Name=cda;dda.CanRename=true;bda.LocalList[# +bda.LocalList+1]=dda +bda.LocalMap[cda]=dda;return dda end;bda.Print=function()return""end;return bda end;local aaa;local baa +local function caa(ada)local bda=_aa(ada)if not __a:ConsumeSymbol('(')then return false, +a_a("`(` expected.")end;local cda={}local dda=false +while not +__a:ConsumeSymbol(')')do +if __a:Is('Ident')then +local c_b=bda:CreateLocal(__a:Get().Data)cda[#cda+1]=c_b;if not __a:ConsumeSymbol(',')then +if +__a:ConsumeSymbol(')')then break else return false,a_a("`)` expected.")end end elseif +__a:ConsumeSymbol('...')then dda=true +if not __a:ConsumeSymbol(')')then return false, +a_a("`...` must be the last argument of a function.")end;break else return false,a_a("Argument name or `...` expected")end end;local __b,a_b=baa(bda)if not __b then return false,a_b end;if not +__a:ConsumeKeyword('end')then +return false,a_a("`end` expected after function body")end;local b_b={} +b_b.AstType='Function'b_b.Scope=bda;b_b.Arguments=cda;b_b.Body=a_b;b_b.VarArg=dda;return true,b_b end +local function daa(ada) +if __a:ConsumeSymbol('(')then local bda,cda=aaa(ada) +if not bda then return false,cda end +if not __a:ConsumeSymbol(')')then return false,a_a("`)` Expected.")end;cda.ParenCount=(cda.ParenCount or 0)+1;return true,cda elseif +__a:Is('Ident')then local bda=__a:Get()local cda=ada:GetLocal(bda.Data)if not cda then +c_a[bda.Data]=true end;local dda={}dda.AstType='VarExpr'dda.Name=bda.Data +dda.Local=cda;return true,dda else return false,a_a("primary expression expected")end end +local function _ba(ada,bda)local cda,dda=daa(ada)if not cda then return false,dda end +while true do +if __a:IsSymbol('.')or +__a:IsSymbol(':')then local __b=__a:Get().Data;if not __a:Is('Ident')then return false, +a_a(" expected.")end;local a_b=__a:Get() +local b_b={}b_b.AstType='MemberExpr'b_b.Base=dda;b_b.Indexer=__b;b_b.Ident=a_b;dda=b_b elseif not +bda and __a:ConsumeSymbol('[')then local __b,a_b=aaa(ada)if not __b then +return false,a_b end;if not __a:ConsumeSymbol(']')then +return false,a_a("`]` expected.")end;local b_b={}b_b.AstType='IndexExpr' +b_b.Base=dda;b_b.Index=a_b;dda=b_b elseif not bda and __a:ConsumeSymbol('(')then local __b={} +while not +__a:ConsumeSymbol(')')do local b_b,c_b=aaa(ada)if not b_b then return false,c_b end +__b[#__b+1]=c_b +if not __a:ConsumeSymbol(',')then if __a:ConsumeSymbol(')')then break else return false, +a_a("`)` Expected.")end end end;local a_b={}a_b.AstType='CallExpr'a_b.Base=dda;a_b.Arguments=__b;dda=a_b elseif not bda and +__a:Is('String')then local __b={}__b.AstType='StringCallExpr'__b.Base=dda +__b.Arguments={__a:Get()}dda=__b elseif not bda and __a:IsSymbol('{')then local __b,a_b=aaa(ada)if not __b then +return false,a_b end;local b_b={}b_b.AstType='TableCallExpr'b_b.Base=dda +b_b.Arguments={a_b}dda=b_b else break end end;return true,dda end +local function aba(ada) +if __a:Is('Number')then local bda={}bda.AstType='NumberExpr'bda.Value=__a:Get()return +true,bda elseif __a:Is('String')then local bda={}bda.AstType='StringExpr' +bda.Value=__a:Get()return true,bda elseif __a:ConsumeKeyword('nil')then local bda={}bda.AstType='NilExpr' +return true,bda elseif __a:IsKeyword('false')or __a:IsKeyword('true')then local bda={} +bda.AstType='BooleanExpr'bda.Value=(__a:Get().Data=='true')return true,bda elseif +__a:ConsumeSymbol('...')then local bda={}bda.AstType='DotsExpr'return true,bda elseif __a:ConsumeSymbol('{')then local bda={} +bda.AstType='ConstructorExpr'bda.EntryList={} +while true do +if __a:IsSymbol('[')then __a:Get()local cda,dda=aaa(ada) +if not cda then return +false,a_a("Key Expression Expected")end +if not __a:ConsumeSymbol(']')then return false,a_a("`]` Expected")end +if not __a:ConsumeSymbol('=')then return false,a_a("`=` Expected")end;local __b,a_b=aaa(ada)if not __b then +return false,a_a("Value Expression Expected")end +bda.EntryList[#bda.EntryList+1]={Type='Key',Key=dda,Value=a_b}elseif __a:Is('Ident')then local cda=__a:Peek(1) +if +cda.Type=='Symbol'and cda.Data=='='then local dda=__a:Get()if not __a:ConsumeSymbol('=')then +return false,a_a("`=` Expected")end;local __b,a_b=aaa(ada)if not __b then return false, +a_a("Value Expression Expected")end +bda.EntryList[ +#bda.EntryList+1]={Type='KeyString',Key=dda.Data,Value=a_b}else local dda,__b=aaa(ada) +if not dda then return false,a_a("Value Exected")end +bda.EntryList[#bda.EntryList+1]={Type='Value',Value=__b}end elseif __a:ConsumeSymbol('}')then break else local cda,dda=aaa(ada) +bda.EntryList[#bda.EntryList+1]={Type='Value',Value=dda}if not cda then return false,a_a("Value Expected")end end +if __a:ConsumeSymbol(';')or __a:ConsumeSymbol(',')then elseif +__a:ConsumeSymbol('}')then break else return false,a_a("`}` or table entry Expected")end end;return true,bda elseif __a:ConsumeKeyword('function')then local bda,cda=caa(ada)if not bda then +return false,cda end;cda.IsLocal=true;return true,cda else return _ba(ada)end end;local bba=lookupify{'-','not','#'}local cba=8 +local dba={['+']={6,6},['-']={6,6},['%']={7,7},['/']={7,7},['*']={7,7},['^']={10,9},['..']={5,4},['==']={3,3},['<']={3,3},['<=']={3,3},['~=']={3,3},['>']={3,3},['>=']={3,3},['and']={2,2},['or']={1,1}} +local function _ca(ada,bda)local cda,dda +if bba[__a:Peek().Data]then local __b=__a:Get().Data +cda,dda=_ca(ada,cba)if not cda then return false,dda end;local a_b={}a_b.AstType='UnopExpr' +a_b.Rhs=dda;a_b.Op=__b;dda=a_b else cda,dda=aba(ada)if not cda then return false,dda end end +while true do local __b=dba[__a:Peek().Data] +if __b and __b[1]>bda then +local a_b=__a:Get().Data;local b_b,c_b=_ca(ada,__b[2])if not b_b then return false,c_b end;local d_b={} +d_b.AstType='BinopExpr'd_b.Lhs=dda;d_b.Op=a_b;d_b.Rhs=c_b;dda=d_b else break end end;return true,dda end;aaa=function(ada)return _ca(ada,0)end +local function aca(ada)local bda=nil +if +__a:ConsumeKeyword('if')then local cda={}cda.AstType='IfStatement'cda.Clauses={} +repeat local dda,__b=aaa(ada)if not dda then +return false,__b end;if not __a:ConsumeKeyword('then')then return false, +a_a("`then` expected.")end +local a_b,b_b=baa(ada)if not a_b then return false,b_b end +cda.Clauses[#cda.Clauses+1]={Condition=__b,Body=b_b}until not __a:ConsumeKeyword('elseif') +if __a:ConsumeKeyword('else')then local dda,__b=baa(ada) +if not dda then return false,__b end;cda.Clauses[#cda.Clauses+1]={Body=__b}end;if not __a:ConsumeKeyword('end')then +return false,a_a("`end` expected.")end;bda=cda elseif __a:ConsumeKeyword('while')then +local cda={}cda.AstType='WhileStatement'local dda,__b=aaa(ada) +if not dda then return false,__b end;if not __a:ConsumeKeyword('do')then +return false,a_a("`do` expected.")end;local a_b,b_b=baa(ada) +if not a_b then return false,b_b end;if not __a:ConsumeKeyword('end')then +return false,a_a("`end` expected.")end;cda.Condition=__b;cda.Body=b_b;bda=cda elseif +__a:ConsumeKeyword('do')then local cda,dda=baa(ada)if not cda then return false,dda end +if not +__a:ConsumeKeyword('end')then return false,a_a("`end` expected.")end;local __b={}__b.AstType='DoStatement'__b.Body=dda;bda=__b elseif +__a:ConsumeKeyword('for')then +if not __a:Is('Ident')then return false,a_a(" expected.")end;local cda=__a:Get() +if __a:ConsumeSymbol('=')then local dda=_aa(ada) +local __b=dda:CreateLocal(cda.Data)local a_b,b_b=aaa(ada)if not a_b then return false,b_b end +if not +__a:ConsumeSymbol(',')then return false,a_a("`,` Expected")end;local c_b,d_b=aaa(ada)if not c_b then return false,d_b end;local _ab,aab +if +__a:ConsumeSymbol(',')then _ab,aab=aaa(ada)if not _ab then return false,aab end end;if not __a:ConsumeKeyword('do')then +return false,a_a("`do` expected")end;local bab,cab=baa(dda) +if not bab then return false,cab end;if not __a:ConsumeKeyword('end')then +return false,a_a("`end` expected")end;local dab={} +dab.AstType='NumericForStatement'dab.Scope=dda;dab.Variable=__b;dab.Start=b_b;dab.End=d_b;dab.Step=aab +dab.Body=cab;bda=dab else local dda=_aa(ada) +local __b={dda:CreateLocal(cda.Data)} +while __a:ConsumeSymbol(',')do if not __a:Is('Ident')then return false, +a_a("for variable expected.")end +__b[#__b+1]=dda:CreateLocal(__a:Get().Data)end;if not __a:ConsumeKeyword('in')then +return false,a_a("`in` expected.")end;local a_b={}local b_b,c_b=aaa(ada)if not b_b then +return false,c_b end;a_b[#a_b+1]=c_b +while __a:ConsumeSymbol(',')do +local bab,cab=aaa(ada)if not bab then return false,cab end;a_b[#a_b+1]=cab end;if not __a:ConsumeKeyword('do')then +return false,a_a("`do` expected.")end;local d_b,_ab=baa(dda) +if not d_b then return false,_ab end;if not __a:ConsumeKeyword('end')then +return false,a_a("`end` expected.")end;local aab={} +aab.AstType='GenericForStatement'aab.Scope=dda;aab.VariableList=__b;aab.Generators=a_b;aab.Body=_ab;bda=aab end elseif __a:ConsumeKeyword('repeat')then local cda,dda=baa(ada) +if not cda then return false,dda end;if not __a:ConsumeKeyword('until')then +return false,a_a("`until` expected.")end;local __b,a_b=aaa(ada) +if not __b then return false,a_b end;local b_b={}b_b.AstType='RepeatStatement'b_b.Condition=a_b;b_b.Body=dda;bda=b_b elseif +__a:ConsumeKeyword('function')then if not __a:Is('Ident')then +return false,a_a("Function name expected")end;local cda,dda=_ba(ada,true)if not cda then +return false,dda end;local __b,a_b=caa(ada)if not __b then return false,a_b end +a_b.IsLocal=false;a_b.Name=dda;bda=a_b elseif __a:ConsumeKeyword('local')then +if __a:Is('Ident')then +local cda={__a:Get().Data}while __a:ConsumeSymbol(',')do if not __a:Is('Ident')then return false, +a_a("local var name expected")end +cda[#cda+1]=__a:Get().Data end;local dda={}if +__a:ConsumeSymbol('=')then +repeat local a_b,b_b=aaa(ada)if not a_b then return false,b_b end +dda[#dda+1]=b_b until not __a:ConsumeSymbol(',')end;for a_b,b_b in +pairs(cda)do cda[a_b]=ada:CreateLocal(b_b)end +local __b={}__b.AstType='LocalStatement'__b.LocalList=cda;__b.InitList=dda;bda=__b elseif +__a:ConsumeKeyword('function')then if not __a:Is('Ident')then +return false,a_a("Function name expected")end;local cda=__a:Get().Data +local dda=ada:CreateLocal(cda)local __b,a_b=caa(ada)if not __b then return false,a_b end;a_b.Name=dda +a_b.IsLocal=true;bda=a_b else +return false,a_a("local var or function def expected")end elseif __a:ConsumeSymbol('::')then if not __a:Is('Ident')then return false, +a_a('Label name expected')end +local cda=__a:Get().Data +if not __a:ConsumeSymbol('::')then return false,a_a("`::` expected")end;local dda={}dda.AstType='LabelStatement'dda.Label=cda;bda=dda elseif +__a:ConsumeKeyword('return')then local cda={} +if not __a:IsKeyword('end')then local __b,a_b=aaa(ada) +if __b then cda[1]=a_b;while +__a:ConsumeSymbol(',')do local b_b,c_b=aaa(ada)if not b_b then return false,c_b end +cda[#cda+1]=c_b end end end;local dda={}dda.AstType='ReturnStatement'dda.Arguments=cda;bda=dda elseif +__a:ConsumeKeyword('break')then local cda={}cda.AstType='BreakStatement'bda=cda elseif __a:IsKeyword('goto')then +if not +__a:Is('Ident')then return false,a_a("Label expected")end;local cda=__a:Get().Data;local dda={}dda.AstType='GotoStatement' +dda.Label=cda;bda=dda else local cda,dda=_ba(ada)if not cda then return false,dda end +if +__a:IsSymbol(',')or __a:IsSymbol('=')then +if(dda.ParenCount or 0)>0 then return false, +a_a("Can not assign to parenthesized expression, is not an lvalue")end;local __b={dda} +while __a:ConsumeSymbol(',')do local _ab,aab=_ba(ada) +if not _ab then return false,aab end;__b[#__b+1]=aab end +if not __a:ConsumeSymbol('=')then return false,a_a("`=` Expected.")end;local a_b={}local b_b,c_b=aaa(ada)if not b_b then return false,c_b end;a_b[1]=c_b;while +__a:ConsumeSymbol(',')do local _ab,aab=aaa(ada)if not _ab then return false,aab end +a_b[#a_b+1]=aab end;local d_b={} +d_b.AstType='AssignmentStatement'd_b.Lhs=__b;d_b.Rhs=a_b;bda=d_b elseif +dda.AstType=='CallExpr'or +dda.AstType=='TableCallExpr'or dda.AstType=='StringCallExpr'then local __b={}__b.AstType='CallStatement'__b.Expression=dda;bda=__b else return false, +a_a("Assignment Statement Expected")end end;bda.HasSemicolon=__a:ConsumeSymbol(';')return true,bda end +local bca=lookupify{'end','else','elseif','until'} +baa=function(ada)local bda={}bda.Scope=_aa(ada)bda.AstType='Statlist'local cda={} +while not +bca[__a:Peek().Data]and not __a:IsEof()do +local dda,__b=aca(bda.Scope)if not dda then return false,__b end;cda[#cda+1]=__b end;bda.Body=cda;return true,bda end;local function cca()local ada=_aa()return baa(ada)end;local dca,_da=cca() +return dca,_da end +local _d=lookupify{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'} +local ad=lookupify{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'} +local bd=lookupify{'0','1','2','3','4','5','6','7','8','9'} +function Format_Mini(cd)local dd,__a;local a_a=0 +local function b_a(d_a,_aa,aaa) +if a_a>150 then a_a=0;return d_a.."\n".._aa end;aaa=aaa or' 'local baa,caa=d_a:sub(-1,-1),_aa:sub(1,1) +if +ad[baa]or _d[baa]or baa=='_'then +if not +(ad[caa]or _d[caa]or caa=='_'or bd[caa])then return d_a.._aa elseif caa=='('then +return d_a..aaa.._aa else return d_a..aaa.._aa end elseif bd[baa]then +if caa=='('then return d_a.._aa else return d_a..aaa.._aa end elseif baa==''then return d_a.._aa else +if caa=='('then return d_a..aaa.._aa else return d_a.._aa end end end +__a=function(d_a)local _aa=string.rep('(',d_a.ParenCount or 0) +if +d_a.AstType=='VarExpr'then if d_a.Local then _aa=_aa..d_a.Local.Name else +_aa=_aa..d_a.Name end elseif d_a.AstType=='NumberExpr'then _aa=_aa.. +d_a.Value.Data elseif d_a.AstType=='StringExpr'then +_aa=_aa..d_a.Value.Data elseif d_a.AstType=='BooleanExpr'then _aa=_aa..tostring(d_a.Value)elseif +d_a.AstType=='NilExpr'then _aa=b_a(_aa,"nil")elseif d_a.AstType=='BinopExpr'then +_aa=b_a(_aa,__a(d_a.Lhs))_aa=b_a(_aa,d_a.Op)_aa=b_a(_aa,__a(d_a.Rhs))elseif d_a.AstType== +'UnopExpr'then _aa=b_a(_aa,d_a.Op) +_aa=b_a(_aa,__a(d_a.Rhs))elseif d_a.AstType=='DotsExpr'then _aa=_aa.."..."elseif d_a.AstType=='CallExpr'then _aa=_aa.. +__a(d_a.Base)_aa=_aa.."("for i=1,#d_a.Arguments do _aa=_aa.. +__a(d_a.Arguments[i]) +if i~=#d_a.Arguments then _aa=_aa..","end end;_aa=_aa..")"elseif d_a.AstType== +'TableCallExpr'then _aa=_aa..__a(d_a.Base)_aa=_aa.. +__a(d_a.Arguments[1])elseif d_a.AstType=='StringCallExpr'then +_aa=_aa..__a(d_a.Base)_aa=_aa..d_a.Arguments[1].Data elseif +d_a.AstType=='IndexExpr'then +_aa=_aa..__a(d_a.Base).."["..__a(d_a.Index).."]"elseif d_a.AstType=='MemberExpr'then _aa=_aa..__a(d_a.Base).. +d_a.Indexer..d_a.Ident.Data elseif +d_a.AstType=='Function'then d_a.Scope:RenameVars() +_aa=_aa.."function(" +if#d_a.Arguments>0 then for i=1,#d_a.Arguments do +_aa=_aa..d_a.Arguments[i].Name +if i~=#d_a.Arguments then _aa=_aa..","elseif d_a.VarArg then _aa=_aa..",..."end end elseif +d_a.VarArg then _aa=_aa.."..."end;_aa=_aa..")"_aa=b_a(_aa,dd(d_a.Body)) +_aa=b_a(_aa,"end")elseif d_a.AstType=='ConstructorExpr'then _aa=_aa.."{" +for i=1,#d_a.EntryList do +local aaa=d_a.EntryList[i] +if aaa.Type=='Key'then _aa=_aa.."[".. +__a(aaa.Key).."]="..__a(aaa.Value)elseif aaa.Type== +'Value'then _aa=_aa..__a(aaa.Value)elseif aaa.Type=='KeyString'then +_aa=_aa.. +aaa.Key.."="..__a(aaa.Value)end;if i~=#d_a.EntryList then _aa=_aa..","end end;_aa=_aa.."}"end +_aa=_aa..string.rep(')',d_a.ParenCount or 0)a_a=a_a+#_aa;return _aa end +local c_a=function(d_a)local _aa='' +if d_a.AstType=='AssignmentStatement'then +for i=1,#d_a.Lhs do +_aa=_aa..__a(d_a.Lhs[i])if i~=#d_a.Lhs then _aa=_aa..","end end;if#d_a.Rhs>0 then _aa=_aa.."=" +for i=1,#d_a.Rhs do +_aa=_aa..__a(d_a.Rhs[i])if i~=#d_a.Rhs then _aa=_aa..","end end end elseif +d_a.AstType=='CallStatement'then _aa=__a(d_a.Expression)elseif d_a.AstType=='LocalStatement'then +_aa=_aa.."local " +for i=1,#d_a.LocalList do _aa=_aa..d_a.LocalList[i].Name;if i~=# +d_a.LocalList then _aa=_aa..","end end +if#d_a.InitList>0 then _aa=_aa.."="for i=1,#d_a.InitList do _aa=_aa.. +__a(d_a.InitList[i]) +if i~=#d_a.InitList then _aa=_aa..","end end end elseif d_a.AstType=='IfStatement'then +_aa=b_a("if",__a(d_a.Clauses[1].Condition))_aa=b_a(_aa,"then") +_aa=b_a(_aa,dd(d_a.Clauses[1].Body)) +for i=2,#d_a.Clauses do local aaa=d_a.Clauses[i] +if aaa.Condition then +_aa=b_a(_aa,"elseif")_aa=b_a(_aa,__a(aaa.Condition)) +_aa=b_a(_aa,"then")else _aa=b_a(_aa,"else")end;_aa=b_a(_aa,dd(aaa.Body))end;_aa=b_a(_aa,"end")elseif d_a.AstType=='WhileStatement'then +_aa=b_a("while",__a(d_a.Condition))_aa=b_a(_aa,"do")_aa=b_a(_aa,dd(d_a.Body)) +_aa=b_a(_aa,"end")elseif d_a.AstType=='DoStatement'then _aa=b_a(_aa,"do") +_aa=b_a(_aa,dd(d_a.Body))_aa=b_a(_aa,"end")elseif d_a.AstType=='ReturnStatement'then _aa="return" +for i=1,#d_a.Arguments +do _aa=b_a(_aa,__a(d_a.Arguments[i]))if i~= +#d_a.Arguments then _aa=_aa..","end end elseif d_a.AstType=='BreakStatement'then _aa="break"elseif d_a.AstType=='RepeatStatement'then +_aa="repeat"_aa=b_a(_aa,dd(d_a.Body))_aa=b_a(_aa,"until") +_aa=b_a(_aa,__a(d_a.Condition))elseif d_a.AstType=='Function'then d_a.Scope:RenameVars()if d_a.IsLocal then +_aa="local"end;_aa=b_a(_aa,"function ")if d_a.IsLocal then +_aa=_aa..d_a.Name.Name else _aa=_aa..__a(d_a.Name)end;_aa= +_aa.."(" +if#d_a.Arguments>0 then +for i=1,#d_a.Arguments do _aa=_aa.. +d_a.Arguments[i].Name;if i~=#d_a.Arguments then _aa=_aa..","elseif d_a.VarArg then +_aa=_aa..",..."end end elseif d_a.VarArg then _aa=_aa.."..."end;_aa=_aa..")"_aa=b_a(_aa,dd(d_a.Body)) +_aa=b_a(_aa,"end")elseif d_a.AstType=='GenericForStatement'then d_a.Scope:RenameVars() +_aa="for " +for i=1,#d_a.VariableList do +_aa=_aa..d_a.VariableList[i].Name;if i~=#d_a.VariableList then _aa=_aa..","end end;_aa=_aa.." in" +for i=1,#d_a.Generators do +_aa=b_a(_aa,__a(d_a.Generators[i]))if i~=#d_a.Generators then _aa=b_a(_aa,',')end end;_aa=b_a(_aa,"do")_aa=b_a(_aa,dd(d_a.Body)) +_aa=b_a(_aa,"end")elseif d_a.AstType=='NumericForStatement'then _aa="for "_aa=_aa.. +d_a.Variable.Name.."="_aa=_aa.. +__a(d_a.Start)..","..__a(d_a.End)if d_a.Step then +_aa=_aa..","..__a(d_a.Step)end;_aa=b_a(_aa,"do") +_aa=b_a(_aa,dd(d_a.Body))_aa=b_a(_aa,"end")end;a_a=a_a+#_aa;return _aa end +dd=function(d_a)local _aa=''d_a.Scope:RenameVars()for aaa,baa in pairs(d_a.Body)do +_aa=b_a(_aa,c_a(baa),';')end;return _aa end;cd.Scope:RenameVars()return dd(cd)end +return function(cd)local dd,__a=ParseLua(cd)if not dd then return false,__a end +return true,Format_Mini(__a)end \ No newline at end of file diff --git a/release/project.lua b/release/project.lua deleted file mode 100644 index 5413bc1..0000000 --- a/release/project.lua +++ /dev/null @@ -1,1315 +0,0 @@ -local project = {} -local Render = {} -Render.__index = Render -local colorChars = require("libraries/colorHex") -local log = require("log") - -function Render.new(terminal) - local self = setmetatable({}, Render) - self.terminal = terminal - self.width, self.height = terminal.getSize() - - self.buffer = { - text = {}, - fg = {}, - bg = {}, - changed = {} - } - - for y=1, self.height do - self.buffer.text[y] = string.rep(" ", self.width) - self.buffer.fg[y] = string.rep("0", self.width) - self.buffer.bg[y] = string.rep("f", self.width) - self.buffer.changed[y] = false - end - - return self -end - -function Render:blit(x, y, text, fg, bg) - if y < 1 or y > self.height then return self end - if(#text ~= #fg or #text ~= #bg)then - error("Text, fg, and bg must be the same length") - end - - self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text) - self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg) - self.buffer.bg[y] = self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg) - self.buffer.changed[y] = true - - return self -end - -function Render:multiBlit(x, y, width, height, text, fg, bg) - if y < 1 or y > self.height then return self end - if(#text ~= #fg or #text ~= #bg)then - error("Text, fg, and bg must be the same length") - end - - text = text:rep(width) - fg = fg:rep(width) - bg = bg:rep(width) - - for dy=0, height-1 do - local cy = y + dy - if cy >= 1 and cy <= self.height then - self.buffer.text[cy] = self.buffer.text[cy]:sub(1,x-1) .. text .. self.buffer.text[cy]:sub(x+#text) - self.buffer.fg[cy] = self.buffer.fg[cy]:sub(1,x-1) .. fg .. self.buffer.fg[cy]:sub(x+#fg) - self.buffer.bg[cy] = self.buffer.bg[cy]:sub(1,x-1) .. bg .. self.buffer.bg[cy]:sub(x+#bg) - self.buffer.changed[cy] = true - end - end - - return self -end - -function Render:textFg(x, y, text, fg) - if y < 1 or y > self.height then return self end - fg = colorChars[fg] or "0" - - self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text) - self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg:rep(#text) .. self.buffer.fg[y]:sub(x+#text) - self.buffer.changed[y] = true - - return self -end - -function Render:text(x, y, text) - if y < 1 or y > self.height then return self end - - self.buffer.text[y] = self.buffer.text[y]:sub(1,x-1) .. text .. self.buffer.text[y]:sub(x+#text) - self.buffer.changed[y] = true - - return self -end - -function Render:fg(x, y, fg) - if y < 1 or y > self.height then return self end - - self.buffer.fg[y] = self.buffer.fg[y]:sub(1,x-1) .. fg .. self.buffer.fg[y]:sub(x+#fg) - self.buffer.changed[y] = true - - return self -end - -function Render:bg(x, y, bg) - if y < 1 or y > self.height then return self end - - self.buffer.bg[y] = self.buffer.bg[y]:sub(1,x-1) .. bg .. self.buffer.bg[y]:sub(x+#bg) - self.buffer.changed[y] = true - - return self -end - -function Render:clear(bg) - local bgChar = colorChars[bg] or "f" - for y=1, self.height do - self.buffer.text[y] = string.rep(" ", self.width) - self.buffer.fg[y] = string.rep("0", self.width) - self.buffer.bg[y] = string.rep(bgChar, self.width) - self.buffer.changed[y] = true - end - return self -end - -function Render:render() - for y=1, self.height do - if self.buffer.changed[y] then - self.terminal.setCursorPos(1, y) - self.terminal.blit( - self.buffer.text[y], - self.buffer.fg[y], - self.buffer.bg[y] - ) - self.buffer.changed[y] = false - end - end - return self -end - -function Render:clearArea(x, y, width, height, bg) - local bgChar = colorChars[bg] or "f" - for dy=0, height-1 do - local cy = y + dy - if cy >= 1 and cy <= self.height then - local text = string.rep(" ", width) - local color = string.rep(bgChar, width) - self:blit(x, cy, text, "0", bgChar) - end - end - return self -end - -function Render:getSize() - return self.width, self.height -end - -return Renderlocal args = table.pack(...) -local dir = fs.getDir(args[2] or "basalt") -if(dir==nil)then - error("Unable to find directory "..args[2].." please report this bug to our discord.") -end - -local log = require("log") - -local ElementManager = {} -ElementManager._elements = {} -ElementManager._plugins = {} -local elementsDirectory = fs.combine(dir, "elements") -local pluginsDirectory = fs.combine(dir, "plugins") - -log.info("Loading elements from "..elementsDirectory) -if fs.exists(elementsDirectory) then - for _, file in ipairs(fs.list(elementsDirectory)) do - local name = file:match("(.+).lua") - if name then - log.debug("Found element: "..name) - ElementManager._elements[name] = { - class = nil, - plugins = {}, - loaded = false - } - end - end -end - -function ElementManager.extendMethod(element, methodName, newMethod, originalMethod) - if not originalMethod then - element[methodName] = newMethod - return - end - element[methodName] = function(self, ...) - if newMethod.before then - newMethod.before(self, ...) - end - - local results - if newMethod.override then - results = {newMethod.override(self, originalMethod, ...)} - else - results = {originalMethod(self, ...)} - end - - if newMethod.after then - newMethod.after(self, ...) - end - - return table.unpack(results) - end -end - -function ElementManager.loadPlugin(name) - local plugin = require("plugins/"..name) - - -- Apply plugin to each targeted element - for elementName, pluginData in pairs(plugin) do - local element = ElementManager._elements[elementName] - if element then - -- Register properties - if pluginData.properties then - element.class.initialize(elementName.."Plugin") - for propName, config in pairs(pluginData.properties) do - element.class.registerProperty(propName, config) - end - end - - -- Register/extend methods - if pluginData.methods then - for methodName, methodData in pairs(pluginData.methods) do - ElementManager.extendMethod( - element.class, - methodName, - methodData, - element.class[methodName] - ) - end - end - end - end -end - -function ElementManager.loadElement(name) - if not ElementManager._elements[name].loaded then - local element = require("elements/"..name) - ElementManager._elements[name] = { - class = element, - plugins = element.plugins, - loaded = true - } - log.debug("Loaded element: "..name) - - -- Load element's required plugins - if element.requires then - for pluginName, _ in pairs(element.requires) do - --ElementManager.loadPlugin(pluginName) - end - end - end -end - -function ElementManager.registerPlugin(name, plugin) - if not plugin.provides then - error("Plugin must specify what it provides") - end - ElementManager._plugins[name] = plugin -end - -function ElementManager.getElement(name) - if not ElementManager._elements[name].loaded then - ElementManager.loadElement(name) - end - return ElementManager._elements[name].class -end - -function ElementManager.getElementList() - return ElementManager._elements -end - -function ElementManager.generateId() - return string.format('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff), - math.random(0, 0x0fff) + 0x4000, math.random(0, 0x3fff) + 0x8000, - math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff)) -end - -return ElementManager -local args = {...} - -local basaltPath = args[1] or "basalt" - -local defaultPath = package.path -local format = "path;/path/?.lua;/path/?/init.lua;" - -local main = format:gsub("path", basaltPath) -package.path = main.."rom/?" - -local function errorHandler(err) - local errorManager = require("errorManager") - errorManager.header = "Basalt Loading Error" - errorManager.error(err) -end - --- Use xpcall with error handler -local ok, result = pcall(require, "main") - -if not ok then - errorHandler(result) -else - return result -endlocal Container = require("elements/Container") -local Render = require("render") - -local BaseFrame = setmetatable({}, Container) -BaseFrame.__index = BaseFrame - ----@diagnostic disable-next-line: duplicate-set-field -function BaseFrame.new(id, basalt) - local self = setmetatable({}, BaseFrame):__init() - self:init(id, basalt) - self.terminal = term.current() -- change to :setTerm later!! - self._render = Render.new(self.terminal) - self._renderUpdate = true - local width, height = self.terminal.getSize() - self.set("width", width) - self.set("height", height) - self.set("background", colors.red) - self.set("type", "BaseFrame") - return self -end - -function BaseFrame:multiBlit(x, y, width, height, text, fg, bg) - self._render:multiBlit(x, y, width, height, text, fg, bg) -end - -function BaseFrame:textFg(x, y, text, fg) - self._render:textFg(x, y, text, fg) -end - ----@diagnostic disable-next-line: duplicate-set-field -function BaseFrame:render() - if(self._renderUpdate) then - Container.render(self) - self._render:render() - self._renderUpdate = false - end -end - -return BaseFramelocal VisualElement = require("elements/VisualElement") -local getCenteredPosition = require("libraries/utils").getCenteredPosition - -local Button = setmetatable({}, VisualElement) -Button.__index = Button - -Button.defineProperty(Button, "text", {default = "Button", type = "string"}) -Button.listenTo(Button, "mouse_click") - ----@diagnostic disable-next-line: duplicate-set-field -function Button.new(id, basalt) - local self = setmetatable({}, Button):__init() - self:init(id, basalt) - self.set("type", "Button") - self.set("width", 10) - self.set("height", 3) - self.set("z", 5) - return self -end - ----@diagnostic disable-next-line: duplicate-set-field -function Button:render() - VisualElement.render(self) - local text = self.get("text") - local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height")) - self:textFg(xO, yO, text, self.get("foreground")) -end - -return Buttonlocal BaseElement = require("elements/BaseElement") -local VisualElement = setmetatable({}, BaseElement) -VisualElement.__index = VisualElement -local tHex = require("libraries/colorHex") - -BaseElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value) - self.basalt.LOGGER.debug("Setting z to " .. value) - if self.parent then - self.parent:sortChildren() - end - return value -end}) -BaseElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "background", {default = colors.black, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "foreground", {default = colors.white, type = "number", canTriggerRender = true}) -BaseElement.defineProperty(VisualElement, "clicked", {default = false, type = "boolean"}) - ----@diagnostic disable-next-line: duplicate-set-field -function VisualElement.new(id, basalt) - local self = setmetatable({}, VisualElement):__init() - self:init(id, basalt) - self.set("type", "VisualElement") - return self -end - -function VisualElement:multiBlit(x, y, width, height, text, fg, bg) - x = x + self.get("x") - 1 - y = y + self.get("y") - 1 - self.parent:multiBlit(x, y, width, height, text, fg, bg) -end - -function VisualElement:textFg(x, y, text, fg) - x = x + self.get("x") - 1 - y = y + self.get("y") - 1 - self.parent:textFg(x, y, text, fg) -end - -function VisualElement:isInBounds(x, y) - local xPos, yPos = self.get("x"), self.get("y") - local width, height = self.get("width"), self.get("height") - - return x >= xPos and x <= xPos + width - 1 and - y >= yPos and y <= yPos + height - 1 -end - -function VisualElement:mouse_click(button, x, y) - if self:isInBounds(x, y) then - self.set("clicked", true) - self:fireEvent("mouse_click", button, x, y) - return true - end -end - -function VisualElement:mouse_up(button, x, y) - if self:isInBounds(x, y) then - self.set("clicked", false) - self:fireEvent("mouse_up", button, x, y) - return true - end - self:fireEvent("mouse_release", button, x, y) -end - -function VisualElement:mouse_release() - self.set("clicked", false) -end - -function VisualElement:handleEvent(event, ...) - if(self[event])then - return self[event](self, ...) - end -end - ---- Returns the absolute position of the element or the given coordinates. ----@param x? number -- x position ----@param y? number -- y position -function VisualElement:getAbsolutePosition(x, y) - if (x == nil) or (y == nil) then - x, y = self.get("x"), self.get("y") - end - - local parent = self.parent - while parent do - local px, py = parent.get("x"), parent.get("y") - x = x + px - 1 - y = y + py - 1 - parent = parent.parent - end - - return x, y -end - ---- Returns the relative position of the element or the given coordinates. ----@param x? number -- x position ----@param y? number -- y position ----@return number, number -function VisualElement:getRelativePosition(x, y) - if (x == nil) or (y == nil) then - x, y = self.get("x"), self.get("y") - end - - local parentX, parentY = 1, 1 - if self.parent then - parentX, parentY = self.parent:getRelativePosition() - end - - local elementX = self.get("x") - local elementY = self.get("y") - - return x - (elementX - 1) - (parentX - 1), - y - (elementY - 1) - (parentY - 1) -end - - ----@diagnostic disable-next-line: duplicate-set-field -function VisualElement:render() - local width, height = self.get("width"), self.get("height") - self:multiBlit(1, 1, width, height, " ", tHex[self.get("foreground")], tHex[self.get("background")]) -end - -return VisualElementlocal PropertySystem = require("propertySystem") -- muss geƤndert werden. - -local BaseElement = setmetatable({}, PropertySystem) -BaseElement.__index = BaseElement -BaseElement._events = {} - -BaseElement.defineProperty(BaseElement, "type", {default = "BaseElement", type = "string"}) -BaseElement.defineProperty(BaseElement, "eventCallbacks", {default = {}, type = "table"}) - -function BaseElement.new(id, basalt) - local self = setmetatable({}, BaseElement):__init() - self:init(id, basalt) - self.set("type", "BaseElement") - return self -end - -function BaseElement:init(id, basalt) - self.id = id - self.basalt = basalt - self._registeredEvents = {} - if BaseElement._events then - for event in pairs(BaseElement._events) do - self._registeredEvents[event] = true - local handlerName = "on" .. event:gsub("_(%l)", function(c) - return c:upper() - end):gsub("^%l", string.upper) - self[handlerName] = function(self, ...) - self:registerCallback(event, ...) - end - end - end - return self -end - -function BaseElement.listenTo(class, eventName) - if not class._events then - class._events = {} - end - class._events[eventName] = true -end - -function BaseElement:listenEvent(eventName, enable) - enable = enable ~= false - if enable ~= (self._registeredEvents[eventName] or false) then - if enable then - self._registeredEvents[eventName] = true - if self.parent then - self.parent:registerChildEvent(self, eventName) - end - else - self._registeredEvents[eventName] = nil - if self.parent then - self.parent:unregisterChildEvent(self, eventName) - end - end - end - return self -end - -function BaseElement:registerCallback(event, callback) - if not self._registeredEvents[event] then - self:listenEvent(event, true) - end - - if not self._values.eventCallbacks[event] then - self._values.eventCallbacks[event] = {} - end - - table.insert(self._values.eventCallbacks[event], callback) - return self -end - -function BaseElement:fireEvent(event, ...) - if self._values.eventCallbacks[event] then - for _, callback in ipairs(self._values.eventCallbacks[event]) do - local result = callback(self, ...) - return result - end - end - return self -end - -function BaseElement:updateRender() - if(self.parent) then - self.parent:updateRender() - else - self._renderUpdate = true - end -end - -return BaseElementlocal VisualElement = require("elements/VisualElement") -local elementManager = require("elementManager") -local expect = require("libraries/expect") - -local max = math.max - -local Container = setmetatable({}, VisualElement) -Container.__index = Container - -Container.defineProperty(Container, "children", {default = {}, type = "table"}) -Container.defineProperty(Container, "childrenEvents", {default = {}, type = "table"}) -Container.defineProperty(Container, "eventListenerCount", {default = {}, type = "table"}) - -for k, _ in pairs(elementManager:getElementList()) do - local capitalizedName = k:sub(1,1):upper() .. k:sub(2) - --if not capitalizedName == "BaseFrame" then - Container["add"..capitalizedName] = function(self, ...) - expect(1, self, "table") - local element = self.basalt.create(k, ...) - self.basalt.LOGGER.debug(capitalizedName.." created with ID: " .. element.id) - self:addChild(element) - return element - end - --end -end - ----@diagnostic disable-next-line: duplicate-set-field -function Container.new(id, basalt) - local self = setmetatable({}, Container):__init() - self:init(id, basalt) - self.set("type", "Container") - return self -end - -function Container:addChild(child) - if child == self then - error("Cannot add container to itself") - end - - local childZ = child.get("z") - local pos = 1 - for i, existing in ipairs(self._values.children) do - if existing.get("z") > childZ then - break - end - pos = i + 1 - end - - table.insert(self._values.children, pos, child) - child.parent = self - self:registerChildrenEvents(child) - return self -end - -function Container:sortChildren() - table.sort(self._values.children, function(a, b) - return a.get("z") < b.get("z") - end) -end - -function Container:sortChildrenEvents(eventName) - if self._values.childrenEvents[eventName] then - table.sort(self._values.childrenEvents[eventName], function(a, b) - return a.get("z") > b.get("z") - end) - end -end - -function Container:registerChildrenEvents(child) - for event in pairs(child._registeredEvents) do - self:registerChildEvent(child, event) - end -end - -function Container:registerChildEvent(child, eventName) - if not self._values.childrenEvents[eventName] then - self._values.childrenEvents[eventName] = {} - self._values.eventListenerCount[eventName] = 0 - - if self.parent then - self.parent:registerChildEvent(self, eventName) - end - end - - for _, registeredChild in ipairs(self._values.childrenEvents[eventName]) do - if registeredChild == child then - return - end - end - - local childZ = child.get("z") - local pos = 1 - for i, existing in ipairs(self._values.childrenEvents[eventName]) do - if existing.get("z") < childZ then - break - end - pos = i + 1 - end - - table.insert(self._values.childrenEvents[eventName], pos, child) - self._values.eventListenerCount[eventName] = self._values.eventListenerCount[eventName] + 1 -end - -function Container:removeChildrenEvents(child) - for event in pairs(child._registeredEvents) do - self:unregisterChildEvent(child, event) - end -end - -function Container:unregisterChildEvent(child, eventName) - if self._values.childrenEvents[eventName] then - for i, listener in ipairs(self._values.childrenEvents[eventName]) do - if listener == child then - table.remove(self._values.childrenEvents[eventName], i) - self._values.eventListenerCount[eventName] = self._values.eventListenerCount[eventName] - 1 - - if self._values.eventListenerCount[eventName] <= 0 then - self._values.childrenEvents[eventName] = nil - self._values.eventListenerCount[eventName] = nil - - if self.parent then - self.parent:unregisterChildEvent(self, eventName) - end - end - break - end - end - end -end - -function Container:removeChild(child) - for i,v in ipairs(self.children) do - if v == child then - table.remove(self._values.children, i) - child.parent = nil - break - end - end - return self -end - -function Container:handleEvent(event, ...) - if(VisualElement.handleEvent(self, event, ...))then - local args = {...} - if event:find("mouse_") then - local button, absX, absY = ... - local relX, relY = self:getRelativePosition(absX, absY) - args = {button, relX, relY} - end - if self._values.childrenEvents[event] then - for _, child in ipairs(self._values.childrenEvents[event]) do - if(child:handleEvent(event, table.unpack(args)))then - return true - end - end - end - end -end - ---[[function Container:mouse_click(button, x, y) - if VisualElement.mouse_click(self, button, x, y) then - if self._values.childrenEvents.mouse_click then - for _, child in ipairs(self._values.childrenEvents.mouse_click) do - if child:mouse_click(button, x, y) then - return true - end - end - end - end -end]] - -function Container:multiBlit(x, y, width, height, text, fg, bg) - local w, h = self.get("width"), self.get("height") - - width = x < 1 and math.min(width + x - 1, w) or math.min(width, math.max(0, w - x + 1)) - height = y < 1 and math.min(height + y - 1, h) or math.min(height, math.max(0, h - y + 1)) - - if width <= 0 or height <= 0 then return end - - VisualElement.multiBlit(self, math.max(1, x), math.max(1, y), width, height, text, fg, bg) -end - -function Container:textFg(x, y, text, fg) - local w, h = self.get("width"), self.get("height") - - if y < 1 or y > h then return end - - local textStart = x < 1 and (2 - x) or 1 - local textLen = math.min(#text - textStart + 1, w - math.max(1, x) + 1) - - if textLen <= 0 then return end - - VisualElement.textFg(self, math.max(1, x), math.max(1, y), text:sub(textStart, textStart + textLen - 1), fg) -end - ----@diagnostic disable-next-line: duplicate-set-field -function Container:render() - VisualElement.render(self) - for _, child in ipairs(self._values.children) do - if child == self then - self.basalt.LOGGER.error("CIRCULAR REFERENCE DETECTED!") - return - end - child:render() - end -end - -return Containerlocal Container = require("elements/Container") - -local Frame = setmetatable({}, Container) -Frame.__index = Frame - ----@diagnostic disable-next-line: duplicate-set-field -function Frame.new(id, basalt) - local self = setmetatable({}, Frame):__init() - self:init(id, basalt) - self.set("width", 12) - self.set("height", 6) - self.set("background", colors.blue) - self.set("type", "Frame") - self.set("z", 10) - return self -end - -return Framelocal LOGGER = require("log") - -local errorHandler = { - tracebackEnabled = true, - header = "Basalt Error" -} - -local function coloredPrint(message, color) - term.setTextColor(color) - print(message) - term.setTextColor(colors.white) -end - -function errorHandler.error(errMsg) - term.setBackgroundColor(colors.black) - - term.clear() - term.setCursorPos(1, 1) - - coloredPrint(errorHandler.header..":", colors.red) - print() - - local level = 2 - local topInfo - while true do - local info = debug.getinfo(level, "Sl") - if not info then break end - topInfo = info - level = level + 1 - end - local info = topInfo or debug.getinfo(2, "Sl") - local fileName = info.source:sub(2) - local lineNumber = info.currentline - local errorMessage = errMsg - - if(errorHandler.tracebackEnabled)then - local stackTrace = debug.traceback() - if stackTrace then - --coloredPrint("Stack traceback:", colors.gray) - for line in stackTrace:gmatch("[^\r\n]+") do - local fileNameInTraceback, lineNumberInTraceback = line:match("([^:]+):(%d+):") - if fileNameInTraceback and lineNumberInTraceback then - term.setTextColor(colors.lightGray) - term.write(fileNameInTraceback) - term.setTextColor(colors.gray) - term.write(":") - term.setTextColor(colors.lightBlue) - term.write(lineNumberInTraceback) - term.setTextColor(colors.gray) - line = line:gsub(fileNameInTraceback .. ":" .. lineNumberInTraceback, "") - end - coloredPrint(line, colors.gray) - end - print() - end - end - - if fileName and lineNumber then - term.setTextColor(colors.red) - term.write("Error in ") - term.setTextColor(colors.white) - term.write(fileName) - term.setTextColor(colors.red) - term.write(":") - term.setTextColor(colors.lightBlue) - term.write(lineNumber) - term.setTextColor(colors.red) - term.write(": ") - - - if errorMessage then - errorMessage = string.gsub(errorMessage, "stack traceback:.*", "") - if errorMessage ~= "" then - coloredPrint(errorMessage, colors.red) - else - coloredPrint("Error message not available", colors.gray) - end - else - coloredPrint("Error message not available", colors.gray) - end - - local file = fs.open(fileName, "r") - if file then - local lineContent = "" - local currentLineNumber = 1 - repeat - lineContent = file.readLine() - if currentLineNumber == tonumber(lineNumber) then - coloredPrint("\149Line " .. lineNumber, colors.cyan) - coloredPrint(lineContent, colors.lightGray) - break - end - currentLineNumber = currentLineNumber + 1 - until not lineContent - file.close() - end - end - - term.setBackgroundColor(colors.black) - LOGGER.error(errMsg) - error() -end - -return errorHandlerlocal floor, len = math.floor, string.len - -local utils = {} - -function utils.getCenteredPosition(text, totalWidth, totalHeight) - local textLength = len(text) - - local x = floor((totalWidth - textLength+1) / 2 + 0.5) - local y = floor(totalHeight / 2 + 0.5) - - return x, y -end - -function utils.deepCopy(obj) - if type(obj) ~= "table" then - return obj - end - - local copy = {} - for k, v in pairs(obj) do - copy[utils.deepCopy(k)] = utils.deepCopy(v) - end - - return copy -end - -return utilslocal errorManager = require("errorManager") - --- Simple type checking without stack traces -local function expect(position, value, expectedType) - local valueType = type(value) - - if expectedType == "element" then - if valueType == "table" and value.get("type") ~= nil then - return true - end - end - - if expectedType == "color" then - if valueType == "number" and value >= 1 and value <= 32768 then - return true - end - if valueType == "string" and colors[value] then - return true - end - end - - if valueType ~= expectedType then - errorManager.header = "Basalt Type Error" - errorManager.error(string.format( - "Bad argument #%d: expected %s, got %s", - position, - expectedType, - valueType - )) - end - - return true -end - -return expectlocal colorHex = {} - -for i = 0, 15 do - colorHex[2^i] = ("%x"):format(i) - colorHex[("%x"):format(i)] = 2^i -end - -return colorHexlocal Log = {} -Log._logs = {} -Log._enabled = true -Log._logToFile = true -Log._logFile = "basalt.log" - -fs.delete(Log._logFile) - --- Log levels -Log.LEVEL = { - DEBUG = 1, - INFO = 2, - WARN = 3, - ERROR = 4 -} - -local levelMessages = { - [Log.LEVEL.DEBUG] = "Debug", - [Log.LEVEL.INFO] = "Info", - [Log.LEVEL.WARN] = "Warn", - [Log.LEVEL.ERROR] = "Error" -} - -local levelColors = { - [Log.LEVEL.DEBUG] = colors.lightGray, - [Log.LEVEL.INFO] = colors.white, - [Log.LEVEL.WARN] = colors.yellow, - [Log.LEVEL.ERROR] = colors.red -} - -function Log.setLogToFile(enable) - Log._logToFile = enable -end - -function Log.setEnabled(enable) - Log._enabled = enable -end - -local function writeToFile(message) - if Log._logToFile then - local file = io.open(Log._logFile, "a") - if file then - file:write(message.."\n") - file:close() - end - end -end - -local function log(level, ...) - if not Log._enabled then return end - - local timeStr = os.date("%H:%M:%S") - - -- Get caller info (skip log function and Log.debug/info/etc functions) - local info = debug.getinfo(3, "Sl") - local source = info.source:match("@?(.*)") - local line = info.currentline - local levelStr = string.format("[%s:%d]", source:match("([^/\\]+)%.lua$"), line) - - local levelMsg = "[" .. levelMessages[level] .. "]" - - local message = "" - for i, v in ipairs(table.pack(...)) do - if i > 1 then - message = message .. " " - end - message = message .. tostring(v) - end - - local fullMessage = string.format("%s %s%s %s", timeStr, levelStr, levelMsg, message) - - -- File output - writeToFile(fullMessage) - -- Store in memory - table.insert(Log._logs, { - time = timeStr, - level = level, - message = message - }) -end - -function Log.debug(...) log(Log.LEVEL.DEBUG, ...) end -function Log.info(...) log(Log.LEVEL.INFO, ...) end -function Log.warn(...) log(Log.LEVEL.WARN, ...) end -function Log.error(...) log(Log.LEVEL.ERROR, ...) end - -Log.info("Logger initialized") - -return Loglocal elementManager = require("elementManager") -local expect = require("libraries/expect") -local errorManager = require("errorManager") - -local basalt = {} -basalt.traceback = true -basalt._events = {} -basalt._schedule = {} -basalt._plugins = {} -basalt.LOGGER = require("log") - -local mainFrame = nil -local updaterActive = false - -function basalt.create(type, id) - if(id==nil)then id = elementManager.generateId() end - local element = elementManager.getElement(type).new(id, basalt) - local ok, result = pcall(require, "main") - if not ok then - errorManager(false, result) - end - return element -end - -function basalt.createFrame() - local frame = basalt.create("BaseFrame") - mainFrame = frame - return frame -end - -function basalt.getElementManager() - return elementManager -end - -function basalt.getMainFrame() - if(mainFrame == nil)then - mainFrame = basalt.createFrame() - end - return mainFrame -end - -function basalt.setActiveFrame(frame) - mainFrame = frame - return false -end - -function basalt.scheduleUpdate(func) - table.insert(basalt._schedule, func) - return #basalt._schedule -end - -function basalt.removeSchedule(id) - basalt._schedule[id] = nil -end - -local function updateEvent(event, ...) - if(event=="terminate")then basalt.stop() end - - if event:find("mouse") then - if mainFrame then - mainFrame:handleEvent(event, ...) - end - end - - if event:find("key") then - if mainFrame then - mainFrame:handleEvent(event, ...) - end - end - - if basalt._events[event] then - for _, callback in ipairs(basalt._events[event]) do - callback(...) - end - end -end - -local function renderFrames() - if(mainFrame)then - mainFrame:render() - end -end - -function basalt.update() - for k,v in pairs(basalt._schedule) do - if type(v)=="function" then - v() - end - end -end - -function basalt.stop() - term.clear() - term.setCursorPos(1,1) - updaterActive = false -end - -function basalt.run(isActive) - updaterActive = isActive - if(isActive==nil)then updaterActive = true end - local function f() - renderFrames() - while updaterActive do - updateEvent(os.pullEventRaw()) - end - end - while updaterActive do - local ok, err = pcall(f) - if not(ok)then - errorManager.header = "Basalt Runtime Error" - errorManager.error(err) - end - end -end -basalt.autoUpdate = basalt.run - -return basaltlocal deepCopy = require("libraries/utils").deepCopy -local expect = require("libraries/expect") - -local PropertySystem = {} -PropertySystem.__index = PropertySystem - -PropertySystem._properties = {} - -function PropertySystem.defineProperty(class, name, config) - if not rawget(class, '_properties') then - class._properties = {} - end - - class._properties[name] = { - type = config.type, - default = config.default, - canTriggerRender = config.canTriggerRender, - getter = config.getter, - setter = config.setter, - } - - local capitalizedName = name:sub(1,1):upper() .. name:sub(2) - - class["get" .. capitalizedName] = function(self) - expect(1, self, "element") - local value = self._values[name] - return config.getter and config.getter(value) or value - end - - class["set" .. capitalizedName] = function(self, value) - expect(1, self, "element") - expect(2, value, config.type) - if config.setter then - value = config.setter(self, value) - end - - self:_updateProperty(name, value) - return self - end -end - -function PropertySystem:__init() - self._values = {} - self._observers = {} - - self.set = function(name, value) - local oldValue = self._values[name] - self._values[name] = value - if(self._properties[name].setter) then - value = self._properties[name].setter(self, value) - end - if oldValue ~= value and self._observers[name] then - for _, callback in ipairs(self._observers[name]) do - callback(self, value, oldValue) - end - end - end - - self.get = function(name) - return self._values[name] - end - - local properties = {} - local currentClass = getmetatable(self).__index - - while currentClass do - if rawget(currentClass, '_properties') then - for name, config in pairs(currentClass._properties) do - if not properties[name] then - properties[name] = config - end - end - end - currentClass = getmetatable(currentClass) and rawget(getmetatable(currentClass), '__index') - end - - self._properties = properties - - local originalMT = getmetatable(self) - local originalIndex = originalMT.__index - setmetatable(self, { - __index = function(t, k) - if self._properties[k] then - return self._values[k] - end - if type(originalIndex) == "function" then - return originalIndex(t, k) - else - return originalIndex[k] - end - end, - __newindex = function(t, k, v) - if self._properties[k] then - if self._properties[k].setter then - v = self._properties[k].setter(self, v) - end - self:_updateProperty(k, v) - else - rawset(t, k, v) - end - end, - __tostring = function(self) - return string.format("Object: %s (id: %s)", self._values.type, self.id) - end - }) - - for name, config in pairs(properties) do - if self._values[name] == nil then - if type(config.default) == "table" then - self._values[name] = deepCopy(config.default) - else - self._values[name] = config.default - end - end - end - - return self -end - -function PropertySystem:_updateProperty(name, value) - local oldValue = self._values[name] - if oldValue ~= value then - self._values[name] = value - if self._properties[name].canTriggerRender then - self:updateRender() - end - if self._observers[name] then - for _, callback in ipairs(self._observers[name]) do - callback(self, value, oldValue) - end - end - end -end - -function PropertySystem:observe(name, callback) - self._observers[name] = self._observers[name] or {} - table.insert(self._observers[name], callback) - return self -end - -return PropertySystem \ No newline at end of file diff --git a/src/main.lua b/src/main.lua index e720a9f..6574f2c 100644 --- a/src/main.lua +++ b/src/main.lua @@ -112,6 +112,5 @@ function basalt.run(isActive) end end end -basalt.autoUpdate = basalt.run return basalt \ No newline at end of file