Code blocks are now eval by Fennel

This commit is contained in:
perro tuerto 2023-03-06 18:17:33 -08:00
parent 0b5213c078
commit a36006cee2
9 changed files with 6840 additions and 135 deletions

11
scripts/get_fennel.sh Normal file
View File

@ -0,0 +1,11 @@
# Variables
NAME="fennel-1.3.0"
URL="https://fennel-lang.org/downloads/$NAME"
ASC="https://fennel-lang.org/downloads/$NAME.asc"
DIR=$(git rev-parse --show-toplevel)
# Copies Fennel from release tarball
cd $DIR/src
curl -o fennel $URL
chmod +x fennel

View File

@ -1,8 +1,8 @@
# Variables
DIR=`dirname -- "$0"`
DIR=$(git rev-parse --show-toplevel)
# Moves to tests directory and clears the terminal
cd $DIR
cd $DIR/tests
clear
# Checks args
@ -17,8 +17,8 @@ fi
echo "🐾 Starting tests"
for arg in "$@"; do
echo && echo "⚗️ Test in '$arg' format:"
md=`cat src/*.md`
rst=$'\n'`(pandoc -t markdown src/*.rst)`
org=$'\n'`pandoc -t markdown src/*.org`
echo "$md" "$rst" "$org" | pandoc --lua-filter ../src/literate.lua -t $arg
md=`cat *.md`
rst=$'\n'`(pandoc -t markdown *.rst)`
org=$'\n'`pandoc -t markdown *.org`
echo "$md" "$rst" "$org" | pandoc --lua-filter $DIR/src/literate.lua -t $arg
done

6778
src/fennel Executable file

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,13 @@ literate.lua
Code under GPLv3 <https://www.gnu.org/licenses/gpl-3.0.en.html>
]]
-- Gets source root directory and Fennel path
-- This will allow to use Lisp inside Lua
-- Cfr. https://fennel-lang.org
local src_root = pandoc.path.directory(PANDOC_SCRIPT_FILE)
local fennel = pandoc.path.join({src_root, "fennel"})
--[[
-- 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,
@ -15,117 +22,6 @@ 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
@ -138,18 +34,32 @@ G = P{
Atom = space + V"Word";
Word = word^1 / pandoc.Str;
}
]]--
-- Evals Lisp code
-- @param code string: code to evaluate
-- @return table: code evaluated as a table of lines
local function eval(code)
local res = {}
local cmd = fennel .. " -e '" .. code .. "' 2>&1"
local handle = io.popen(cmd)
for line in handle:lines() do
table.insert(res, line)
end
handle:close()
return res
end
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)
local res = eval(raw)
print("", "=>", res[1])
if block.classes:includes("replace") then
return pandoc.CodeBlock(res, {code=raw})
return pandoc.CodeBlock(table.concat(res, "\n"), {code=raw})
end
end
end,

View File

@ -1,9 +0,0 @@
# Test 2
This is written in MD format.
Evals and replaces:
``` {.eval .replace}
(list 1 (+ 2 3) 9)
```

View File

@ -11,5 +11,5 @@ Does nothing:
Evals:
``` eval
(first (list 1 (+ 2 3) 9))
(+ 1 2 3)
```

15
tests/t2.md Normal file
View File

@ -0,0 +1,15 @@
# Test 2
This is written in MD format.
Evals and replaces:
``` {.eval .replace}
(.. "hello" " world")
```
Fails and replaces with error:
``` {.eval .replace}
(FAIL! "hello" " world")
```

View File

@ -8,11 +8,11 @@ Evals:
.. code::
:class: eval
(+ 2 3)
(+ 4 5 6)
Evals and replaces:
.. code::
:class: eval replace
(first (list 9))
(+ 4 5 6)

View File

@ -4,11 +4,11 @@ This is written in ORG format.
Evals:
#+begin_src eval
(+ 2 3 9)
(+ 7 8 9)
#+end_src
Fails:
#+begin_src eval replace
(list (+ 2 3 9))
(+ 7 8 9)
#+end_src