From reader to filter... again

This commit is contained in:
perro tuerto 2023-03-11 12:14:24 -08:00
parent a786c4f227
commit 37eb069a1b
8 changed files with 176 additions and 113 deletions

View File

@ -1,8 +1,8 @@
# Literate Pandoc # Literate Pandoc
Literate Pandoc is a [Pandoc reader] written in [Lua] for [literate] and Literate Pandoc is a [Pandoc filter] written in [Lua] for [literate] and
[natural] programming (LNP), i.e.: "[Programming \[...\] as the process of [natural] programming (LiNP or just Linp), i.e.: "[Programming \[...\] as the
creating works of literature][1]". process of creating works of literature][1]".
## Requirements ## Requirements
@ -11,22 +11,22 @@ creating works of literature][1]".
## Install ## Install
1. Go to [Releases]. 1. Go to [Releases].
2. Download the latest version of `literate.lua`. 2. Download the latest version of `linp.lua`.
3. Done! 3. Done!
## Usage ## Usage
With `literate.lua` downloaded and Pandoc installed, do: With `linp.lua` downloaded and Pandoc installed, do:
pandoc -f PATH/TO/literate.lua -t FORMAT DOC pandoc -L PATH/TO/linp.lua -t FORMAT DOC
For example, if `DOC` is `source.md` and the output `FORMAT` is HTML, do: For example, if `DOC` is `source.md` and the output `FORMAT` is HTML, do:
pandoc -f PATH/TO/literate.lua -t html source.md pandoc -L PATH/TO/linp.lua -t html source.md
## Manual ## Manual
Learn how to do LNP [here]. Learn how to do LiNP [here].
## Test ## Test
@ -48,17 +48,17 @@ For example, if `FORMAT1` is Markdwon and `FORMAT2` is HTML, do:
For distribution tests, do: For distribution tests, do:
sh scripts/test.sh --dist FORMAT1 FORMAT2 sh scripts/test.sh FORMAT1 FORMAT2
## Acknowledgments ## Acknowledgments
This wouldn't be possible without these projects and their collaborators: This wouldn't be possible without these projects and their collaborators:
- [Pandoc][]: universal document converter and parser; handles the - [Pandoc][]: universal document converter and parser; handles the
requirements for LNP. requirements for LiNP.
- [Lua][]: programming language; enables LNP. - [Lua][]: programming language; enables LiNP.
- [Fennel][]: [Lisp] dialect with full Lua compatibility; allows to go from - [Fennel][]: [Lisp] dialect with full Lua compatibility; allows to go from
LNP to Lisp or Lua. LiNP to Lisp or Lua.
## License ## License
@ -66,7 +66,7 @@ Literate Pandoc is under [GPLv3].
Happy hacking :) Happy hacking :)
[Pandoc reader]: https://pandoc.org/custom-readers.html [Pandoc filter]: https://pandoc.org/lua-filters.html
[Lua]: https://www.lua.org/ [Lua]: https://www.lua.org/
[literate]: https://en.wikipedia.org/wiki/Literate_programming [literate]: https://en.wikipedia.org/wiki/Literate_programming
[natural]: https://en.wikipedia.org/wiki/Natural-language_programming [natural]: https://en.wikipedia.org/wiki/Natural-language_programming

View File

