commit 8d306ab6905628f92a73d05622b02e3983c4eeb1 Author: Er2 Date: Wed May 4 04:48:42 2022 +0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe4fb11 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build.lua diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..dbd0f16 --- /dev/null +++ b/config.lua @@ -0,0 +1,12 @@ +return { + entry = 'main.lua', + output = { + name = 'build.lua' + }, + plug = { + --require 'plug.switch' {}, + require 'plug.arrow' {}, + require 'plug.assigns' {}, + require 'plug.num' {}, + }, +} diff --git a/examples/config.lua b/examples/config.lua new file mode 100644 index 0000000..fc841b2 --- /dev/null +++ b/examples/config.lua @@ -0,0 +1,14 @@ +return { + entry = { + 'main.lua', + }, + output = { + name = '$hash.lua' + }, + plug = { + --require 'plug.switch' {}, + require 'plug.arrow' {}, + require 'plug.assigns' {}, + require 'plug.num' {}, + }, +} diff --git a/examples/main.lua b/examples/main.lua new file mode 100644 index 0000000..96100b1 --- /dev/null +++ b/examples/main.lua @@ -0,0 +1,29 @@ + +local main = () ==> + + emp = () => + + add = (a, b) => a + b + + local a, b = 5, 8 + add(a, b) + + a += 0x10_10 + +--[[ + switch(a) { + 5, 8 => error 'cant', + 300 => error 'trahtarista' + (() =>)() + , + _ => print 'can', + } +--]] + + print 'hello world' +<== +main() + +{ + tst = true +} diff --git a/img/luapack.png b/img/luapack.png new file mode 100644 index 0000000..e126649 Binary files /dev/null and b/img/luapack.png differ diff --git a/img/luapack.svg b/img/luapack.svg new file mode 100644 index 0000000..ec4f9fa --- /dev/null +++ b/img/luapack.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + diff --git a/license b/license new file mode 100644 index 0000000..888e15d --- /dev/null +++ b/license @@ -0,0 +1,19 @@ +Zlib License + +Copyright (c) 2022 Er2 + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..7ef87ce --- /dev/null +++ b/main.lua @@ -0,0 +1,303 @@ +lp = { + name = 'LuaPack', + version = '0.1', + copyright = '(c) Er2 2022, Zlib License', + + path = {}, +} + +-- String interpolation +function lp.strf(str, env) + return str:gsub('%$([%w_]+)%]?', (n) ==> + if env[n] + then return tostring(env[n]) + end + <==) .. '' +end + +-- Helper function +function lp.insPath(t) + for _, v in pairs(t) + do table.insert(lp.path, v) + end +end + +function lp.foreach(e, fn) + for _, v in pairs(e.plug) + do fn(v) end +end + +function lp.reqf(file, opts) + if opts.ext[file] + then return opts.ext[file] + end +end + +function lp.req(e, opts) + return lp.reqf(e.file, opts) +end + +-- Polyfill +function table.contains(t, V) + for k, v in pairs(t) do + if v == V + then return k + end + end +end + +-- Try to find file path +function lp.resolve(name, dir) + if path.absolute(name) + then return name + end + + name = name:gsub('%.[%w_]+$', '') + + for _, v in pairs(lp.path) do + v = v:gsub('%?', name) + v = path.join(dir, v) + + local f = io.open(v) + if f then + f:close() + return v + end + end +end + +-- Use this in plugins to get module +function lp.get(d, e, opts) + local f + for _, v in pairs(e.plug) do + f = lp.resolve(d, e.dir) + if f then break end + end + + if not f then + print(lp.strf(' [WRN] Cannot find "$file"', {file = d})) + return nil + end + + local c = lp.reqf(f, opts) + if not c + then c = lp.make(f, opts, f .. '.out.lua') + end + + if not e.req[c.i] + then e.reqs = e.reqs + 1 + end + e.req[c.i] = true + + print(lp.strf(' [GOT] $file', c)) + + return c +end + +-- Alias to makeEnt without entry flag +function lp.make(file, opts, out) + return lp.makeEnt(file, opts, out, false) +end + +-- Make function +function lp.makeEnt(file, opts, out, entry) + local dir = path.abs(path.dir(file)) + local ext = file:match '%.([%w_]+)$' + local oext = out:match '%.([%w_]+)$' + + file = lp.resolve(file, dir, ext) + + local e = lp.reqf(file, opts) + if e then return e end + + local f, err = io.open(file) + assert(f, err) + local src = f:read '*a' + f:close() + + e = { + i = #opts.ext + 1, + file = file, + dir = dir, + ext = ext, + src = src, + reqs = 0, + req = {}, + entry = entry, + plug = opts.plug, + } + table.insert(opts.ext, file) + opts.ext[file] = e + + if #e.plug == 0 + then error(lp.strf('File "$file" cannot be loaded. Add plugins.', e)) + end + + lp.foreach(e, (v) ==> + v.load(e, ext, oext) + e.src = v.deps(e, opts) or e.src + e.src = v.transform(e, opts) or e.src + <==) + + return e +end + +local alp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +function lp.randgen(n) + local g = '' + for i = 1, n do + local v = math.random(#alp) + g = g.. alp:sub(v, v) + end + return g +end + +function lp.save(e, out) + out = lp.strf(out, { + name = path.base(e.file):gsub('%.[%w_]+$', ''), + hash = lp.randgen(6), + }) + local f, err = io.open(out, 'w') + assert(f, err) + f:write(e.src) + f:close() + return out +end + +function lp.config(conf, dir) + local cfg = { + -- defaults + entry = {}, + output = {}, + ext = {}, + plug = { + require 'plug.lua' {} + }, + } + for k, v in pairs(conf) do + if k == 'entry' + or k == 'output' + then + if type(v) == 'string' + then table.insert(cfg[k], path.abs(v, dir)) + elseif type(v) == 'table' then + for _, v in pairs(v) + do table.insert(cfg[k], path.abs(tostring(v), dir)) + end + else error(lp.strf('Unknown type $type in "$opt"', {type = type(v), opt = k})) + end + elseif k == 'plug' + then for _, v in pairs(v) do + if type(v) == 'table' + then table.insert(cfg[k], v) + v.load = v.load or function(e, ext, oext) end + v.deps = v.deps or function(e, opts) end + v.transform = v.transform or function(e, opts) end + v.postbuild = v.postbuild or function(e, opts) end + else error(lp.strf('Unknown type $type in "$opt"', {type = type(v), opt = k})) + end + end + else error(lp.strf('Unknown option "$opt"', {opt = k})) + end + end + + return cfg +end + +function lp.build(conf, dir) + local opts = lp.config(conf, dir) + for k, v in pairs(opts.entry) do + local out = opts.output[math.min(k, #opts.output)] + print(lp.strf('[BUILD] $file => $out', {file = v, out = out})) + local e = lp.makeEnt(v, opts, out, true) + lp.foreach(e, function(v) + v.postbuild(e, opts) + end) + local save = lp.save(e, out) + print(lp.strf('[BUILT] $o => $r', {o = e.file, r = save})) + end +end + +function main(argv) + print(lp.name ..' '.. lp.version) + print(lp.copyright) + print '' + + local int = (argv[0] or ''):match '^lua' -- started using interpreter + if #argv == 0 + or (#argv == 1 and int) then + print(('Usage: %s config.lua'):format(int and (argv[0] ..' '.. argv[1]) or argv[0])) + return -1 + end + + local t = os.time() + + local ent = path.abs(int and argv[2] or argv[1]) + local dir = path.dir(ent) + + local f, err = io.open(ent) + assert(f, err) + local conf = f:read '*a' + f:close() + + f, err = load(conf, 'config') + if not f + then error(err) + else conf = f() + end + + lp.build(conf, dir) + + print(lp.strf('Built under $sec seconds.', {sec = os.time() - t})) + + return 0 +end + +path = { + absolute = (f) ==> + f = f:sub(1, 1) + return f == '/' or f == '\\' + <== + + , relative = (f) => not path.absolute(f) + + , abs = (f, pwd) ==> + if path.absolute(f) + then return f end + + pwd or= os.getenv 'PWD' + + f = pwd .. '/'.. f + f = f:gsub('(.*)[\\/]+%.%.', path.dir) + f = f:gsub('%.[\\/]+', '/') + f = f:gsub('[\\/]+', '/') + + return f + <== + + , rel = (f) => f + + , base = (f) => f:match '([^/\\]*)[/\\]*$' + + , dir = (f) => f:match '^(.*)[\\/]+[^\\/]*[\\/]*$' + + , file = (f) => f:match '([^\\/]+)[\\/]*$' + + , join = function(base, add) + if path.absolute(add) + then return add end + + if path.relative(base) + then base = path.abs(base) + end + + local p = path.abs(base ..'/'.. add) + if path.relative(p) + then return add end + + return p + end, +} + +math.randomseed(os.time()) +os.exit(main(arg)) diff --git a/plug/arrow.lua b/plug/arrow.lua new file mode 100644 index 0000000..b317f31 --- /dev/null +++ b/plug/arrow.lua @@ -0,0 +1,17 @@ +-- Arrow functions in Lua +-- f = (n) => 2 + n + +return function(op) + return { + name = 'Arrow functions', + transform = function(e, opts) + return e.src + -- multiple-line + :gsub('%(([^%(%)]*)%)%s*==>%s*(.-)%s<==', + 'function(%1) %2 end') + -- single-line + :gsub('%(([^%(%)]*)%)%s*=>[ \t\v\f]*([^\n\r]*)', + 'function(%1) return %2 end') + end, + } +end diff --git a/plug/assigns.lua b/plug/assigns.lua new file mode 100644 index 0000000..cebbf3c --- /dev/null +++ b/plug/assigns.lua @@ -0,0 +1,16 @@ + +local reg = '([%%w_%%.%%[%%]]+)%%s*(%s)=%%s*' +local regv = '%1 = %1 %2 ' + +return function(op) + return { + name = 'Assigments', + transform = function(e, opts) + return e.src + :gsub(reg:format '[%+%-%*%/%%^|&]', regv) + :gsub(reg:format '%.%.', regv) + :gsub(reg:format 'and', regv) + :gsub(reg:format 'or', regv) + end, + } +end diff --git a/plug/lua.lua b/plug/lua.lua new file mode 100644 index 0000000..a100f64 --- /dev/null +++ b/plug/lua.lua @@ -0,0 +1,108 @@ + +lp.insPath { + '?.lua', + '?/init.lua', +} + +local env = { + name = lp.name, + ver = lp.version, + copy = lp.copyright, + + cache = '_c_', + req = '_r_', + mreq = '_m_', -- require by map + mtemp = ' function($req) -- $file\n$src\n end, -- $file\n', + pre = '--[=[=]=]', + regex = '([^\n]-)' .. 'require%s*%(?["\'](.-)["\']%)?%s*', +} + +env.require = lp.strf('$req(%d)', env) + +local base = lp.strf([[ +$pre -- $name $ver +$pre -- $copy +$pre (function(m) +$pre -- Module cache +$pre local $cache = {} +%s$pre +$pre -- Load main module +$pre $require +$pre +$pre end) { +%s +$pre } +]], env) + +local main = lp.strf([[ +$pre +$pre -- Require function +$pre local function $req(i) +$pre -- Check in cache +$pre local c = $cache[i] +$pre if not c then +$pre -- Execute module function +$pre c = m[i]($req) +$pre +$pre -- And put it to cache +$pre $cache[i] = c +$pre end +$pre +$pre -- Return cached returned value +$pre return c +$pre end +]], env) + +local mreq = lp.strf([[ +$pre +$pre -- Require by map +$pre local function $mreq(m, r) +$pre -- Check if module exists +$pre if not m[r] +$pre then error('Cannot find module "'..r..'"') +$pre end +$pre +$pre -- Using default require +$pre return $req(m[r]) +$pre end +]], env) + +return function(op) + return { + name = 'Base Lua plugin', + load = function(e, ext, oext) + if ext == 'lua' + then return true -- load every lua file + end + end, + deps = function(e, opts) + return e.src:gsub(env.regex, function(bef, name) + name = name:gsub('%.', '/') -- lua.lua => lua/lua + if bef:match '%-%-' + then return '' end -- under comment + local d = lp.get(name, e, opts) + if d then + return bef.. env.require:format(d.i) + end + end) + end, + transform = function(e, opts) + if not e.entry then return end -- change only entry files + if e.reqs > 0 then + local m = '' + for i = 1, #opts.ext do + local v = opts.ext[opts.ext[i]] -- [i] => [file] => object + m = m.. lp.strf(lp.strf(env.mtemp, env), v) + end + return base:format(main, opts.ext[e.file].i, m) + end + end, + postbuild = function(e, opts) + local f, err = load(e.src, 'lint') + if err then + print(e.src) + error(err) + end + end, + } +end diff --git a/plug/num.lua b/plug/num.lua new file mode 100644 index 0000000..a454e41 --- /dev/null +++ b/plug/num.lua @@ -0,0 +1,28 @@ + +return function(op) + return { + name = 'Better numbers', + transform = function(e, opts) + return e.src + :gsub('%.?%d[%._0-9A-Fa-fxboXBO]+', function(num) + local base = 10 + local nnum = num + :gsub('_', '') + :gsub('^0([xboXBO])', function(m) + if m == 'x' or m == 'X' + then base = 16 + elseif m == 'b' or m == 'B' + then base = 2 + elseif m == 'o' or m == 'O' + then base = 8 + end + return '' + end) + assert(not nnum:match '[xboXBO]', 'invalid number '..num) + nnum = tonumber(nnum, base) + assert(nnum, 'invalid number '..num) + return tostring(nnum) + end) + end, + } +end diff --git a/plug/switch.lua b/plug/switch.lua new file mode 100644 index 0000000..7cb1b48 --- /dev/null +++ b/plug/switch.lua @@ -0,0 +1,31 @@ + +-- BROKEN + +local reg = 'switch%s*%(([^{}]-)%)%s*{(.-)}' +local regv = '%s*([^=>]+)%s*=>%s*(.-);' + +return function(op) + return { + name = 'Switch-case', + transform = function(e, opts) + return e.src:gsub(reg, function(case, code) + local st + return code:gsub(regv, function(when, c) + local code = st and 'elseif' or 'if' + st = true + if when == '_' + then code = 'else ' + else local st + for v in when:gmatch '%s*([^,]+)' do + code = code .. (st and ' or' or '') + .. (' %s == %s'):format(case, v) + st = true + end + code = code .. ' then ' + end + return code .. c + end) .. ' end' + end) + end, + } +end diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..139f971 --- /dev/null +++ b/readme.md @@ -0,0 +1,21 @@ +
+ +[![Logo](img/luapack.svg)](https://gitdab.com/er2/luapack) + +# LuaPack + +LuaPack is a module bundler. It takes Lua, MoonScript etc. files, transforms and bundles (packing). + +
+ +# Usage + +Usage is very simple: + +```lua build.lua config.lua``` + +# Installation + +You can't use main.lua file directly, because it uses non-standard Lua features like arrow functions. + +Download `build.lua` from releases.