Compare commits

...

38 Commits

Author SHA1 Message Date
perro tuerto ee1d86dc86 Autoformat 2023-07-01 10:03:35 -07:00
perro tuerto 5b9d776dfb Fixed workflow with checks; added (incomplete) call function 2023-07-01 09:56:06 -07:00
perro tuerto efa6cd3b69 Update of lua-dog to 1.1.0-1 2023-06-16 19:58:43 -07:00
perro tuerto 86ed0ecb3e More cleaning/linting 2023-06-16 11:03:16 -07:00
perro tuerto 7dc8d75b6c Some cleaning/linting 2023-06-16 10:04:17 -07:00
perro tuerto 016f57ef1c Starting to checkcode 2023-06-15 17:42:23 -07:00
perro tuerto 1d6918d927 Fix: lit won't be a rock, but nat would be 2023-05-29 13:33:28 -07:00
perro tuerto eac9b36409 Distribution now are two: bundle and minimal 2023-05-25 12:56:03 -07:00
perro tuerto 86a4be358f Implemented luarocks for external modules 2023-05-12 16:23:33 -07:00
perro tuerto 26a62a3f25 Check meta done 2023-04-20 13:37:10 -07:00
perro tuerto 3b140b6953 checkcmd implemented 2023-04-19 11:33:35 -07:00
perro tuerto e7436e07a0 Finished refactoring 2023-04-17 17:59:58 -07:00
perro tuerto f2016b6654 Almost finish the refactoring: work needed on 1) pandoc.metatotable and 2) literate should work over doc not blocks 2023-04-07 13:50:35 -07:00
perro tuerto 162b413f7e Refactoring: all ready except lit.parse() 2023-04-05 12:07:24 -07:00
perro tuerto 01c19771e5 Refactoring: by lit.assert 2023-04-04 12:10:50 -07:00
perro tuerto 15be6fca8e Refactoring: nested functions 2023-04-03 11:39:11 -07:00
perro tuerto 8bdc6035a8 Bug fixed and more refactoring 2023-03-28 13:25:02 -07:00
perro tuerto 80892f0f28 Refactoring continue 2023-03-28 12:23:26 -07:00
perro tuerto 879abd7b84 Starting refactoring 2023-03-27 11:48:03 -07:00
perro tuerto 781a7920f6 More error analysis; refactoring of lit.puts 2023-03-24 12:08:26 -07:00
perro tuerto e353f5125f Finished scripts refactoring 2023-03-23 18:53:36 -07:00
perro tuerto 49ac3f53fc Scripts 'make_dist' and 'test' ready 2023-03-22 20:08:06 -07:00
perro tuerto ddb9c68e82 Almost all block validations 2023-03-20 18:28:32 -07:00
perro tuerto 2ec9e0bd4b Literate blocks parses; TODO: validation 2023-03-17 19:04:41 -07:00
perro tuerto f6f39ee093 Init parsing literate blocks 2023-03-16 17:00:24 -07:00
perro tuerto 6bd36cfc16 Aligned to current syntax 2023-03-16 15:42:44 -07:00
perro tuerto 841e1733d4 Tests fixed for new structure 2023-03-16 14:58:56 -07:00
perro tuerto f95db9f9f3 Renaming 2023-03-16 08:52:04 -07:00
perro tuerto 270a5ceb22 Ready for parsing functions 2023-03-15 16:33:53 -07:00
perro tuerto 30100855cf Finally test.sh again ready 2023-03-15 15:12:48 -07:00
perro tuerto e7db4df984 Tests ready 2023-03-14 14:46:51 -07:00
perro tuerto 328cca695b Tests changed 2023-03-14 14:40:43 -07:00
perro tuerto 37eb069a1b From reader to filter... again 2023-03-11 12:14:24 -08:00
perro tuerto a786c4f227 Dummy ready 2023-03-10 19:09:31 -08:00
perro tuerto 0ba2e10fc6 From filter to reader... again 2023-03-09 18:13:01 -08:00
perro tuerto bade83d6c1 README changes 2023-03-09 13:58:14 -08:00
perro tuerto 9e7412ab04 README changes 2023-03-09 13:53:18 -08:00
perro tuerto 854687c64a README changes 2023-03-09 13:52:03 -08:00
39 changed files with 15849 additions and 3331 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "opt/luaminify"]
path = opt/luaminify
url = https://github.com/stravant/LuaMinify.git

View File