@ -1,6 +1,6 @@
--[[ --[[
Literate Pandoc & Fennel Bundle: Literate Pandoc & Fennel Bundle:
A Pandoc reader for literate and natural programming A Pandoc filter for literate and natural programming
Fennel: Fennel:
(C) 2016-2023 Calvin Rose and contributors (C) 2016-2023 Calvin Rose and contributors
License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE
@ -10,7 +10,6 @@ Literate Pandoc:
(C) 2023 perro hi@perrotuerto.blog (C) 2023 perro hi@perrotuerto.blog
License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt
Source: https://git.cuates.net/perro/literate-pandoc Source: https://git.cuates.net/perro/literate-pandoc
The following code is minified, check Literate Pandoc source for a readable version
]]-- ]]--
package.preload["fennel.repl"] = package.preload["fennel.repl"] or function(...) package.preload["fennel.repl"] = package.preload["fennel.repl"] or function(...)
local utils = require("fennel.utils") local utils = require("fennel.utils")
@ -6258,33 +6257,48 @@ do
end end
package.preload[module_name] = nil package.preload[module_name] = nil
end end
local fennel = mod local fnl = mod
----------------------------------- NATURAL -----------------------------------
-- Lua LPeg shortcuts local nat = {}
local P, S, R, Cf, Cc, Ct, V, Cs, Cg, Cb, B, C, Cmt =
lpeg.P, lpeg.S, lpeg.R, lpeg.Cf, lpeg.Cc, lpeg.Ct, lpeg.V, ----------------------------------- LITERATE ----------------------------------
lpeg.Cs, lpeg.Cg, lpeg.Cb, lpeg.B, lpeg.C, lpeg.Cmt
-- TODO
local lit = {}
-- Ordered collection of fn call, fn declarations or literal elements
lit.collection = {}
-- Lexical elements -- Lexical elements
local space = S(" \t\r\n") lit.space = lpeg.S" \t\r\n"
local lbracket = P"(" lit.spot = (1 - lit.space)
local rbracket = P")" lit.t1 = lpeg.S"()" -- TMP
local word = (1 - (space + lbracket + rbracket)) lit.t2 = (1 - (lit.t1 + lit.space)) -- TMP
function lit.add_call(str)
table.insert(lit.collection, {call = str})
end
function lit.add_declaration(str)
table.insert(lit.collection, {declaration = str})
end
function lit.add_lit(str)
table.insert(lit.collection, {literal = str})
end
-- Grammar -- Grammar
G = P{ lit.G = lpeg.P {
"Doc"; "Doc";
Doc = space^0 * Ct(V"SExpr"^0) * space^0 / pandoc.Pandoc; Doc = (lpeg.V"Call" + lpeg.V"Declaration" + lpeg.V"Literal")^0;
SExpr = (V"List" + V"Atom"); Call = lit.t1^1 / lit.add_call;
List = lbracket * V"SExpr"^0 * rbracket; Declaration = lit.t2^1 / lit.add_declaration;
Atom = space + V"Word"; Literal = lit.space^1 / lit.add_lit;
Word = word^1 / pandoc.Str;
} }
-- Evals Lisp code -- Evals Lisp code
-- @param code string: code to evaluate function lit.eval(code)
-- @return table: evaluation result as {bool, string, string, string}
local function eval(code)
local is_passed, out = pcall ( local is_passed, out = pcall (
function () return fennel.eval(code) end, function () return fennel.eval(code) end,
function (e) return e end function (e) return e end
@ -6298,15 +6312,48 @@ local function eval(code)
return {is_passed = is_passed, preview = preview, out = out, lua = lua} return {is_passed = is_passed, preview = preview, out = out, lua = lua}
end end
-- Calls Pandoc Reader. function lit.parse(raw)
function Reader(input, options) lpeg.match(lit.G, raw) -- TODO
--[[ local doc = ""
local doc = lpeg.match(G, tostring(input)) for i, t in ipairs(lit.collection) do
if options.standalone then for k, v in pairs(t) do
return exec(doc) -- print(i, k, v)
else doc = doc .. v
return doc end
end end
]]-- return doc
return pandoc.Pandoc(tostring(input))
end end
------------------------------------ PANDOC -----------------------------------
local function is_chosen(block)
chosen, content = true, block.content
if content == nil or pandoc.utils.type(content) ~= "Inlines" then
chosen = false
end
return chosen
end
local function sanitize(inlines)
for i, inline in ipairs(inlines) do
if inline.tag == "Code" then
inlines:remove(i)
elseif inline.tag == "Quoted" then
str = pandoc.utils.stringify(inline.content):gsub('"', '\\"')
inlines[i] = '"' .. str .. '"'
end
end
return pandoc.utils.stringify(inlines)
end
return {
{
Block = function (block)
if is_chosen(block) then
raw = sanitize(block.content)
doc = lit.parse(raw)
print(doc)
end
end,
}
}

View File

@ -1,11 +1,11 @@
# Makes distribution release # Makes distribution release
# Variables # Variables
NAME="literate.lua" NAME="linp.lua"
DIST=dist/$NAME DIST=dist/$NAME
LICENSE="--[[ LICENSE="--[[
Literate Pandoc & Fennel Bundle: Literate Pandoc & Fennel Bundle:
A Pandoc reader for literate and natural programming A Pandoc filter for literate and natural programming
Fennel: Fennel:
(C) 2016-2023 Calvin Rose and contributors (C) 2016-2023 Calvin Rose and contributors
License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE
@ -15,11 +15,12 @@ Literate Pandoc:
(C) 2023 perro hi@perrotuerto.blog (C) 2023 perro hi@perrotuerto.blog
License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt
Source: https://git.cuates.net/perro/literate-pandoc Source: https://git.cuates.net/perro/literate-pandoc
The following code is minified, check Literate Pandoc source for a readable version
]]--" ]]--"
# Merges Fennel and Literate Pandoc # Merges Fennel and Literate Pandoc
(echo "$LICENSE") > $DIST (echo "$LICENSE") > $DIST
head -n -1 opt/fennel.lua >> $DIST head -n -1 opt/fennel.lua >> $DIST
echo "local fennel = mod" >> $DIST echo "local fnl = mod" >> $DIST
tail -n +10 src/literate.lua >> $DIST head -n -1 src/natural.lua >> $DIST
head -n -1 src/literate.lua >> $DIST
cat src/pandoc.lua >> $DIST

View File

@ -1,32 +1,25 @@
# Makes tests # Makes tests
# Variables # Variables
NAME=literate.lua FILTER=dist/linp.lua
READER=src/$NAME
ARGS=()
# Removes unwanted args
for arg in "$@"; do
case $arg in
"--dist") READER=dist/$NAME ;;
*) ARGS+=($arg) ;;
esac
done
# Checks args # Checks args
if [ -z "$ARGS" ]; then if [ -z "$@" ]; then
echo "ERROR: At least one argument is needed. For example:" echo "ERROR: At least one argument is needed. For example:"
echo " sh $0 native" echo " sh $0 native"
echo " sh $0 native markdown" echo " sh $0 native markdown"
exit 1 exit 1
fi fi
# Makes distribution bundle
sh scripts/make_dist.sh
# Does tests # Does tests
clear && echo "🐾 Starting tests" clear && echo "🐾 Starting tests"
for arg in "$ARGS"; do for arg in "$@"; do
echo && echo "⚗️ Test in '$arg' format:" echo && echo "⚗️ Test in '$arg' format:"
mds=$'\n\n'`(pandoc -t markdown tests/*.md)` mds=$'\n\n'`(pandoc -t markdown tests/*.md)`
rst=$'\n\n'`(pandoc -t markdown tests/*.rst)` rst=$'\n\n'`(pandoc -t markdown tests/*.rst)`
org=$'\n\n'`(pandoc -t markdown tests/*.org)` org=$'\n\n'`(pandoc -t markdown tests/*.org)`
echo "$mds" "$rst" "$org" | pandoc -f $READER -t $arg echo "$mds" "$rst" "$org" | pandoc -L $FILTER -t $arg
done done

