From eac9b3640956f055aa00b885654bc2a3f9792670 Mon Sep 17 00:00:00 2001 From: perro Date: Thu, 25 May 2023 12:56:03 -0700 Subject: [PATCH] Distribution now are two: bundle and minimal --- README.md | 24 +- dist/{lin.lua => lin.bundle.lua} | 3 + dist/lin.min.lua | 363 +++++++++++++++++++++++++++++++ scripts/make_dist.lua | 45 ++-- scripts/make_jsons.lua | 3 - scripts/test.lua | 2 +- tests/pass.lit.infos.md | 185 ++++++++++++++++ 7 files changed, 596 insertions(+), 29 deletions(-) rename dist/{lin.lua => lin.bundle.lua} (99%) create mode 100644 dist/lin.min.lua create mode 100644 tests/pass.lit.infos.md diff --git a/README.md b/README.md index 24785fb..37c1f89 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,27 @@ process of creating works of literature][1]". - [Pandoc] v3 +if you decide to use `lin.min.lua` instead of `lin.bundle.lua`, you need to +install and preload the following rocks: + +- [fennel] +- [lua-dog] + ## Install 1. Go to [Releases]. -2. Download the latest version of `lin.lua`. +2. Download the latest version of `lin.bundle.lua` or `lin.min.lua`. 3. Done! ## Usage -With `lin.lua` downloaded and Pandoc installed, do: +With `lin.bundle.lua` or `lin.min.lua` downloaded and Pandoc installed, do: - pandoc -L PATH/TO/lin.lua -t FORMAT DOC + pandoc -L PATH/TO/lin.bundle.lua -t FORMAT DOC For example, if `DOC` is `source.md` and the output `FORMAT` is HTML, do: - pandoc -L PATH/TO/lin.lua -t html source.md + pandoc -L PATH/TO/lin.bundle.lua -t html source.md ## Manual @@ -50,7 +56,7 @@ For make distribution filter, do: pandoc lua scripts/make_dist.lua -For make jsons (intented for testing asserts), do: +For make JSON (intented for testing asserts), do: pandoc lua scripts/make_jsons.lua @@ -67,8 +73,8 @@ This wouldn't be possible without these projects and their collaborators: - [Pandoc][]: universal document converter and parser; handles the requirements for LIN. - [Lua][]: programming language; enables LIN. -- [Fennel][]: [Lisp] dialect with full Lua compatibility; allows native evals - for Lisp. +- [Fennel][2]: [Lisp] dialect with full Lua compatibility; allows native + evals for Lisp. ## License @@ -82,9 +88,11 @@ Happy hacking :) [natural]: https://en.wikipedia.org/wiki/Natural-language_programming [1]: https://web.archive.org/web/20170605163729/http://www.desy.de/user/projects/LitProg/Philosophy.html [Pandoc]: https://pandoc.org/ + [fennel]: https://luarocks.org/modules/technomancy/fennel + [lua-dog]: https://luarocks.org/modules/perro/lua-dog [Releases]: https://git.cuates.net/perro/computable-pandoc/releases [here]: https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/man/README.md [`luarocks`]: https://luarocks.org/ - [Fennel]: https://fennel-lang.org + [2]: https://fennel-lang.org [Lisp]: https://en.wikipedia.org/wiki/Lisp_(programming_language) [GPLv3]: https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt diff --git a/dist/lin.lua b/dist/lin.bundle.lua similarity index 99% rename from dist/lin.lua rename to dist/lin.bundle.lua index da87f6d..5b7a4f0 100644 --- a/dist/lin.lua +++ b/dist/lin.bundle.lua @@ -11,6 +11,7 @@ Fennel: Source: https://sr.ht/~technomancy/fennel or https://github.com/bakpakin/Fennel/issues Website: https://fennel-lang.org ]]-- + package.preload["fennel.repl"] = package.preload["fennel.repl"] or function(...) local utils = require("fennel.utils") local parser = require("fennel.parser") @@ -6556,6 +6557,7 @@ if pandoc ~= nil then end end + ----------------------------------- NATURAL ----------------------------------- local nat = {} @@ -6563,6 +6565,7 @@ local nat = {} function nat.get(str) return str end + ---------------------------------- LITERATE ---------------------------------- -- Variable for all literate stuff diff --git a/dist/lin.min.lua b/dist/lin.min.lua new file mode 100644 index 0000000..49ac8a6 --- /dev/null +++ b/dist/lin.min.lua @@ -0,0 +1,363 @@ +--[[ +Computable Pandoc: + (C) 2023 perro hi@perrotuerto.blog + License: GPLv3 https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt + Source: https://git.cuates.net/perro/computable-pandoc +]]-- + +require "fennel" +require "dog" + +----------------------------------- NATURAL ----------------------------------- + +local nat = {} + +function nat.get(str) + return str +end + +---------------------------------- LITERATE ---------------------------------- + +-- Variable for all literate stuff +local lit = {} + +-- Status indicator +lit.status = true + +-- Grammars +function lit.g(name) + + -- Lexical elements + local newline = lpeg.P"\r"^-1 * lpeg.P"\n" + local space = lpeg.S" \t" + local anyspace = lpeg.S" \t\r\n" + local spot = (1 - anyspace) + local any = (spot + space) + local yamlheader = space^0 * lpeg.P"---" * space^0 * newline + local yamlfooter = space^0 * lpeg.P"..." * space^0 * newline^-1 + local yamlbody = -yamlfooter * any^0 * newline + -- Still not used + local id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0 + local ref = lpeg.P"#" * id + + local grammars = { + -- Block grammar + ["block"] = lpeg.P { + "Block"; + Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name"); + YAML = lpeg.C(yamlheader * yamlbody^0 * yamlfooter); + Code = lpeg.C((any + newline)^0); + }, + } + + return grammars[name] +end + +-- Prints located messages +function lit.puts(key, ...) + + -- Returns debug level as number + local function debuglevel(str) + if str == "ERROR" then + lit.status = false + return 2 + elseif str == "WARNING" then + return 1 + else + return 0 + end + end + + -- Gets located message + local function getmsg(key, ...) + local msg = pandoc.metatotable(pandoc.read([[--- +INFO: + checking: + en: "Checking:\n#1" + es: "Comprobando:\n#1" + skip_check: + en: "Skipping '#1': this check is not supported" + es: "Skipping '#1': esta comprobación no está soportada" +WARNING: + ignore_lang: + en: "Keys 'cmd' and 'lang' present: '#1' overrides '#2'" + es: "Llaves 'cmd' y 'lang' presentes: '#1' sobrescribe '#2'" +ERROR: + invalid_key: + en: Invalid key '#1' + es: Clave '#1' inválida + invalid_path: + en: Invalid path '#1' in key '#2' + es: Ruta '#1' inválida en clave '#2' + invalid_type: + en: Invalid type '#1' in key '#2' + es: Tipo '#1' inválido en clave '#2' + invalid_value: + en: Invalid value '#1' in key '#2' + es: Valor '#1' inválido en clave '#2' + invalid_cmd: + en: Invalid cmd '#1' + es: Comando '#1' inválido + no_key: + en: Key '#1' not found + es: Clave '#1' no encontrada + aborted: + en: Aborted due previous errors + es: Abortado debido a previos errores + meta_empty: + en: Empty metadata + es: Metadatos vacíos + meta_invalid: + en: Invalid metadata + es: Metadatos inválidos +... +]]).meta) + local levelname, level, lang = "INFO", 0, os.lang() + for mtype, msgs in pairs(msg) do + if msgs[key] ~= nil then + key = (msgs[key][lang] ~= nil and msgs[key][lang] or msgs[key]["en"]) + for i, str in ipairs({...}) do key = key:gsub("#" .. i, str) end + levelname, level = mtype, debuglevel(mtype) + break + end + end + return key, levelname, level + end + + local verbosity = debuglevel(PANDOC_STATE.verbosity) + local msg, levelname, level = getmsg(key, ...) + if level >= verbosity then + print("[" .. levelname .. "] [LIT] " .. msg) + end +end + +-- Examinates (parses and evaluates) document +function lit.exam(doc) + + -- Evaluates code block + -- Evals Lisp code + --[[ + local function eval(code) + local is_passed, out = pcall ( + function () return fennel.eval(code) end, + function (e) return e end + ) + local lua = "" + local out = tostring(out) + local preview = out:gsub("\n.*", "") + if is_passed then + lua = fennel.compileString(code) + end + return {is_passed = is_passed, preview = preview, out = out, lua = lua} + end + ]]-- + + -- Parses code block + local function parse(codeblock) + + -- Checks code block + local function check(parsed) + + -- Indicates if checks passes + local ispass = true + + -- Valid metadata structure + local metastruct = { + ["mandatory"] = { + -- Value as table == whole patterns to match + ["id"] = {"%a[_%w]*"}, + }, + ["optional"] = { + ["lang"] = { + "lua", "fennel", "python", "js", "ruby", "lisp", "graphviz", + }, + ["cmd"] = {".+"}, + -- Value as string == type to match + ["args"] = "table", + ["shift"] = "boolean", + ["wipe"] = "boolean", + ["typed"] = "boolean", + ["link"] = "path", + ["img"] = "path", + ["alt"] = "string", + ["dump"] = "path", + ["quote"] = "boolean", + }, + } + + -- Language commands + local langcmds = { + ["lua"] = {["int"] = "TODO"}, + ["fennel"] = {["int"] = "TODO"}, + ["python"] = {["ext"] = "TODO"}, + ["js"] = {["ext"] = "TODO"}, + ["ruby"] = {["ext"] = "TODO"}, + ["lisp"] = {["ext"] = "TODO"}, + ["graphviz"] = {["ext"] = "TODO"}, + } + + -- Prints error and does not pass the check + local function putserr(...) + lit.puts(...) + ispass = false + end + + -- Adds 'eval' key in meta + -- This keys indicates what to eval + local function addeval(meta) + meta["eval"] = {} + if ispass then + if meta["lang"] and meta["cmd"] then + lit.puts("ignore_lang", meta["cmd"], meta["lang"]) + meta["eval"] = {["ext"] = meta["cmd"]} + elseif not(meta["lang"]) and not(meta["cmd"])then + meta["lang"] = "lua" + meta["eval"] = langcmds["lua"] + elseif meta["lang"] and not(meta["cmd"]) then + meta["eval"] = langcmds[meta["lang"]] + elseif not(meta["lang"]) and meta["cmd"] then + meta["eval"] = {["ext"] = meta["cmd"]} + end + end + return meta + end + + -- Checks for valid command + local function checkcmd(meta) + if meta["cmd"] then + cmd = meta["cmd"]:split()[1] + if os.isunix() then + status = io.try("type", cmd) + if not(status) then + putserr("invalid_cmd", cmd) + end + else + lit.puts("skip_check", "checkcmd") + end + end + end + + -- Checks for extra and unwanted metadata + local function checkextra(meta) + for key, val in pairs(meta) do + local missing1 = metastruct["mandatory"][key] == nil + local missing2 = metastruct["optional"][key] == nil + if missing1 and missing2 then + putserr("invalid_key", key) + end + end + end + + -- Checks for valid metadata + local function checkmeta(meta, kind) + + -- Checks for valid metadata type + local function checktype(type1, meta, key) + local val = meta[key] + local type2 = type(val) + if type1 ~= type2 then + if type1 == "path" then + if not(pandoc.path.directory(val):isdir()) then + putserr("invalid_path", val, key) + end + else + putserr("invalid_type", type2, key) + end + end + end + + -- Checks for valid metadata pattern + local function checkpattern(table, meta, key) + local val = tostring(meta[key]) + local err = key + for _, pattern in pairs(table) do + if val:match("^" .. pattern .. "$") then + err = "" + break + end + end + if not(err:isempty()) then + putserr("invalid_value", val, err) + end + end + + for key, val in pairs(metastruct[kind]) do + if meta[key] == nil and kind == "mandatory" then + putserr("no_key", key) + elseif meta[key] and type(val) == "table" then + checkpattern(val, meta, key) + elseif meta[key] then + checktype(val, meta, key) + end + end + end + + -- Parses metadata + local function parsemeta(yaml) + local isok, res = pcall(pandoc.read, yaml) + local meta = {} + if isok and not(pandoc.utils.stringify(res.meta):isempty()) then + meta = pandoc.metatotable(res.meta) + checkmeta(meta, "mandatory") + checkmeta(meta, "optional") + checkextra(meta) + checkcmd(meta) + elseif isok and pandoc.utils.stringify(res.meta):isempty() then + putserr("meta_empty") + else + putserr("meta_invalid") + end + return addeval(meta) + end + + lit.puts("checking", table.concat(parsed, ""):indent()) + local meta = parsemeta(parsed[1]) + -- TODO: + -- meta["code"] = checkcode(parsed[2], meta) + -- NOTE: if not valid code, return meta["eval"] = {} + return meta + end + + local parsed = lpeg.match(lit.g("block"), codeblock.text) + local checked = (parsed ~= nil and check(parsed) or parsed) + -- TODO: + -- evaluated = eval(checked) + -- TODO: write evaluated and do overrides with warns + -- return evaluated + return codeblock + end + + -- TODO + -- Evals and inserts inline code + local function insert(code) + return code + end + + -- Asserts literate status + local function assert() + if not(lit.status) then + lit.puts("aborted") + os.exit(1) + end + end + + return doc:walk { CodeBlock = function(block) return parse(block) end } + :walk { Code = function(inline) return insert(inline) end } + :walk { Pandoc = function(_) assert() end } +end + +------------------------------------ PANDOC ----------------------------------- + +return { + -- Parses and evals literate programming + { Pandoc = function(doc) return lit.exam(doc) end }, + { + -- Parses and evals natural programming + -- TODO + Inlines = function(inlines) + md = pandoc.utils.stringify(inlines) + md = nat.get(md) + return inlines + end, + } +} diff --git a/scripts/make_dist.lua b/scripts/make_dist.lua index 7429023..5aadc5e 100644 --- a/scripts/make_dist.lua +++ b/scripts/make_dist.lua @@ -1,4 +1,4 @@ --- Makes bundle distribution +-- Makes bundle and min distribution -- Adds local luarocks modules local optpath = "./opt/share/lua/5.4/" @@ -8,34 +8,36 @@ package.path = optpath .. "?.lua;" .. package.path require "dog" -- Makes distribution -local function make_dist() +local function make_dist(name, bundle) -- Chomps file - local function chomp(str, without) + local function chomp(str) local without = without or false str = string.readtext(str):strip() - if without then - return str:gsub("\n[^\n]+$", "") - end return "\n" .. str .. "\n" end -- Variables - local name = "lin.lua" - local dist = pandoc.path.join({"dist", name}) + local bundle = (bundle == nil or bundle == true) + local dist = pandoc.path.join({"dist", name}) local ext = chomp(optpath .. "dog.lua") - local fnl = chomp(optpath .. "fennel.lua", true) + local fnl = chomp(optpath .. "fennel.lua") + local fnl = fnl:gsub("\nreturn mod\n", "\nlocal fnl = mod\n") local pan = chomp("src/pandoc.lua") - local nat = chomp("src/natural.lua", true) - local lit = chomp("src/literate.lua", true) + local nat = chomp("src/natural.lua"):gsub("\nreturn nat\n", "") local msg = ("src/locale.yaml"):readtext() + local lit = chomp("src/literate.lua"):gsub("\nreturn lit\n", "") + local lit = lit:gsub("#locale%(%)", msg) local license = string.strip([[ -Computable Pandoc & Fennel Bundle: - A Pandoc filter for literate and natural programming Computable Pandoc: (C) 2023 perro hi@perrotuerto.blog License: GPLv3 https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt Source: https://git.cuates.net/perro/computable-pandoc + ]]) + local extralicense = string.strip([[ +Computable Pandoc & Fennel Bundle: + A Pandoc filter for literate and natural programming +]] .. license .. "\n" .. [[ Fennel: (C) 2016-2023 Calvin Rose and contributors License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE @@ -44,11 +46,20 @@ Fennel: ]]) -- Bundles Fennel and Computable Pandoc + -- TODO: lit and nat should be rocks and, therefore, should be: + -- added only in bundle + -- use 'require' in minimal file = io.open(dist, "w") - file:write("--[[\n", license, "\n]]--\n") - file:write(fnl, "\nlocal fnl = mod\n", ext, nat) - file:write(lit:gsub("#locale%(%)", msg), pan) + if bundle then + file:write("--[[\n", extralicense, "\n]]--\n") + file:write(fnl, ext) + else + file:write("--[[\n", license, "\n]]--\n") + file:write('\nrequire "fennel"\nrequire "dog"\n') + end + file:write(nat, lit, pan) file:close() end -make_dist() +make_dist("lin.bundle.lua") +make_dist("lin.min.lua", false) diff --git a/scripts/make_jsons.lua b/scripts/make_jsons.lua index db71552..a713819 100644 --- a/scripts/make_jsons.lua +++ b/scripts/make_jsons.lua @@ -6,9 +6,6 @@ package.path = "./opt/share/lua/5.4/?.lua;" .. package.path -- Adds Lua custom extensions require "dog" --- Variables -local filter = "dist/lin.lua" - -- Gets command according to OS local function getcmd(str) local system = (os.isunix() and "unix" or "win") diff --git a/scripts/test.lua b/scripts/test.lua index 1438d51..473bc43 100644 --- a/scripts/test.lua +++ b/scripts/test.lua @@ -8,7 +8,7 @@ require "dog" require "scripts.make_dist" -- Variables -local filter = "dist/lin.lua" +local filter = "dist/lin.bundle.lua" local verbose = "" local trace = "" local files = {} diff --git a/tests/pass.lit.infos.md b/tests/pass.lit.infos.md new file mode 100644 index 0000000..8abcf56 --- /dev/null +++ b/tests/pass.lit.infos.md @@ -0,0 +1,185 @@ +# Inserts Before Blocks + +- `fn1()``fn2()` +- `fn3(true)` +- `fn4(2.0, 3.1)` +- `fn3(n: false)` +- `fn4(b: 4, a: 5)` + +# Literate Blocks + +All literate blocks should be print on `--verbose`. + +Minimal (Lua by default): + + --- + id: fn1 + ... + 1 + 2 + 3 + +With scape: + + --- + id: fn2 + ... + "\#x" + +With arg: + + --- + id: fn3 + args: + n: str + ... + #n == #n + +With lang and args: + + --- + id: fn4 + lang: fennel + args: + a: 1 + b: 2 + ... + (* #a #b) + +With cmd (ignores lang): + + --- + id: fn5 + cmd: python -E -X utf8 + args: + n: 2 + ... + #n + #n + +With shift: + + --- + id: fn6 + shift: true + ... + "The literate block is shifted by its eval result." + +With wipe: + + --- + id: fn7 + wipe: true + ... + "This evals but it is wipe from doc." + +With typed: + + --- + id: fn4 + typed: true + args: + a: 1 + b: 2 + ... + #a * #b + +With link and alt: + + --- + id: fn8 + lang: graphviz + link: ./graph.png + alt: A graph. + ... + digraph G { + a -> b; + b -> c + c -> a; + } + +With img and alt: + + --- + id: fn9 + lang: graphviz + img: ./graph.png + alt: A graph. + ... + digraph G { + a -> b; + b -> c + c -> a; + } + +With dump and quote: + + --- + id: fn10 + dump: ./dump.txt + quote: true + ... + This code is saved into './dump.txt' because of 'dump'. + This code is not evaluated because 'quote' is true. + +With inner function: + + --- + id: fn11 + args: + x: 2 + ... + #fn1() * x + +With inner function with args: + + --- + id: fn12 + args: + y: 1 + z: 2 + ... + #y + #fn4(3, 4) + #z + +With inner inner function: + + --- + id: fn13 + args: + a: 1 + ... + #a + #fn4(#a, #fn1()) + +# Code Blocks + +Always ignored: + + --- + echo "Ignore me!" + +# Inserts and Data Types + +- `fn3(true)` +- `fn3(false)` +- `fn3([])` +- `fn3([0, 1])` +- `fn3({})` +- `fn3({"k1": 1, "k2": 2})` +- `fn4(3)` +- `fn5(1.0)` +- `fn5("str")` + +# Messy Inserts + +- `fn1( )` +- `fn3("\"str\"")` +- `fn3( 4)` +- `fn3(5 )` +- `fn3( 6 )` +- `fn3( n: 7)` +- `fn3(n: 8 )` +- `fn3( n: 9 )` +- `fn3(n:10)` +- `fn4( a: 6, b: 7)` +- `fn4( a: 8 , b: 9)` +- `fn4( a: 10 , b: 11)` +- `fn4( a: 12 , b: 13)` +- `fn4( a: 14 , b: 15 )` +- `fn4(a:16,b:17)`