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

View File

@ -1,6 +1,6 @@
--[[
Literate Pandoc & Fennel Bundle:
A Pandoc reader for literate and natural programming
A Pandoc filter for literate and natural programming
Fennel:
(C) 2016-2023 Calvin Rose and contributors
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
License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt
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(...)
local utils = require("fennel.utils")
@ -6258,33 +6257,48 @@ do
end
package.preload[module_name] = nil
end
local fennel = mod
local fnl = mod
----------------------------------- NATURAL -----------------------------------
-- 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
local nat = {}
----------------------------------- LITERATE ----------------------------------
-- TODO
local lit = {}
-- Ordered collection of fn call, fn declarations or literal elements
lit.collection = {}
-- Lexical elements
local space = S(" \t\r\n")
local lbracket = P"("
local rbracket = P")"
local word = (1 - (space + lbracket + rbracket))
lit.space = lpeg.S" \t\r\n"
lit.spot = (1 - lit.space)
lit.t1 = lpeg.S"()" -- TMP
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
G = P{
lit.G = lpeg.P {
"Doc";
Doc = space^0 * Ct(V"SExpr"^0) * space^0 / pandoc.Pandoc;
SExpr = (V"List" + V"Atom");
List = lbracket * V"SExpr"^0 * rbracket;
Atom = space + V"Word";
Word = word^1 / pandoc.Str;
Doc = (lpeg.V"Call" + lpeg.V"Declaration" + lpeg.V"Literal")^0;
Call = lit.t1^1 / lit.add_call;
Declaration = lit.t2^1 / lit.add_declaration;
Literal = lit.space^1 / lit.add_lit;
}
-- Evals Lisp code
-- @param code string: code to evaluate
-- @return table: evaluation result as {bool, string, string, string}
local function eval(code)
function lit.eval(code)
local is_passed, out = pcall (
function () return fennel.eval(code) 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}
end
-- Calls Pandoc Reader.
function Reader(input, options)
--[[
local doc = lpeg.match(G, tostring(input))
if options.standalone then
return exec(doc)
else
return doc
function lit.parse(raw)
lpeg.match(lit.G, raw) -- TODO
local doc = ""
for i, t in ipairs(lit.collection) do
for k, v in pairs(t) do
-- print(i, k, v)
doc = doc .. v
end
end
]]--
return pandoc.Pandoc(tostring(input))
return doc
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
# Variables
NAME="literate.lua"
NAME="linp.lua"
DIST=dist/$NAME
LICENSE="--[[
Literate Pandoc & Fennel Bundle:
A Pandoc reader for literate and natural programming
A Pandoc filter for literate and natural programming
Fennel:
(C) 2016-2023 Calvin Rose and contributors
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
License: GPLv3 https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt
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
(echo "$LICENSE") > $DIST
head -n -1 opt/fennel.lua >> $DIST
echo "local fennel = mod" >> $DIST
tail -n +10 src/literate.lua >> $DIST
echo "local fnl = mod" >> $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
# Variables
NAME=literate.lua
READER=src/$NAME
ARGS=()
# Removes unwanted args
for arg in "$@"; do
case $arg in
"--dist") READER=dist/$NAME ;;
*) ARGS+=($arg) ;;
esac
done
FILTER=dist/linp.lua
# Checks args
if [ -z "$ARGS" ]; then
if [ -z "$@" ]; then
echo "ERROR: At least one argument is needed. For example:"
echo " sh $0 native"
echo " sh $0 native markdown"
exit 1
fi
# Makes distribution bundle
sh scripts/make_dist.sh
# Does tests
clear && echo "🐾 Starting tests"
for arg in "$ARGS"; do
for arg in "$@"; do
echo && echo "⚗️ Test in '$arg' format:"
mds=$'\n\n'`(pandoc -t markdown tests/*.md)`
rst=$'\n\n'`(pandoc -t markdown tests/*.rst)`
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

View File

@ -1,54 +1,40 @@
-------------------------------------------------------------------------------
-- 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")
-------------------------------------------------------------------------------
----------------------------------- LITERATE ----------------------------------
------------------------------- GRAMMAR related -------------------------------
-- TODO
local lit = {}
-- Ordered collection of fn call, fn declarations or literal elements
local 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
lit.collection = {}
-- Lexical elements
local space = S" \t\r\n"
local spot = (1 - space) -- TMP
local t1 = S"()" -- TMP
local t2 = (1 - (t1 + space)) -- TMP
lit.space = lpeg.S" \t\r\n"
lit.spot = (1 - lit.space)
lit.t1 = lpeg.S"()" -- TMP
lit.t2 = (1 - (lit.t1 + lit.space)) -- TMP
local function add_call(str)
table.insert(collection, {call = str})
function lit.add_call(str)
table.insert(lit.collection, {call = str})
end
local function add_declaration(str)
table.insert(collection, {declaration = str})
function lit.add_declaration(str)
table.insert(lit.collection, {declaration = str})
end
local function add_lit(str)
table.insert(collection, {literal = str})
function lit.add_lit(str)
table.insert(lit.collection, {literal = str})
end
-- Grammar
G = P {
lit.G = lpeg.P {
"Doc";
Doc = (V"Call" + V"Declaration" + V"Literal")^0;
Call = t1^1 / add_call;
Declaration = t2^1 / add_declaration;
Literal = space^1 / add_lit;
Doc = (lpeg.V"Call" + lpeg.V"Declaration" + lpeg.V"Literal")^0;
Call = lit.t1^1 / lit.add_call;
Declaration = lit.t2^1 / lit.add_declaration;
Literal = lit.space^1 / lit.add_lit;
}
--------------------------------- LNP related ---------------------------------
-- Evals Lisp code
local function eval(code)
function lit.eval(code)
local is_passed, out = pcall (
function () return fennel.eval(code) 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}
end
function parse(_)
function lit.parse(raw)
lpeg.match(lit.G, raw) -- TODO
local doc = ""
for i, t in ipairs(collection) do
for i, t in ipairs(lit.collection) do
for k, v in pairs(t) do
print(i, k, v)
-- print(i, k, v)
doc = doc .. v
end
end
return doc
end
function read(sources)
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
return lit

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 `()`
* f20() = + 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`.
@ -75,6 +80,11 @@ Invalid calls:
* f9(1, b: 2); mixed arg and kwarg
* f10(...: 0); `...` can't be kwarg
* f11(); not declared
* `f22()`; inline code is ignored
Block code is ignored:
f23()
Invalid calls generate error.