View File

@ -1,54 +1,40 @@
------------------------------------------------------------------------------- ----------------------------------- LITERATE ----------------------------------
-- IMPORTANT: these lines are for development setup
-- IMPORTANT: these lines are changed to "local fennel = mod"
-- Enables Fennel <https://fennel-lang.org> for Lisp-Lua compatibility
local src_root = pandoc.path.directory(PANDOC_SCRIPT_FILE)
local fennel_lua = pandoc.path.join({src_root, "../opt/fennel.lua"})
package.path = package.path .. ";" .. fennel_lua
local fennel = require("fennel")
-------------------------------------------------------------------------------
------------------------------- GRAMMAR related ------------------------------- -- TODO
local lit = {}
-- Ordered collection of fn call, fn declarations or literal elements -- Ordered collection of fn call, fn declarations or literal elements
local collection = {} lit.collection = {}
-- Lua LPeg shortcuts
local P, S, R, Cf, Cc, Ct, V, Cs, Cg, Cb, B, C, Cmt =
lpeg.P, lpeg.S, lpeg.R, lpeg.Cf, lpeg.Cc, lpeg.Ct, lpeg.V,
lpeg.Cs, lpeg.Cg, lpeg.Cb, lpeg.B, lpeg.C, lpeg.Cmt
-- Lexical elements -- Lexical elements
local space = S" \t\r\n" lit.space = lpeg.S" \t\r\n"
local spot = (1 - space) -- TMP lit.spot = (1 - lit.space)
local t1 = S"()" -- TMP lit.t1 = lpeg.S"()" -- TMP
local t2 = (1 - (t1 + space)) -- TMP lit.t2 = (1 - (lit.t1 + lit.space)) -- TMP
local function add_call(str) function lit.add_call(str)
table.insert(collection, {call = str}) table.insert(lit.collection, {call = str})
end end
local function add_declaration(str) function lit.add_declaration(str)
table.insert(collection, {declaration = str}) table.insert(lit.collection, {declaration = str})
end end
local function add_lit(str) function lit.add_lit(str)
table.insert(collection, {literal = str}) table.insert(lit.collection, {literal = str})
end end
-- Grammar -- Grammar
G = P { lit.G = lpeg.P {
"Doc"; "Doc";
Doc = (V"Call" + V"Declaration" + V"Literal")^0; Doc = (lpeg.V"Call" + lpeg.V"Declaration" + lpeg.V"Literal")^0;
Call = t1^1 / add_call; Call = lit.t1^1 / lit.add_call;
Declaration = t2^1 / add_declaration; Declaration = lit.t2^1 / lit.add_declaration;
Literal = space^1 / add_lit; Literal = lit.space^1 / lit.add_lit;
} }
--------------------------------- LNP related ---------------------------------
-- Evals Lisp code -- Evals Lisp code
local function eval(code) function lit.eval(code)
local is_passed, out = pcall ( local is_passed, out = pcall (
function () return fennel.eval(code) end, function () return fennel.eval(code) end,
function (e) return e end function (e) return e end
@ -62,28 +48,16 @@ local function eval(code)
return {is_passed = is_passed, preview = preview, out = out, lua = lua} return {is_passed = is_passed, preview = preview, out = out, lua = lua}
end end
function parse(_) function lit.parse(raw)
lpeg.match(lit.G, raw) -- TODO
local doc = "" local doc = ""
for i, t in ipairs(collection) do for i, t in ipairs(lit.collection) do
for k, v in pairs(t) do for k, v in pairs(t) do
print(i, k, v) -- print(i, k, v)
doc = doc .. v doc = doc .. v
end end
end end
return doc return doc
end end
function read(sources) return lit
raw = ""
for _, src in ipairs(sources) do
raw = raw .. src.text
end
return raw
end
-- Calls Pandoc Reader.
function Reader(sources, options)
local raw = read(sources)
local doc = parse(lpeg.match(G, raw))
return pandoc.read(doc, FORMAT, options)
end

5
src/natural.lua Normal file
View File

@ -0,0 +1,5 @@
----------------------------------- NATURAL -----------------------------------
local nat = {}
return natural

33
src/pandoc.lua Normal file
View File

@ -0,0 +1,33 @@
------------------------------------ PANDOC -----------------------------------
local function is_chosen(block)
chosen, content = true, block.content
if content == nil or pandoc.utils.type(content) ~= "Inlines" then
chosen = false
end
return chosen
end
local function sanitize(inlines)
for i, inline in ipairs(inlines) do
if inline.tag == "Code" then
inlines:remove(i)
elseif inline.tag == "Quoted" then
str = pandoc.utils.stringify(inline.content):gsub('"', '\\"')
inlines[i] = '"' .. str .. '"'
end
end
return pandoc.utils.stringify(inlines)
end
return {
{
Block = function (block)
if is_chosen(block) then
raw = sanitize(block.content)
doc = lit.parse(raw)
print(doc)
end
end,
}
}

View File

@ -26,6 +26,11 @@ Not declarations:
* f19 = (+ 1 2); misses `()` * f19 = (+ 1 2); misses `()`
* f20() = + 1 2; misses `()` * f20() = + 1 2; misses `()`
* f21() (+ 1 2); misses `=` * f21() (+ 1 2); misses `=`
* `f22() = (+ 1 2)`; inline code is ignored
Block code is ignored:
f23() = (+ 1 2)
Overrides `f1()` with warn: f1() = (+ 2 3 4) and it should fail on `--fail-if-warnings`. Overrides `f1()` with warn: f1() = (+ 2 3 4) and it should fail on `--fail-if-warnings`.
@ -75,6 +80,11 @@ Invalid calls:
* f9(1, b: 2); mixed arg and kwarg * f9(1, b: 2); mixed arg and kwarg
* f10(...: 0); `...` can't be kwarg * f10(...: 0); `...` can't be kwarg
* f11(); not declared * f11(); not declared
* `f22()`; inline code is ignored
Block code is ignored:
f23()
Invalid calls generate error. Invalid calls generate error.