Init
This commit is contained in:
commit
f37cf4bba4
|
@ -0,0 +1,157 @@
|
|||
--[[
|
||||
literate.lua
|
||||
(C) 2023 perro tuerto <hi@perrotuerto.blog>
|
||||
Code under GPLv3 <https://www.gnu.org/licenses/gpl-3.0.en.html>
|
||||
]]
|
||||
|
||||
-- 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
|
||||
local space = S(" \t\r\n")
|
||||
local lbracket = P"("
|
||||
local rbracket = P")"
|
||||
local word = (1 - (space + lbracket + rbracket))
|
||||
|
||||
-- Table of Lisp functions.
|
||||
-- The key is the operator and the value is the operator function.
|
||||
-- All functions 'e' are the operands, a list of Lisp atoms as pandoc.Inlines.
|
||||
-- Note: it doesn't support all the available Lisp functions, just the required
|
||||
-- for this exercise.
|
||||
local operators = {
|
||||
-- Lisp addition function.
|
||||
-- The operands are looped, converted to a number and added to the result.
|
||||
-- The result is a number that is converted to pandoc.Str because Pandoc
|
||||
-- structure only accepts Pandoc elements.
|
||||
["+"] = function (e)
|
||||
local result = 0
|
||||
e:walk {
|
||||
Str = function (str)
|
||||
local num = tonumber(pandoc.utils.stringify(str))
|
||||
result = result + num
|
||||
end
|
||||
}
|
||||
return pandoc.Str(result)
|
||||
end,
|
||||
-- Lisp list function.
|
||||
-- Operands are already in a list, so it returns the same.
|
||||
["list"] = function (e) return e end,
|
||||
-- Lisp first function.
|
||||
-- Since operands are already in a list, it returns the first element.
|
||||
["first"] = function (e) return e[1] end,
|
||||
}
|
||||
|
||||
-- Transforms lists to spans.
|
||||
-- Each Lisp list turns to Pandoc Span.
|
||||
-- @param ... pandoc.Inline: Lisp atoms are treated as Pandoc Inline.
|
||||
local function embed (...)
|
||||
local els = table.pack(...)
|
||||
return pandoc.Span(els)
|
||||
end
|
||||
|
||||
-- Evals Lisp list.
|
||||
-- The first list element is the operator for evaluation.
|
||||
-- @param list pandoc.Inlines: Lisp list as pandoc.Inlines.
|
||||
-- @return pandoc.Span or pandoc.Str: Evaluation result.
|
||||
local function eval_list(list)
|
||||
local operator = pandoc.utils.stringify(list[1])
|
||||
list:remove(1)
|
||||
-- When first atom is a number and there aren't other atoms.
|
||||
if operator:match("[%d]") and #list == 0 then
|
||||
return operator
|
||||
-- When first atom is not a number.
|
||||
-- Note: this assumes that the function is implemented in 'operators'.
|
||||
elseif operator:match("[%D]") then
|
||||
return operators[operator](list)
|
||||
-- Rest of cases.
|
||||
-- For example, when first atom is a number but it has other atoms.
|
||||
else
|
||||
print("ERROR: " .. operator .. " is not a function")
|
||||
sys.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Evals Lisp atom.
|
||||
-- When atom is in a list, it doesn't get affected; otherwise, all the
|
||||
-- non digit elements are eliminated.
|
||||
-- @param atom pandoc.Str: Lisp atom as pandoc.Str.
|
||||
-- @param in_list boolean: Indicates if atom is in a list or not.
|
||||
-- @return atom pandoc.Str: Lisp atom.
|
||||
local function eval_atom(atom, in_list)
|
||||
if not in_list then
|
||||
atom = pandoc.utils.stringify(atom)
|
||||
atom = atom:gsub("[%D]", "")
|
||||
-- Note: Pandoc content empty structures are ignored on conversion, this
|
||||
-- enables expressions like '+ 1 2' be '1 2'.
|
||||
atom = pandoc.Str(atom)
|
||||
end
|
||||
return atom
|
||||
end
|
||||
|
||||
-- Formats code.
|
||||
-- Adds and space between each pandoc.Str (Lisp atom).
|
||||
-- @param res pandoc.Plain: Lisp eval result.
|
||||
-- @return res string: Formatted result.
|
||||
local function format(res)
|
||||
local res = res:walk {
|
||||
Str = function (str) return pandoc.Inlines{str, pandoc.Space()} end
|
||||
}
|
||||
return pandoc.utils.stringify(res)
|
||||
end
|
||||
|
||||
-- Evals code.
|
||||
-- The code consists in a table of pandoc.Plain elements,
|
||||
-- each pandoc.Plain is a table of pandoc.Span or pandoc.Str,
|
||||
-- so the equivalency with Lisp is:
|
||||
-- pandoc.Plain => Lisp expression
|
||||
-- pandoc.Span => Lisp list
|
||||
-- pandoc.Str => Lisp atom
|
||||
-- According to the children Pandoc element of pandoc.Plain, this
|
||||
-- function deals with Lisp lists or Lisp atoms. The last walk is just for
|
||||
-- formatting, since it separates atoms with an space.
|
||||
-- @param code pandoc.Plain: Lisp expression as pandoc.Plain.
|
||||
-- @return res string: Result of evaluation.
|
||||
local function eval(code)
|
||||
local res = code:walk {
|
||||
Plain = function (plain)
|
||||
local in_span = plain.content[1].tag == "Span"
|
||||
return plain:walk {
|
||||
Span = function (span) return eval_list(span.content) end,
|
||||
Str = function (str) return eval_atom(str, in_span) end
|
||||
}
|
||||
end
|
||||
}
|
||||
return format(res)
|
||||
end
|
||||
|
||||
-- Grammar for Pandoc parser that converts:
|
||||
-- Lisp expression => pandoc.Plain
|
||||
-- Lisp list => pandoc.Span
|
||||
-- Lisp atom => pandoc.Str
|
||||
G = P{
|
||||
"Doc";
|
||||
Doc = space^0 * Ct(V"SExpr"^0) * space^0 / pandoc.Pandoc;
|
||||
SExpr = (V"List" + V"Atom");
|
||||
List = lbracket * V"SExpr"^0 * rbracket / embed;
|
||||
Atom = space + V"Word";
|
||||
Word = word^1 / pandoc.Str;
|
||||
}
|
||||
|
||||
return {
|
||||
{
|
||||
CodeBlock = function (block)
|
||||
if block.classes:includes("eval") then
|
||||
local raw = block.text
|
||||
local code = lpeg.match(G, raw)
|
||||
local res = eval(code)
|
||||
print("⚙️ ", raw)
|
||||
print("", "=>", res)
|
||||
if block.classes:includes("replace") then
|
||||
return pandoc.CodeBlock(res, {code=raw})
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
# Test 1
|
||||
|
||||
In this test, the following block code should not be executed in the terminal:
|
||||
|
||||
```
|
||||
:(){ :|:& };: # NEVER try to execute this
|
||||
```
|
||||
|
||||
But the following block code should be executed:
|
||||
|
||||
``` eval
|
||||
(first (list 1 (+ 2 3) 9))
|
||||
```
|
|
@ -0,0 +1,8 @@
|
|||
# Test 2
|
||||
|
||||
In this test, the following block code should be executed in the terminal, the
|
||||
code should be saved as an attribute, and the block should be the result:
|
||||
|
||||
``` {.numberLines .eval .replace}
|
||||
(list 1 (+ 2 3) 9)
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
# Variables
|
||||
DIR=`dirname -- "$0"`
|
||||
|
||||
# Moves to tests directory and clears the terminal
|
||||
cd $DIR
|
||||
clear
|
||||
|
||||
# Checks args
|
||||
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
|
||||
|
||||
# Does tests
|
||||
echo "🐾 Starting tests"
|
||||
for arg in "$@"; do
|
||||
echo && echo "⚗️ Test in '$arg' format:"
|
||||
pandoc --lua-filter ../src/literate.lua -t $arg src/*.md
|
||||
done
|
Loading…
Reference in New Issue