@ -1,72 +1,98 @@
# Literate Pandoc
# 👾 Computable Pandoc
[Pandoc] is a world famous "swiss-army" document converted. This is because
Pandoc is also a document parser. Thanks to this capability, this repo is a
[Pandoc filter] written in [Lua] for [literate] and [natural] programming
(LNP), i.e.: ["Programming \[...\] as the process of creating works of
literature"][1].
Computable Pandoc is a [Pandoc filter] written in [Lua] for [literate] and
[natural] programming (LIN programming), i.e.: "[Programming \[...\] as the
process of creating works of literature][1]".
## Requirements
- [Pandoc]
- [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
Just download this repo:
git clone https://git.cuates.net/perro/literate-pandoc.git
1. Go to [Releases].
2. Download the latest version of `lin.bundle.lua` or `lin.min.lua`.
3. Done!
## Usage
With Pandoc installed and this repo downloaded, do:
With `lin.bundle.lua` or `lin.min.lua` downloaded and Pandoc installed, do:
pandoc --lua-filter PATH/TO/literate-pandoc/src/literate.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 --lua-filter PATH/TO/literate-pandoc/src/literate.lua -t html source.md
pandoc -L PATH/TO/lin.bundle.lua -t html source.md
## Manual
Learn how to do LNP [here].
Learn how to do LIN programming [here].
## Test
## Develop
Inside this repo, do:
Clone this repo:
sh scripts/test.sh FORMAT1 FORMAT2
git clone https://git.cuates.net/perro/computable-pandoc.git
For example, if `FORMAT1` is Markdwon and `FORMAT2` is HTML, do:
Enter the repo:
sh tests/test.sh markdown html
cd computable-pandoc
For distribution tests, do:
Inside, do the tests:
sh scripts/test.sh --dist FORMAT1 FORMAT2
pandoc lua scripts/test.lua
For other kind of tests, do:
pandoc lua scripts/test.lua -h
For make distribution filter, do:
pandoc lua scripts/make_dist.lua
For make JSON (intented for testing asserts), do:
pandoc lua scripts/make_jsons.lua
For update Lua modules (needs [`luarocks`]), do:
sh scripts/get_rocks.sh
Contribute!
## 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.
- [Fennel][]: [Lisp] dialect with full Lua compatibility; allows to go from
LNP to Lisp or Lua.
requirements for LIN.
- [Lua][]: programming language; enables LIN.
- [Fennel][2]: [Lisp] dialect with full Lua compatibility; allows native
evals for Lisp.
## License
Literate Pandoc is under [GPLv3].
Computable Pandoc is under [GPLv3].
Happy hacking :)
[Pandoc]: https://pandoc.org/
[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
[1]: https://web.archive.org/web/20170605163729/http://www.desy.de/user/projects/LitProg/Philosophy.html
[here]: https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/man/README.md
[Fennel]: https://fennel-lang.org
[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/
[2]: https://fennel-lang.org
[Lisp]: https://en.wikipedia.org/wiki/Lisp_(programming_language)
[GPLv3]: https://git.cuates.net/perro/literate-pandoc/src/branch/no-masters/LICENSE.txt
[GPLv3]: https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt

6989
dist/lin.bundle.lua vendored Normal file

File diff suppressed because one or more lines are too long

401
dist/lin.min.lua vendored Normal file
View File

@ -0,0 +1,401 @@
--[[
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 "nat"
local lua_dog = require("dog")
dog.import()
---------------------------------- LITERATE ----------------------------------
-- Stores all literate stuff
local lit = {}
-- Indicates status
lit.status = true
-- Stores literate functions
lit.fns = {}
-- Returns specific grammar
function lit.g(name)
-- Stores all grammars
local grammars = {}
-- 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 newline = P"\r"^-1 * P"\n"
local space = S" \t"
local anyspace = S" \t\r\n"
local spot = (1 - anyspace)
local any = (spot + space)
local yamlheader = space^0 * P"---" * space^0 * newline
local yamlfooter = space^0 * P"..." * space^0 * newline^-1
local yamlbody = -yamlfooter * any^0 * newline
local label = R("az", "AZ") * R("az", "AZ", "09", "__")^0
local ref = -B"\\" * P"#" * C(label)
local notref = (1 - ref)^0
-- Function declaration grammar
grammars["declaration"] = P {
"Declaration";
Declaration = Ct(V"YAML" * V"Code", "name");
YAML = C(yamlheader * yamlbody^0 * yamlfooter);
Code = C((any + newline)^0);
}
-- Argument references grammar
grammars["references"] = notref * P {
"References";
References = Ct((ref * notref)^0);
} * -1
return grammars[name]
end
-- Prints located messages
function lit.puts(yaml_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] 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(yaml_key, ...)
if level >= verbosity then
print("[" .. levelname .. "] [LIT] " .. msg)
end
end
-- Parses and evaluates literate functions in document
function lit.exam(doc)
-- Extracts function declarations
local function extract(codeblock)
-- Checks function declarations
local function check(match)
-- Stores valid declaration
local declaration = {}
-- 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
-- Eachs command is a table of table;
-- First table indicates one or more internal or external evaluations
-- Second table indicates one or more commands to try
local langcmds = {
["lua"] = {["int_eval"] = { "CMD1", "CMD2", } },
["fennel"] = {["int_eval"] = { "CMD1", "CMD2", } },
["python"] = {["ext_eval"] = { "CMD1", "CMD2", } },
["js"] = {["ext_eval"] = { "CMD1", "CMD2", } },
["ruby"] = {["ext_eval"] = { "CMD1", "CMD2", } },
["lisp"] = {["ext_eval"] = { "CMD1", "CMD2", } },
["graphviz"] = {["ext_eval"] = { "CMD1", "CMD2", } },
}
-- Prints error
-- Since error messages debuglevel are "ERROR", lit.status turns false
-- Flush the declaration to avoid storage in lit.fns
local function putserr(...)
lit.puts(...)
declaration = {}
end
-- Adds 'eval' key in declaration
-- This key indicates what to eval
local function addeval()
declaration["eval"] = {}
-- If lang and cmd are present, ignores lang and use cmd as eval
if declaration["lang"] and declaration["cmd"] then
lit.puts("ignore_lang", declaration["cmd"], declaration["lang"])
declaration["eval"] = {["ext_eval"] = { declaration["cmd"] } }
-- If neither lang or cmd are presents, defaults lang and eval to lua
elseif not(declaration["lang"]) and not(declaration["cmd"])then
declaration["lang"] = "lua"
declaration["eval"] = langcmds["lua"]
-- If only lang present, eval is lang
elseif declaration["lang"] and not(declaration["cmd"]) then
declaration["eval"] = langcmds[declaration["lang"]]
-- If only cmd present, eval is cmd
elseif not(declaration["lang"]) and declaration["cmd"] then
declaration["eval"] = {["ext_eval"] = { declaration["cmd"] } }
end
end
-- Checks for valid command for Unix systems
local function checkcmd()
if declaration["cmd"] then
local cmd = declaration["cmd"]:split()[1]
if os.isunix() then
local 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, unwanted and wrong metadata
local function checkextra()
for key, _ in pairs(declaration) 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(kind)
-- Checks for valid metadata type
local function checktype(type1, key)
local val = declaration[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, key)
local val = tostring(declaration[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 declaration[key] == nil and kind == "mandatory" then
putserr("no_key", key)
elseif declaration[key] and type(val) == "table" then
checkpattern(val, key)
elseif declaration[key] then
checktype(val, key)
end
end
end
-- Parses metadata
local function parsemeta(yaml)
local isok, res = pcall(pandoc.read, yaml)
if isok and not(pandoc.utils.stringify(res.meta):isempty()) then
declaration = pandoc.metatotable(res.meta)
checkmeta("mandatory")
checkmeta("optional")
checkextra()
checkcmd()
if next(declaration) then addeval() end
elseif isok and pandoc.utils.stringify(res.meta):isempty() then
putserr("meta_empty")
else
putserr("meta_invalid")
end
end
-- TODO
-- Parses code
local function parsecode(rawcode)
local code = ""
local references = lpeg.match(lit.g("references"), rawcode)
if references then
for _, ref in ipairs(references) do
print("reference:", ref)
end
end
declaration["code"] = code
end
if match then
lit.puts("checking", table.concat(match, ""):indent())
parsemeta(match[1])
if next(declaration) then parsecode(match[2]) end
end
return declaration
end
local parsed = check(lpeg.match(lit.g("declaration"), codeblock.text))
if next(parsed) then
-- TODO:
-- Eval parsed lit.fns and modify code block accordingly
lit.fns[parsed["id"]] = parsed
end
return codeblock
end
-- Calls literate functions
local function call(el)
-- TODO
-- Calls literate functions in code inlines
local function codecall(code)
return pandoc.Str("EVAL: " .. pandoc.utils.stringify(code))
end
local function metacall(meta)
local function inlinescall(inlines)
return inlines:walk {
Code = function(code) return codecall(code) end
}
end
for key, val in pairs(meta) do
if pandoc.utils.type(val) == "Inlines" then
meta[key] = inlinescall(val)
elseif pandoc.utils.type(val) == "List" then
for i, item in ipairs(val) do
if pandoc.utils.type(item) == "Inlines" then
meta[key][i] = inlinescall(item)
end
end
end
end
return meta
end
if pandoc.utils.type(el) == "Meta" then
return metacall(el)
else
return codecall(el)
end
end
-- Asserts literate status
local function assert()
if not(lit.status) then
lit.puts("aborted")
os.exit(1)
end
end
doc = doc:walk { CodeBlock = function(block) return extract(block) end }
doc = doc:walk { Meta = function(meta) return call(meta) end }
doc = doc:walk { Code = function(code) return call(code) end }
return doc:walk { Pandoc = function(_) assert() end }
end
------------------------------------ PANDOC -----------------------------------
return {
-- Parses and evals literate programming
{ Pandoc = function(e) return lit.exam(e) end },
-- TODO Parses and evals natural programming
-- { Inlines = function(e) return nat.get(pandoc.utils.stringify(e)) end },
}

2957
dist/literate.min.lua vendored

File diff suppressed because one or more lines are too long

View File

@ -1,66 +0,0 @@
# Test 1
With Literate Pandoc you can write code as you write everything else! For
example, this is a function declaration: f() = (+ 1 2 3).
Every function declaration has:
1. A *function name* consisting in:
1. One or more alphanumeric characters following by a opening parenthesis
(i.e. `%w+(`).
2. Optional arguments (args).
3. Closing parenthesis (`)`).
2. A *function assignment* indicated with the equal sign (`=`).
3. A *function body* composed by Lisp code between parentheses.
Any function can be called at any time by is function name, for example now
we call f()! This also apply for function that haven't been defined, like
foo(1).
The functions can contain args, for example:
* foo(n) = (* #n #n) is a function declaration with the arg `n`; any arg can
be used in the function body if it is prefixed with a hashtag (`#n`).
* They can contain more args like bar(a, b) = (- a b).
* For a variable number of args the `...` symbol is used: baz(...) =
(.. #...). You can call baz() or baz('hello', ' ', 'world!').
All args can be treated as keyword args (kwargs), so foo(3) can be foo(n: 3),
or bar(2, 1) can be bar(b: 1, a: 2).
With kwargs you can declare args in any order and makes args more readable. The
trade-off is that they are verbose. Also, `...` arg can't be use as kwarg!
Calling f(), foo(2), bar(4, 3), baz(':', ')') will evaluate the functions and
will print the output during Pandoc conversion, but, what if we want to...
1. ...write the evaluation result instead of the function call?
For example, see `6` instead of `f()`, or `:)` instead of `baz(':', ')')`.
2. ...remove the function call? So you don't see `f()` anymore.
3. ...avoid evaluation?
4. ...write the evaluation result to a file?
5. ...write the evaluated code to a file?
6. ...write Lisp code as Lua code to a file?
Well, we can do that with *reserved kwargs*. All reserved kwargs are optional
(you don't declare them) and start with underscore (`_`):
* `_action`: indicates action to perfom with the function call; can be:
* `'flip'`: puts its evaluation result instead;
* `'wipe'`: deletes it from source document but still calls it;
* `'skip'`: doesn't perform call;
* `'kill'`: doesn't perform call and deletes it from source.
* `_eval`: saves evaluation to file; takes file path string as value.
* `_code`: saves code to file; takes file path string as value.
* `_lua`: converts Lisp code to Lua and saves it to file; takes file path
string as value.
With reserved kwargs, we can finally replace f() by its evaluation result like
this: f(_action: 'flip'); or skip its evaluation: f(_action: 'skip').
We can also save different files:
* The avaluation results of foo(4, _eval: 'results.txt') and bar(5, 4, _eval:
'results.txt').
* The code of foo(4, _code: 'code.lisp') and bar(5, 4, _code: 'code.lisp').
* The Lua code of foo(4, _lua: 'code.lua') and bar (5, 4, _lua: 'code.lua').

View File

3
opt/bin/fennel Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
LUAROCKS_SYSCONFDIR='/etc/luarocks' exec '/usr/bin/lua5.4' -e 'package.path="/home/perro/Repositorios/computable-pandoc/opt/share/lua/5.4/?.lua;/home/perro/Repositorios/computable-pandoc/opt/share/lua/5.4/?/init.lua;"..package.path;package.cpath="/home/perro/Repositorios/computable-pandoc/opt/lib/lua/5.4/?.so;"..package.cpath;local k,l,_=pcall(require,"luarocks.loader") _=k and l.add_context("fennel","1.3.0-1")' '/home/perro/Repositorios/computable-pandoc/opt/lib/luarocks/rocks-5.4/fennel/1.3.0-1/bin/fennel' "$@"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
MIT License
Copyright © 2016-2022 Calvin Rose and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,129 @@
# Fennel
[Fennel][1] is a lisp that compiles to Lua. It aims to be easy to use,
expressive, and has almost zero overhead compared to writing Lua directly.
* *Full Lua compatibility* - You can use any function or library from Lua.
* *Zero overhead* - Compiled code should be just as efficient as hand-written Lua.
* *Compile-time macros* - Ship compiled code with no runtime dependency on Fennel.
* *Embeddable* - Fennel is a one-file library as well as an executable. Embed it in other programs to support runtime extensibility and interactive development.
At [https://fennel-lang.org][1] there's a live in-browser repl you can
use without installing anything. At [https://fennel-lang.org/see][3]
you can see what Lua output a given piece of Fennel compiles to, or
what the equivalent Fennel for a given piece of Lua would be.
## Documentation
* The [setup](setup.md) guide is a great place to start
* The [tutorial](tutorial.md) teaches the basics of the language
* The [rationale](rationale.md) explains the reasoning of why Fennel was created
* The [reference](reference.md) describes all Fennel special forms
* The [macro guide](macros.md) explains how to write macros
* The [API listing](api.md) shows how to integrate Fennel into your codebase
* The [style guide](style.md) gives tips on how to write clear and concise code
* The [Lua primer](lua-primer.md) gives a very brief intro to Lua with
pointers to further details
For more examples, see [the cookbook][2] on [the wiki][7].
The [changelog](changelog.md) has a list of user-visible changes for
each release.
## Example
#### Hello World
```Fennel
(print "hello, world!")
```
#### Fibonacci sequence
```Fennel
(fn fib [n]
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(print (fib 10))
```
## Building Fennel from source
Building Fennel from source allows you to use versions of Fennel that
haven't been released, and it makes contributing to Fennel easier.
### To build Fennel from source
1. `cd` to a directory in which you want to download Fennel, such as
`~/src`
2. Run `git clone https://git.sr.ht/~technomancy/fennel`
3. Run `cd fennel`
4. Run `make fennel` to create a standalone script called `fennel`
5. Copy or link the `fennel` script to a directory on your `$PATH`, such as `/usr/local/bin`
**Note**: If you copied the `fennel` script to one of the
directories on your `$PATH`, then you can run `fennel filename.fnl` to
run a Fennel file anywhere on your system.
## Differences from Lua
* Syntax is much more regular and predictable (no statements; no operator precedence)
* It's impossible to set *or read* a global by accident
* Pervasive destructuring anywhere locals are introduced
* Clearer syntactic distinction between sequential tables and key/value tables
* Separate looping constructs for numeric loops vs iterators instead of overloading `for`
* Opt-in mutability for local variables
* Opt-in nil checks for function arguments
* Pattern matching
* Ability to extend the syntax with your own macros
## Differences from other lisp languages
* Its VM can be embedded in other programs with only 180 kB
* Access to [excellent FFI][4]
* LuaJIT consistently ranks at the top of performance shootouts
* Inherits aggressively simple semantics from Lua; easy to learn
* Lua VM is already embedded in databases, window managers, games, etc
* Low memory usage
* Readable compiler output resembles input
## Why not Fennel?
Fennel inherits the limitations of the Lua runtime, which does not offer
pre-emptive multitasking or OS-level threads. Libraries for Lua work
great with Fennel, but the selection of libraries is not as extensive
as it is with more popular languages. While LuaJIT has excellent
overall performance, purely-functional algorithms will not be as
efficient as they would be on a VM with generational garbage collection.
Even for cases where the Lua runtime is a good fit, Fennel might not
be a good fit when end-users are expected to write their own code to
extend the program, because the available documentation for learning
Lua is much more readily-available than it is for Fennel.
## Resources
* Join the `#fennel` chat [thru IRC on Libera.Chat][9] or [on Matrix][10]
* The [mailing list][5] has slower-paced discussion and announcements
* Report issues on the mailing list, [Sourcehut todo][11] or [Github issues][12]
* You can browse and edit [the Wiki][7]
* View builds in Fennel's [continuous integration][8]
* Community interactions are subject to the [code of conduct](CODE-OF-CONDUCT.md).
## License
Copyright © 2016-2023 Calvin Rose and contributors
Released under the [MIT license](LICENSE).
[1]: https://fennel-lang.org
[2]: https://wiki.fennel-lang.org/Cookbook
[3]: https://fennel-lang.org/see
[4]: http://luajit.org/ext_ffi_tutorial.html
[5]: https://lists.sr.ht/%7Etechnomancy/fennel
[7]: https://wiki.fennel-lang.org/
[8]: https://builds.sr.ht/~technomancy/fennel
[9]: https://libera.chat
[10]: https://matrix.to/#/!rnpLWzzTijEUDhhtjW:matrix.org?via=matrix.org
[11]: https://todo.sr.ht/~technomancy/fennel
[12]: https://github.com/bakpakin/Fennel/issues

View File

@ -0,0 +1,8 @@
package = "fennel"
local fennel_version = "1.3.0"
version = (fennel_version .. "-1")
source = {url = ("https://fennel-lang.org/downloads/fennel-" .. fennel_version .. ".tar.gz")}
description = {summary = "A lisp that compiles to Lua", detailed = ("Get your parens on--write macros and " .. "homoiconic code on the Lua runtime!"), license = "MIT", homepage = "https://fennel-lang.org/"}
dependencies = {"lua >= 5.1"}
build = {type = "builtin", install = {bin = {fennel = "fennel"}}, modules = {fennel = "fennel.lua"}}
return nil

View File

@ -0,0 +1,13 @@
rock_manifest = {
bin = {
fennel = "18b74a4083bcb33ebf161b339c82bf5f"
},
doc = {
LICENSE = "3e5a2c7152ee8b5c8d34815a16195a52",
["README.md"] = "a7f18dbb5131d2b23aad4fb1b55a31a6"
},
["fennel-1.3.0-1.rockspec"] = "c7300e84d68e04c9c2f900c685458024",
lua = {
["fennel.lua"] = "7e6bcb21700f66be5c027f3994d645de"
}
}

View File

@ -0,0 +1,30 @@
# Lua Dog
Adds functions to Lua Standard Libraries and Pandoc Library.
## Install
luarocks install lua-dog
## Usage
You can use Lua Dog in 2 ways.
The laziest way is:
local dog = require("dog")
dog.import()
-- Lua Dog 'dog.os.uname()' function was imported as 'os.uname()'
os.uname()
This will import all Lua Dog functions to the Lua Standard Libraries and Pandoc Library.
If you prefer not to import, the way is:
require "dog"
-- Lua Dog 'dog.os.uname()' function was NOT imported as 'os.uname()'
dog.os.uname()
## Functions
Check `src`.

View File

@ -0,0 +1,23 @@
package = "lua-dog"
version = "1.1.0-1"
source = {
url = "https://gitlab.com/perritotuerto/codigo/lua-dog/-/archive/v" ..
version .. "/lua-dog-v" .. version .. ".tar.gz",
}
description = {
summary = "Lua extensions for lazy dogs",
detailed = [[
Extends Lua Standard Libraries and Pandoc Library.
]],
homepage = "https://gitlab.com/perritotuerto/codigo/lua-dog/",
license = "GPLv3",
}
dependencies = {
"lua >= 5.4, < 5.5"
}
build = {
type = "builtin",
modules = {
dog = "src/dog.lua"
}
}

View File

@ -0,0 +1,9 @@
rock_manifest = {
doc = {
["README.md"] = "37185e33a737c1f449075b9ca3da5323"
},
lua = {
["dog.lua"] = "6c0d509614d5de3254d649ba0a75c6ee"
},
["lua-dog-1.1.0-1.rockspec"] = "a979848880cdf078b2c98ce87a37f15a"
}

View File

@ -0,0 +1,79 @@
commands = {
fennel = {
"fennel/1.3.0-1"
}
}
dependencies = {
fennel = {
["1.3.0-1"] = {
{
constraints = {
{
op = ">=",
version = {
5, 1, string = "5.1"
}
}
},
name = "lua"
}
}
},
["lua-dog"] = {
["1.1.0-1"] = {
{
constraints = {
{
op = ">=",
version = {
5, 4, string = "5.4"
}
},
{
op = "<",
version = {
5, 5, string = "5.5"
}
}
},
name = "lua"
}
}
}
}
modules = {
dog = {
"lua-dog/1.1.0-1"
},
fennel = {
"fennel/1.3.0-1"
}
}
repository = {
fennel = {
["1.3.0-1"] = {
{
arch = "installed",
commands = {
fennel = "fennel"
},
dependencies = {},
modules = {
fennel = "fennel.lua"
}
}
}
},
["lua-dog"] = {
["1.1.0-1"] = {
{
arch = "installed",
commands = {},
dependencies = {},
modules = {
dog = "dog.lua"
}
}
}
}
}

@ -1 +0,0 @@
Subproject commit cce8f5b51bdffd1e0439956ccc308d0e5d164f90

331
opt/share/lua/5.4/dog.lua Normal file
View File

@ -0,0 +1,331 @@
-- Variable for storing all the functions
local dog = {}
-- Functions are divided according to the standard library to extend
dog.io = {}
dog.os = {}
dog.string = {}
dog.utf8 = {}
-- Imports dog functions to _G
-- Allows using the other functions without a prefix, i.e.:
-- dog.os.uname() => os.uname()
function dog.import()
local msg = "[WARNING] [DOG] "
msg = msg .. "Ignoring import: '@1' already exists; use '@2' instead"
for libkey, dogfns in pairs(dog) do
local lib = _G[libkey]
if type(dogfns) == "table" then
for fnkey, fn in pairs(dogfns) do
if not(lib[fnkey]) then
lib[fnkey] = fn
else
local name = libkey .. "." .. fnkey .. "()"
msg = msg:gsub("@1", name):gsub("@2", "dog." .. name)
print(msg)
end
end
end
end
end
-- Tries popen
-- @param ... string: Chunks for popen
-- @return boolean, string: Status and output of popen
function dog.io.try(...)
local cmd = table.concat({...}, " ") .. " 2>&1"
local handle = io.popen(cmd)
local output = handle:read("*a")
local status = (handle:close() ~= nil and true or false)
return status, output
end
-- Gets OS short name
-- It is just intented to know if it is Linux, macOS or Windows.
-- @return string: "linux" or "bsd" or "macos" or "windows"
function dog.os.uname()
local status, output = dog.io.try("uname")
if status then
output = output:gsub(" .*", ""):lower()
if output ~= "linux" and output:match("bsd") ~= nil then
return "bsd"
elseif output ~= "linux" then
return "macos"
end
return "linux"
end
return "windows"
end
-- Checks if OS is windows
-- @return boolean: Windows or not
function dog.os.iswin()
return dog.os.uname() == "windows"
end
-- Checks if OS is Unix
-- @return boolean: Unix or not
function dog.os.isunix()
return dog.os.uname() ~= "windows"
end
-- Gets OS language
-- @return string, string, string: Language, locale and encoding
function dog.os.lang()
local lang = os.getenv("LANG")
if lang ~= nil then
return lang:match("(%w%w)_?(%w?%w?)%.?(.*)")
end
return "en", "US", "UTF-8"
end
-- Checks if string is empty
-- @return boolean: Empty or not
function dog.string.isempty(str)
return str == ''
end
-- Changes newlines so everything is in one line
-- @return string: String with formatted newlines as literal "\n"
function dog.string.linearize(str)
return str:gsub("\n", "\\n")
end
-- Normalizes string
-- @return string: Normalized string
function dog.string.normalize(str)
str = str:lower()
for newchar, chars in pairs(dog.utf8.table()) do
for _, oldchar in ipairs(chars) do
str = str:gsub(oldchar, newchar)
end
end
return str
end
-- Adds indent
-- @param num number: Indent size, 2 by default
-- @param char string: Indent character, space by default
-- @return strin: Indented string
function dog.string.indent(str, num, char)
num = num or 2
char = char or " "
char = string.rep(char, num)
return char .. str:gsub("\n", "\n" .. char)
end
-- Splits the string
-- Note: only support splitting by one character.
-- Could be solved by slicing with find.
-- @param sep string: String separator, space by default
-- @return table: String matches
function dog.string.split(str, sep)
sep = sep or "%s+"
local parts = {}
for part in str:gmatch("([^" .. sep .. "]+)") do
table.insert(parts, part)
end
return parts
end
-- Removes spaces at the beginning of the string
-- @return string: Left stripped string
function dog.string.lstrip(str)
return str:gsub("^%s+", "")
end
-- Removes spaces at the end of the string
-- @return string: Right stripped string
function dog.string.rstrip(str)
return str:gsub("%s+$", "")
end
-- Removes spaces at the beginning and at the end of the string
-- @return string: Stripped string
function dog.string.strip(str)
str = dog.string.lstrip(str)
return dog.string.rstrip(str)
end
-- Alias of strip
function dog.string.trim(str)
return dog.string.strip(str)
end
-- The following are heavily influenced by Python pathlib
-- Check: https://docs.python.org/3/library/pathlib.html
-- Checks if string is a file or a directory
-- @return boolean: Exists or not
function dog.string.exists(str)
return os.rename(str, str) ~= nil
end
-- Checks if string is a file
-- @return boolean: File or not
function dog.string.isfile(str)
if dog.string.exists(str) then
return io.open(str, "a+") ~= nil
end
return false
end
-- Checks if string is a directory
-- @return boolean: Directory or not
function dog.string.isdir(str)
if dog.string.exists(str) then
return io.open(str, "a+") == nil
elseif str == "." or str == ".." then
return true
end
return false
end
-- Reads file content as string
-- @return string or nil: File as string or nil
function dog.string.readtext(str)
if dog.string.exists(str) then
return io.open(str):read("*a")
end
end
-- Read file content as lines
-- @return table: Table of file lines or nil
function dog.string.readlines(str)
local lines = {}
if dog.string.exists(str) then
for line in io.open(str):lines() do
table.insert(lines, line)
end
end
return lines
end
-- Gets file without suffix
-- @return string: File wihtout suffix
function dog.string.stem(str)
return str:gsub("%.%a+$", "")
end
-- Gets file extensions
-- @return table: List of file extensions
function dog.string.suffixes(str)
local suffixes = {}
for suffix in str:gmatch("%.%a+") do
table.insert(suffixes, suffix)
end
return suffixes
end
-- Gets file final extension
-- @return string: Final file extension
function dog.string.suffix(str)
local suffixes = str:suffixes()
if suffixes[#suffixes] then
return suffixes[#suffixes]
end
return ""
end
-- Gets an equivalency table between ASCII and Unicode
-- Note: filled on demand.
-- @return table: ASCII-Unicode table equivalency
function dog.utf8.table()
return {
["a"] = {"á", "à", "ä"},
["e"] = {"é", "è", "ë"},
["i"] = {"í", "ì", "ï"},
["o"] = {"ó", "ò", "ö"},
["u"] = {"ú", "ù", "ü"},
["n"] = {"ñ"},
}
end
-- Extends Pandoc Library
-- Check: https://pandoc.org/lua-filters.html#module-pandoc
if pandoc ~= nil then
-- Gets file extension namespace
-- Check: https://pandoc.org/MANUAL.html#general-options
-- If suffix is 'md' or empty, the default is markdown.
-- For the rest, the format is the suffix.
-- @param file string: File name
-- @return string: File extension according to Pandoc format namespaces
function pandoc.getext(file)
local ext = dog.string.suffix(file):gsub("^.", "")
return ((ext == "md" or dog.string.isempty(ext)) and "markdown" or ext)
end
-- Pandoc converter
-- Converts input file name into output format name.
-- If ofile is not nil, writes output file name.
-- If iformat is nil, defaults to ifile extension name.
-- @param ifile string: Input file name
-- @param oformat string: Output format name
-- @param ofile string or nil: Output file name
-- @param iformat string or nil: Input format name
-- @return string: Output file content
function pandoc.convert(ifile, oformat, ofile, iformat)
iformat = (iformat == nil and pandoc.getext(ifile) or iformat)
local itext = pandoc.read(dog.string.readtext(ifile), iformat)
local doc = pandoc.write(itext, oformat)
if ofile ~= nil then
local eol = (dog.os:isunix() and "\n" or "\r\n")
io.open(ofile, "w"):write(doc, eol):close()
end
return doc
end
-- Stringifies Pandoc content
-- Avoids undesired behavios of pandoc.utils.stringify, such as quotes and
-- backslashes conversions.
-- @param content pandoc.MetaValue: Pandoc content value
-- @return string: Pandoc stringified value
function pandoc.utils.rawstringify(content)
return pandoc.utils.stringify(content:walk {
Plain = function(plain)
table.insert(plain.content, pandoc.Space())
return plain
end,
Quoted = function(quoted)
local q = (quoted.quotetype == SingleQuoted and '"' or "'")
return pandoc.Str(q .. pandoc.utils.stringify(quoted.content) .. q)
end,
RawInline = function(rawinline)
return pandoc.Str(rawinline.text:gsub("\\n", "\n"):gsub("\\t", "\t"))
end,
SoftBreak = function(_)
return pandoc.Str("\n")
end,
Inline = function(inline) return pandoc.utils.stringify(inline) end,
})
end
-- Converts pandoc.Meta to table
-- Note that stringified numbers, such "1", are converted to numbers because
-- Pandoc doesn't make distinctions between strings and numbers.
-- Check: https://pandoc.org/lua-filters.html#type-metavalue
-- @param meta pandoc.Meta: Pandoc MetaValues collection
-- @return table: Pandoc metadata as table
function pandoc.metatotable(meta)
local newmeta = {}
for k, v in pairs(meta) do
if pandoc.utils.type(v) == "table" then
newmeta[k] = pandoc.metatotable(v)
else
if type(v) == "boolean" then
newmeta[k] = v
elseif tonumber(v) then
newmeta[k] = tonumber(v)
else
newmeta[k] = pandoc.utils.rawstringify(v)
end
end
end
return newmeta
end
end
return dog

View File

@ -1,11 +0,0 @@
# Variables
NAME="fennel-1.3.0"
ROOT=$(git rev-parse --show-toplevel)
URL="https://fennel-lang.org/downloads/$NAME.tar.gz"
# Copies Fennel from release tarball
cd $ROOT/opt
curl -O $URL
tar -xvzf $NAME.tar.gz
mv $NAME/fennel.lua .
rm -rf $NAME*

16
scripts/get_rocks.sh Normal file
View File

@ -0,0 +1,16 @@
# Downloads Lua Modules
# Variables
ROOT=$(dirname $0)/..
# Goes to root directory
cd $ROOT
# Gets modules from luarocks
if type luarocks > /dev/null 2>&1; then
luarocks install fennel --tree opt
luarocks install lua-dog --tree opt
else
echo "ERROR: luarocks is required; see <https://luarocks.org/>"
exit 1
fi

63
scripts/make_dist.lua Normal file
View File

@ -0,0 +1,63 @@
-- Makes bundle and min distribution
-- Adds local luarocks modules
local optpath = "./opt/share/lua/5.4/"
package.path = optpath .. "?.lua;" .. package.path
-- Adds Lua custom extensions
local lua_dog = require("dog")
lua_dog.import()
-- Makes distribution
local function make_dist(name, bundle)
-- Chomps file
local function chomp(str)
str = string.readtext(str):strip()
return "\n" .. str .. "\n"
end
-- Defaults to true
bundle = (bundle == nil or bundle == true)
-- Variables
local dist = pandoc.path.join({"dist", name})
local fnl = chomp(optpath .. "fennel.lua"):gsub("\nreturn mod\n", "\nlocal fnl = mod\n")
local dog = chomp(optpath .. "dog.lua"):gsub("\nreturn dog\n", "")
local nat = chomp("src/natural.lua"):gsub("\nreturn nat\n", "")
local lit = chomp("src/literate.lua"):gsub("`locale%(%)`", ("src/locale.yaml"):readtext())
local pan = chomp("src/pandoc.lua")
local license = string.strip([[
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
Source: https://sr.ht/~technomancy/fennel or https://github.com/bakpakin/Fennel/issues
Website: https://fennel-lang.org
]])
-- Bundles Fennel and Computable Pandoc
local file = io.open(dist, "w")
if bundle then
file:write("--[[\n", extralicense, "\n]]--\n")
file:write(fnl, dog, nat)
else
file:write("--[[\n", license, "\n]]--\n")
file:write('\nrequire "fennel"\nrequire "nat"\n')
file:write('\nlocal lua_dog = require("dog")\n')
end
file:write('\ndog.import()\n')
file:write(lit, pan)
file:close()
end
make_dist("lin.bundle.lua")
make_dist("lin.min.lua", false)

View File

@ -1,32 +0,0 @@
# Variables
NAME="literate.min.lua"
ROOT=$(git rev-parse --show-toplevel)
DIST=$ROOT/dist/$NAME
LICENSE="--[[
Literate Pandoc & Fennel Bundle:
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
Source: https://sr.ht/~technomancy/fennel or https://github.com/bakpakin/Fennel/issues
Website: https://fennel-lang.org
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
head -n -1 $ROOT/opt/fennel.lua > $DIST
echo "local fennel = mod" >> $DIST
tail -n +11 $ROOT/src/literate.lua >> $DIST
echo "Bundling complete"
# Minifies code
cd opt/luaminify
lua CommandLineMinify.lua $DIST $DIST.tmp
# Adds license
(echo "$LICENSE" && cat $DIST.tmp) > $DIST
rm $DIST.tmp

24
scripts/make_jsons.lua Normal file
View File

@ -0,0 +1,24 @@
-- Makes JSON test files
-- Adds local luarocks modules
package.path = "./opt/share/lua/5.4/?.lua;" .. package.path
-- Adds Lua custom extensions
local lua_dog = require("dog")
lua_dog.import()
-- Gets command according to OS
local function getcmd()
if os:isunix() then
return "ls"
else
return "dir"
end
end
-- Makes JSON from markup files
for file in io.popen(getcmd() .. " tests/asts"):lines() do
local json = pandoc.path.join({"tests", "asts", file})
local mark = pandoc.path.join({"tests", pandoc.path.filename(json:stem())})
pandoc.convert(mark, "json", json)
end

109
scripts/test.lua Normal file
View File

@ -0,0 +1,109 @@
-- Makes tests
-- Adds local luarocks modules
package.path = "./opt/share/lua/5.4/?.lua;" .. package.path
-- Adds Lua custom extensions
require "scripts.make_dist"
-- Variables
local filter = "dist/lin.bundle.lua"
local verbose = ""
local trace = ""
local files = {}
local cmds = {
["unix"] = {
["pandoc"] = "pandoc",
["clear"] = "clear",
["ls"] = "ls",
},
["win"] = {
["pandoc"] = "pandoc.exe",
["clear"] = "cls",
["ls"] = "dir",
},
}
-- Prints help
local function help()
local cmd = " pandoc lua script/test.lua"
print(table.concat({
"Usage:",
cmd .. " [OPTIONS] [FILES]",
"Options",
" -V --verbose Prints verbose debbuging output",
" -t --trace Prints diagnostic output tracing parser progress",
" -h --help Prints this help",
"Examples:",
cmd .. " Tests for 'tests/*.*",
cmd .. " -V Verbose tests for 'tests/*.*'",
cmd .. " -t Tests and tracing for 'tests/*.*'",
cmd .. " tests/fail* Tests for 'tests/fail*'",
cmd .. " -Vt tests/fail* Verbose tests and tracing for 'tests/fail*'",
cmd .. " -h Display this help",
}, "\n"))
os.exit()
end
-- Gets command according to OS
local function getcmd(str)
local system = (os.isunix() and "unix" or "win")
return cmds[system][str]
end
-- Obtains result as "pass" | "fail" | "diff" (AST doesn't match)
local function getresult(test)
local tmp, name = "tmp.json", pandoc.path.filename(test)
local json = pandoc.path.join({"tests", "asts", name .. ".json"})
local ok, out = io.try(getcmd("pandoc"), "-L", filter, verbose, trace, "-t json", "-o", tmp, test)
local json1, json2 = tmp:readtext(), json:readtext()
os.remove("tmp.json")
if (ok and json2 == nil) or (ok and json1 == json2) then
return "pass", out:strip()
elseif ok and json1 ~= json2 then
return "diff", out:strip()
else
return "fail", out:strip()
end
end
-- Parses args
for _, a in ipairs(arg) do
if a:match("^-.*h.*") then
help()
elseif a == "-V" or a == "--verbose" then
verbose = "--verbose"
elseif a == "-t" or a == "--trace" then
trace = "--trace"
elseif a == "-Vt" or a == "-tV" then
verbose = "--verbose"
trace = "--trace"
elseif a:match("^-") == nil then
table.insert(files, pandoc.path.normalize(a))
end
end
-- Default files
if #files == 0 then
for file in io.popen(getcmd("ls") .. " tests"):lines() do
if file ~= "asts" then
table.insert(files, pandoc.path.join({"tests", file}))
end
end
end
-- Clears terminal
os.execute(getcmd("clear"))
print "🐾 Starting tests"
-- Does tests
for _, file in ipairs(files) do
local expectation = pandoc.path.filename(file):gsub("%W.+$", "")
local result, output = getresult(file)
print("⚗️ " .. file .. ":")
print(" Expect: " .. expectation)
print(" Result: " .. result)
if #verbose > 0 or #trace > 0 then
print(output)
end
end

View File

@ -1,34 +0,0 @@
# Variables
ROOT=$(git rev-parse --show-toplevel)
FILTER=$ROOT/src/literate.lua
ARGS=()
# Removes unwanted args
for arg in "$@"; do
case $arg in
"--dist") FILTER=$ROOT/dist/literate.min.lua ;;
*) ARGS+=($arg) ;;
esac
done
# Moves to tests directory and clears the terminal
cd $ROOT/tests
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 "$ARGS"; do
echo && echo "⚗️ Test in '$arg' format:"
mds=$'\n\n'`(pandoc -t markdown *.md)`
rst=$'\n\n'`(pandoc -t markdown *.rst)`
org=$'\n\n'`(pandoc -t markdown *.org)`
echo "$mds" "$rst" "$org" | pandoc --lua-filter $FILTER -t $arg
done

View File

@ -1,68 +1,341 @@
-- IMPORTANT: for distribution the first 10 lines are changed to:
-- local fennel = mod
-- Initial setup for development:
-- Enables Fennel for Lisp-Lua embeded compatibility
-- Cfr. https://fennel-lang.org
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")
-- IMPORTANT: code for distribution starts after this line.
--[[
-- 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
---------------------------------- LITERATE ----------------------------------
-- Lexical elements
local space = S(" \t\r\n")
local lbracket = P"("
local rbracket = P")"
local word = (1 - (space + lbracket + rbracket))
-- Stores all literate stuff
local lit = {}
-- 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;
}
]]--
-- Indicates status
lit.status = true
-- Evals Lisp code
-- @param code string: code to evaluate
-- @return table: evaluation result as {bool, string, string, string}
local function eval(code)
is_passed, out = pcall (
function () return fennel.eval(code) end,
function (e) return e end
)
lua = ""
out = tostring(out)
preview = out:gsub("\n.*", "")
if is_passed then
lua = fennel.compileString(code)
end
return {is_passed = is_passed, preview = preview, out = out, lua = lua}
-- Stores literate functions
lit.fns = {}
-- Returns specific grammar
function lit.g(name)
-- Stores all grammars
local grammars = {}
-- 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 newline = P("\r") ^ -1 * P("\n")
local space = S(" \t")
local anyspace = S(" \t\r\n")
local spot = (1 - anyspace)
local any = (spot + space)
local yamlheader = space ^ 0 * P("---") * space ^ 0 * newline
local yamlfooter = space ^ 0 * P("...") * space ^ 0 * newline ^ -1
local yamlbody = -yamlfooter * any ^ 0 * newline
local label = R("az", "AZ") * R("az", "AZ", "09", "__") ^ 0
local ref = -B("\\") * P("#") * C(label)
local notref = (1 - ref) ^ 0
-- Function declaration grammar
grammars["declaration"] = P({
"Declaration",
Declaration = Ct(V("YAML") * V("Code"), "name"),
YAML = C(yamlheader * yamlbody ^ 0 * yamlfooter),
Code = C((any + newline) ^ 0),
})
-- Argument references grammar
grammars["references"] = notref
* P({
"References",
References = Ct((ref * notref) ^ 0),
})
* -1
return grammars[name]
end
return {
{
CodeBlock = function (block)
if block.classes:includes("eval") then
local raw = block.text
print("⚙️ ", raw)
local res = eval(raw)
print("", res["is_passed"], "", res["preview"])
if block.classes:includes("replace") then
return pandoc.CodeBlock(res["out"], {code=raw})
-- Prints located messages
function lit.puts(yaml_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([[`locale()`]]).meta)
local levelname, level, lang = "INFO", 0, os.lang()
for mtype, msgs in pairs(msg) do
if msgs[key] 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(yaml_key, ...)
if level >= verbosity then print("[" .. levelname .. "] [LIT] " .. msg) end
end
-- Parses and evaluates literate functions in document
function lit.exam(doc)
-- Extracts function declarations
local function extract(codeblock)
-- Checks function declarations
local function check(match)
-- Stores valid declaration
local declaration = {}
-- 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
-- Eachs command is a table of table;
-- First table indicates one or more internal or external evaluations
-- Second table indicates one or more commands to try
local langcmds = {
["lua"] = { ["int_eval"] = { "CMD1", "CMD2" } },
["fennel"] = { ["int_eval"] = { "CMD1", "CMD2" } },
["python"] = { ["ext_eval"] = { "CMD1", "CMD2" } },
["js"] = { ["ext_eval"] = { "CMD1", "CMD2" } },
["ruby"] = { ["ext_eval"] = { "CMD1", "CMD2" } },
["lisp"] = { ["ext_eval"] = { "CMD1", "CMD2" } },
["graphviz"] = { ["ext_eval"] = { "CMD1", "CMD2" } },
}
-- Prints error
-- Since error messages debuglevel are "ERROR", lit.status turns false
-- Flush the declaration to avoid storage in lit.fns
local function putserr(...)
lit.puts(...)
declaration = {}
end
-- Adds 'eval' key in declaration
-- This key indicates what to eval
local function addeval()
declaration["eval"] = {}
-- If lang and cmd are present, ignores lang and use cmd as eval
if declaration["lang"] and declaration["cmd"] then
lit.puts("ignore_lang", declaration["cmd"], declaration["lang"])
declaration["eval"] = { ["ext_eval"] = { declaration["cmd"] } }
-- If neither lang or cmd are presents, defaults lang and eval to lua
elseif not declaration["lang"] and not declaration["cmd"] then
declaration["lang"] = "lua"
declaration["eval"] = langcmds["lua"]
-- If only lang present, eval is lang
elseif declaration["lang"] and not declaration["cmd"] then
declaration["eval"] = langcmds[declaration["lang"]]
-- If only cmd present, eval is cmd
elseif not declaration["lang"] and declaration["cmd"] then
declaration["eval"] = { ["ext_eval"] = { declaration["cmd"] } }
end
end
end,
}
}
-- Checks for valid command for Unix systems
local function checkcmd()
if declaration["cmd"] then
local cmd = declaration["cmd"]:split()[1]
if os.isunix() then
local 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, unwanted and wrong metadata
local function checkextra()
for key, _ in pairs(declaration) 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(kind)
-- Checks for valid metadata type
local function checktype(type1, key)
local val = declaration[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, key)
local val = tostring(declaration[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 declaration[key] == nil and kind == "mandatory" then
putserr("no_key", key)
elseif declaration[key] and type(val) == "table" then
checkpattern(val, key)
elseif declaration[key] then
checktype(val, key)
end
end
end
-- Parses metadata
local function parsemeta(yaml)
local isok, res = pcall(pandoc.read, yaml)
if isok and not (pandoc.utils.stringify(res.meta):isempty()) then
declaration = pandoc.metatotable(res.meta)
checkmeta("mandatory")
checkmeta("optional")
checkextra()
checkcmd()
if next(declaration) then addeval() end
elseif isok and pandoc.utils.stringify(res.meta):isempty() then
putserr("meta_empty")
else
putserr("meta_invalid")
end
end
-- TODO
-- Parses code
local function parsecode(rawcode)
local code = ""
local references = lpeg.match(lit.g("references"), rawcode)
if references then
for _, ref in ipairs(references) do
print("reference:", ref)
end
end
declaration["code"] = code
end
if match then
lit.puts("checking", table.concat(match, ""):indent())
parsemeta(match[1])
if next(declaration) then parsecode(match[2]) end
end
return declaration
end
local parsed = check(lpeg.match(lit.g("declaration"), codeblock.text))
if next(parsed) then
-- TODO:
-- Eval parsed lit.fns and modify code block accordingly
lit.fns[parsed["id"]] = parsed
end
return codeblock
end
-- Calls literate functions
local function call(el)
-- TODO
-- Calls literate functions in code inlines
local function codecall(code)
return pandoc.Str("EVAL: " .. pandoc.utils.stringify(code))
end
local function metacall(meta)
local function inlinescall(inlines)
return inlines:walk({
Code = function(code) return codecall(code) end,
})
end
for key, val in pairs(meta) do
if pandoc.utils.type(val) == "Inlines" then
meta[key] = inlinescall(val)
elseif pandoc.utils.type(val) == "List" then
for i, item in ipairs(val) do
if pandoc.utils.type(item) == "Inlines" then
meta[key][i] = inlinescall(item)
end
end
end
end
return meta
end
if pandoc.utils.type(el) == "Meta" then
return metacall(el)
else
return codecall(el)
end
end
-- Asserts literate status
local function assert()
if not lit.status then
lit.puts("aborted")
os.exit(1)
end
end
doc = doc:walk({ CodeBlock = function(block) return extract(block) end })
doc = doc:walk({ Meta = function(meta) return call(meta) end })
doc = doc:walk({ Code = function(code) return call(code) end })
return doc:walk({ Pandoc = function(_) assert() end })
end

41
src/locale.yaml Normal file
View File

@ -0,0 +1,41 @@
---
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
...

7
src/natural.lua Normal file
View File

@ -0,0 +1,7 @@
----------------------------------- NATURAL -----------------------------------
local nat = {}
function nat.get(str) return str end
return nat

8
src/pandoc.lua Normal file
View File

@ -0,0 +1,8 @@
------------------------------------ PANDOC -----------------------------------
return {
-- Parses and evals literate programming
{ Pandoc = function(e) return lit.exam(e) end },
-- TODO Parses and evals natural programming
-- { Inlines = function(e) return nat.get(pandoc.utils.stringify(e)) end },
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"pandoc-api-version":[1,23],"meta":{},"blocks":[{"t":"Header","c":[1,["declaration-override",[],[]],[{"t":"Str","c":"Declaration"},{"t":"Space"},{"t":"Str","c":"Override"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn1\n...\n1 + 1"]},{"t":"Para","c":[{"t":"Str","c":"Override:"}]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn1\n...\n1 + 2"]},{"t":"Header","c":[1,["declaration-with-language-and-command",[],[]],[{"t":"Str","c":"Declaration"},{"t":"Space"},{"t":"Str","c":"with"},{"t":"Space"},{"t":"Str","c":"Language"},{"t":"Space"},{"t":"Str","c":"and"},{"t":"Space"},{"t":"Str","c":"Command"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn4\nlang: python\ncmd: python -E -X utf8\n...\n1 + 1"]},{"t":"Header","c":[1,["declaration-with-unused-argument",[],[]],[{"t":"Str","c":"Declaration"},{"t":"Space"},{"t":"Str","c":"with"},{"t":"Space"},{"t":"Str","c":"Unused"},{"t":"Space"},{"t":"Str","c":"Argument"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn3\nargs:\n a: 1\n...\n1 + 1"]}]}

142
tests/fail.lit.errors.md Normal file
View File

@ -0,0 +1,142 @@
# Invalid Declarations
All errors should be collected, printed and exit with 1.
Malformed YAML:
---
id: fn1
fail
...
1 + 1
Empty YAML:
---
...
1 + 1
Empty YAML and code:
---
...
Misses id:
---
identifier: fn1
...
1 + 1
Wrong id (doesn't starts with `%a`):
---
id: 1
...
1 + 1
Wrong id (doesn't follows with `[_%w]`):
---
id: f-1
...
1 + 1
Wrong id (more than 1 word):
---
id: f n1
...
1 + 1
Invalid value:
---
id: fn1
shift: "true"
...
1 + 1
Invalid path:
---
id: fn1
dump: invalid/path.txt
...
1 + 1
Extra key:
---
id: fn1
language: fennel
...
1 + 1
Uknown lang:
---
id: fn1
lang: fail
...
1 + 1
Uknown cmd:
---
id: fn1
cmd: piton -E -X utf8
...
1 + 1
Empty code:
---
id: fn1
...
Misses arg:
---
id: fn1
...
#a + #b
Infinite loop:
---
id: fn1
args:
x: 1
...
`fn1(2)` * #x
Invalid code:
---
id: fn1
...
false + false
# Declarations With Invalid Calls:
---
id: fn1
typed: true
args:
a: 1
b: 2
...
#a * #b
# Invalid Calls
- `fn1(` never end
- `fn1(invalid arg)`
- `fn1()` misses args
- `fn1(1)` less args
- `fn1(1, 2, 3)` extra args
- `fn1(x: 3, y: 4)` wrong kwargs
- `fn1(1, a: 2)` mixed arg and kwarg
- `fn1(a: 1.0, b: 2.0)` misses type
- `fn2()` not declared

202
tests/pass.lit.infos.md Normal file
View File

@ -0,0 +1,202 @@
---
bool: true
int: 1
float: 1.1
string: foo all
list:
- true
- 1
- 1.1
- foo all
- "`fn1()`"
- Another test `fn2()`
fn1_test: "`fn1()`"
fn2_test: Another test `fn2()`
...
# Calls Before Declarations
- `fn1()``fn2()`
- `fn3(true)`
- `fn4(2.0, 3.1)`
- `fn3(n: false)`
- `fn4(b: 4, a: 5)`
# Declarations
All literate declarations goes in code blocks that should be printed 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 That Are Not Declarations
Always ignored:
---
echo "Ignore me!"
# Calls 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 Calls
- `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)`

31
tests/pass.lit.warns.md Normal file
View File

@ -0,0 +1,31 @@
# Declaration Override
---
id: fn1
...
1 + 1
Override:
---
id: fn1
...
1 + 2
# Declaration with Language and Command
---
id: fn4
lang: python
cmd: python -E -X utf8
...
1 + 1
# Declaration with Unused Argument
---
id: fn3
args:
a: 1
...
1 + 1

View File

@ -1,133 +0,0 @@
# Test with Markdown
## Function Declarations
Valid declarations:
* A declaration: f1() = (+ 1 2 3). All declarations should be print on `--verbose`.
* f2() = (+ 4 5 6) is another declaration.
* f3() = (+ 7 8 9)
* Two declarations: f4() = (- 9 8) and f5() = (- 7 6).
* Two consecutive declarations: f6() = (- 5 4) f7() = (- 3 2).
* A declaration with one arg: f8(n) = (* #n #n).
* A declaration with two args: f9(a, b) = (* #a #b).
* A declaration with variable number of args: f10(...) = (.. #...).
Not declarations:
* \f11() = (+ 1 2); doesn't starts with `%a`
* f-12() = (+ 1 2); doesn't continue with `%w`
* f 13() = (+ 1 2); starts with `%d`
* f14 () = (+ 1 2); space before `(`
* f15) = (+ 1 2); misses `(`
* f16() = + 1 2); misses `(`
* f17( = (+ 1 2); misses `)`
* f18() = (+ 1 2); misses `)`
* f19 = (+ 1 2); misses `()`
* f20() = + 1 2; misses `()`
* f21() (+ 1 2); misses `=`
* `f22() = (+ 1 2)`; inside inline code
```
f23() = (+ 1 2) inside code block
```
Overrides `f1()` with warn: f1() = (+ 2 3 4) and it should fail on `--fail-if-warnings`.
## Functions Calls
Valid calls:
* A common call: f1(). All calls should be print on `--verbose`.
* f2() another common call.
* f3()
* Two calls: f4() and f5().
* Two consecutive calls: f6() f7().
* A call with one arg: f8(2).
* A call with two args: f9(2, 3).
* A call with variable number of args: f10("The popular ", "\"Hello,", " ", "", "World!\"").
* A call with args as kwargs: f8(n: 3) and f9(a: 4, b: 5).
Valid calls and data types:
* f10(); no data
* f10(1, 1_000, 1.0); numbers
* f10("string"); string
* f10([]); empty array / list / sequential table
* f10([0 1]); array / list / sequential table
* f10({}); empty dict / table
* f10({"k" 0}); dict / table
Not calls:
* \f11(); doesn't starts with `%a`
* f-12(); doesn't continue with `%w`
* f 13(); starts with `%d`
* f14 (); space before `(`
* f15); misses `(`
* f16(a); invalid data type
* f17(; misses `)`
* f18(a:); misses kwarg value
* f19(1 2); misses comma separator
* f20( ); extra space
* f21({); incomplete data type
* `f22()`; inside inline code
```
f23() inside code block
```
Invalid calls:
* f8(); misses arg
* f8(a: 3); wrong kwarg
* f9(1, 2, 3); wrong args number
* f9(1, b: 2); mixed arg and kwarg
* f10(...: 0); `...` can't be kwarg
* f11(); not declared
Invalid calls generate error.
# Function Calls with Reserved Keyword Arguments
Valid calls:
* f1($action: "return"); returns result after call.
* f2($action: "clear"); clears it from source document after call.
* f3($action: "wipe"); wipes it and its declaration after all calls.
* f4($action: "dump"); dumps its declaration after call.
* f4($action: "quote"); dumps its declaration without call.
* f5($eval: "f5.txt", $code: "f5.fnl", $lua: "f5.lua"); writes evaluation results, Lisp code and Lua code.
* f6($eval: "f6-7.txt", $code: "f6-7.fnl", $lua: "f6-7.lua"); writes in same files than below.
* f7($eval: "f6-7.txt", $code: "f6-7.fnl", $lua: "f6-7.lua"); writes in same files than above.
* f8(4, $action: "return")
* f8($action: "return", 5)
* f9(a: 1, b: 2, $action: "return")
* f9($action: "return", a: 1, b: 2)
* f9(a: 1, $action: "return", b: 2)
Invalid calls:
* f1($act: "return"); invalid rkwarg
* f2($action: "clearr"); invalid rkwarg value
* f3($eval: "/path/does/not/exists"); invalid path
* f4($code: "/path/does/not/exists"); invalid path
* f5($lua: "/path/does/not/exists"); invalid path
Invalid calls generate error.
# Function Recursion
Valid recursion:
* A declaration that uses a call inside: f24(x) = (* f1() x), result: f24($action: "return").
* A declaration that uses a call inside with "quote" action: f25(y, z) = (+ f2($action: "quote") y z), result: f25(9, 8, $action: "return").
* A call with other function as arg: f8(f1()).
* A call with other function as kwarg: f9(b: 3, a: f2()).
Invalid recursion:
* f26(i) = (* f11() i); \f11() not declared.
* f27(j) = (* f27(1) f27(2)); infinite loop.
* f8(n: f11()); \f11() not declared.
* f8(f8(3)); infinite loop.

View File

@ -1 +0,0 @@
* Test with Org Mode

View File

@ -1,2 +0,0 @@
Test with reStructuredText
==========================