Almost all block validations

This commit is contained in:
perro tuerto 2023-03-20 18:28:32 -07:00
parent 2ec9e0bd4b
commit ddb9c68e82
10 changed files with 368 additions and 109 deletions

189
dist/lin.lua vendored
View File

@ -6262,7 +6262,7 @@ local fnl = mod
local nat = {}
function nat.parse(str)
function nat.get(str)
return str
end
@ -6270,15 +6270,114 @@ end
-- Types functions extensions
-- Changes newlines so everything is in one line
function string:linearize()
return self:gsub("\n", "\\\\n")
end
-- Checks if is empty
function string:isempty()
return self == ''
end
-- Checks if is directory
function string:isdir()
if self ~= "." and os.rename(self, self) == nil then
return false
end
return true
end
-- Variable for all literate stuff
local lit = {}
-- Error collector
lit.e = {}
-- Status indicator
lit.status = true
-- Valid metadata structure
lit.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",
},
}
function lit.getmetaval(meta, key)
local mtype = pandoc.utils.type(meta[key])
local mval = meta[key]
if mtype == "Inlines" then
mval = pandoc.utils.stringify(mval)
mtype = pandoc.utils.type(mval)
end
return mval, mtype
end
function lit.checkmetatype(type, meta, key)
local mval, mtype = lit.getmetaval(meta, key)
local err = ""
if type ~= mtype then
if type == "path" then
if not(pandoc.path.directory(mval):isdir()) then
err = "Invalid path '" .. mval .. "' in key '" .. key .. "'"
end
else
err = "Invalid type '" .. mtype .. "' in key '" .. key .. "'"
end
end
if not(err:isempty()) then lit.puts(err, "ERROR") end
end
function lit.checkmetatable(table, meta, key)
local mval = lit.getmetaval(meta, key)
local err = ""
for _, pattern in pairs(table) do
if mval:match("^" .. pattern .. "$") then
err = ""
break
else
err = "Invalid value '" .. mval .. "' in key '" .. key .. "'"
end
end
if not(err:isempty()) then lit.puts(err, "ERROR") end
end
function lit.checkmeta(meta, kind)
for key, val in pairs(lit.metastruct[kind]) do
if meta[key] == nil then
if kind == "mandatory" then
lit.puts("Key '" .. key .. "' not found", "ERROR")
end
else
if type(val) == "table" then
lit.checkmetatable(val, meta, key)
else
lit.checkmetatype(val, meta, key)
end
end
end
end
function lit.setmeta(meta)
lit.checkmeta(meta, "mandatory")
lit.checkmeta(meta, "optional")
-- TODO checks for 1) extra keys and 2) duplicates
-- Set defaults if lit.status = true
return meta
end
-- Grammars
lit.g = {}
@ -6289,9 +6388,9 @@ lit.space = lpeg.S" \t"
lit.anyspace = lpeg.S" \t\r\n"
lit.spot = (1 - lit.anyspace)
lit.any = (lit.spot + lit.space)
lit.yaml_header = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
lit.yaml_footer = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
lit.yaml_body = -lit.yaml_footer * lit.any^0 * lit.newline
lit.yamlheader = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
lit.yamlfooter = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
lit.yamlbody = -lit.yamlfooter * lit.any^0 * lit.newline
lit.id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
lit.ref = lpeg.P"#" * lit.id
@ -6299,7 +6398,7 @@ lit.ref = lpeg.P"#" * lit.id
lit.g.block = lpeg.P {
"Block";
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
YAML = lpeg.C(lit.yaml_header * lit.yaml_body^0 * lit.yaml_footer);
YAML = lpeg.C(lit.yamlheader * lit.yamlbody^0 * lit.yamlfooter);
Code = lpeg.C((lit.any + lit.newline)^0);
}
@ -6320,8 +6419,17 @@ function lit.eval(code)
end
]]--
function lit.debug_level(str)
function lit.assert(e, status)
if status == false then
lit.puts("Aborted due previous errors", "ERROR")
os.exit(1)
end
return e
end
function lit.debuglevel(str)
if str == "ERROR" then
lit.status = false
return 2
elseif str == "WARNING" then
return 1
@ -6330,67 +6438,80 @@ function lit.debug_level(str)
end
end
function lit.puts(msg, level)
local verbosity = lit.debug_level(PANDOC_STATE.verbosity)
level = lit.debug_level(level)
function lit.puts(msg, kind)
local kind = kind or "INFO"
local verbosity = lit.debuglevel(PANDOC_STATE.verbosity)
level = lit.debuglevel(kind)
if level >= verbosity then
print("[" .. PANDOC_STATE.verbosity .. "] " .. msg)
print("[" .. kind .. "] [LIT] " .. msg)
end
end
function lit.add_error(err)
end
function lit.check_yaml(raw_yaml)
local yaml = io.popen("pandoc -t json <<<\"" .. raw_yaml .. "\" 2>&1")
function lit.parseyaml(rawyaml)
-- TODO: avoid popen?
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
if yaml:read("*l"):sub(1, 1) == "{" then
yaml = pandoc.read(raw_yaml)
return yaml
yaml = pandoc.read(rawyaml).meta
if pandoc.utils.stringify(yaml):isempty() then
lit.puts("Empty YAML", "ERROR")
else
return lit.setmeta(yaml)
end
else
lit.add_error(err)
lit.puts("Invalid YAML", "ERROR")
return nil
end
end
function lit.check_block(parsed)
function lit.parseblock(parsed)
lit.puts("Parsing " .. table.concat(parsed, "\n"):linearize())
local yaml = lit.check_yaml(parsed[1])
local yaml = lit.parseyaml(parsed[1])
-- TODO: parsecode
return "TODO"
end
function lit.parse_blocks(codeblock)
function lit.getblock(codeblock)
local parsed = lpeg.match(lit.g.block, codeblock.text)
local valid = (parsed ~= nil and lit.check_block(parsed) or false)
return codeblock
parsed = (parsed ~= nil and lit.parseblock(parsed) or parsed)
return codeblock, lit.status
end
function lit.parse_inserts(code)
-- print(code)
return code
function lit.getinsert(code)
return code, lit.status
end
------------------------------------ PANDOC -----------------------------------
local litstatus = true
return {
{
-- Parses and evals literate blocks
CodeBlock = function(codeblock)
return lit.parse_blocks(codeblock)
codeblock, litstatus = lit.getblock(codeblock)
return codeblock
end,
-- Asserts literate
Pandoc = function(pandoc)
return lit.assert(pandoc, litstatus)
end,
},
{
-- Parses literate inserts
Code = function(code)
return lit.parse_inserts(code)
code, litstatus = lit.getinsert(code)
return code
end,
-- Asserts literate
Pandoc = function(pandoc)
return lit.assert(pandoc, litstatus)
end,
}, {
-- Parses and evals natural programming
-- TODO
Inlines = function(inlines)
md = pandoc.utils.stringify(inlines)
md = nat.parse(md)
md = nat.get(md)
return inlines
end,
}

View File

@ -3,7 +3,7 @@
# Variables
FILTER=dist/lin.lua
FILES="tests/*.md"
VERBOSE=false
VERBOSE=""
AST=false
CMD="sh $0"
@ -36,7 +36,7 @@ get_result () {
# Checks options
while getopts ':vah' opt; do
case "$opt" in
v) VERBOSE=true ; shift ;;
v) VERBOSE="--verbose" ; shift ;;
a) AST=true ; shift ;;
h) echo_help ;;
?) echo "ERROR: only -v, -a or -h is allowed" ; exit 1 ;;
@ -61,11 +61,11 @@ clear && echo "🐾 Starting tests"
for file in $FILES; do
echo "⚗️ $file:"
expectation=${file:6:4}
ast=$(pandoc -L $FILTER -t json -o tmp.json $file)
ast=$(pandoc $VERBOSE -L $FILTER -t json -o tmp.json $file)
result=$(get_result $? $file)
echo " Expect: $expectation"
echo " Result: $result"
[ "$VERBOSE" = true ] && echo -e "$ast"
[ "$VERBOSE" = "--verbose" ] && echo -e "$ast"
[ "$AST" = true ] && pandoc -L $FILTER -t native $file
rm tmp.json
done

View File

@ -2,15 +2,114 @@
-- Types functions extensions
-- Changes newlines so everything is in one line
function string:linearize()
return self:gsub("\n", "\\\\n")
end
-- Checks if is empty
function string:isempty()
return self == ''
end
-- Checks if is directory
function string:isdir()
if self ~= "." and os.rename(self, self) == nil then
return false
end
return true
end
-- Variable for all literate stuff
local lit = {}
-- Error collector
lit.e = {}
-- Status indicator
lit.status = true
-- Valid metadata structure
lit.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",
},
}
function lit.getmetaval(meta, key)
local mtype = pandoc.utils.type(meta[key])
local mval = meta[key]
if mtype == "Inlines" then
mval = pandoc.utils.stringify(mval)
mtype = pandoc.utils.type(mval)
end
return mval, mtype
end
function lit.checkmetatype(type, meta, key)
local mval, mtype = lit.getmetaval(meta, key)
local err = ""
if type ~= mtype then
if type == "path" then
if not(pandoc.path.directory(mval):isdir()) then
err = "Invalid path '" .. mval .. "' in key '" .. key .. "'"
end
else
err = "Invalid type '" .. mtype .. "' in key '" .. key .. "'"
end
end
if not(err:isempty()) then lit.puts(err, "ERROR") end
end
function lit.checkmetatable(table, meta, key)
local mval = lit.getmetaval(meta, key)
local err = ""
for _, pattern in pairs(table) do
if mval:match("^" .. pattern .. "$") then
err = ""
break
else
err = "Invalid value '" .. mval .. "' in key '" .. key .. "'"
end
end
if not(err:isempty()) then lit.puts(err, "ERROR") end
end
function lit.checkmeta(meta, kind)
for key, val in pairs(lit.metastruct[kind]) do
if meta[key] == nil then
if kind == "mandatory" then
lit.puts("Key '" .. key .. "' not found", "ERROR")
end
else
if type(val) == "table" then
lit.checkmetatable(val, meta, key)
else
lit.checkmetatype(val, meta, key)
end
end
end
end
function lit.setmeta(meta)
lit.checkmeta(meta, "mandatory")
lit.checkmeta(meta, "optional")
-- TODO checks for 1) extra keys and 2) duplicates
-- Set defaults if lit.status = true
return meta
end
-- Grammars
lit.g = {}
@ -21,9 +120,9 @@ lit.space = lpeg.S" \t"
lit.anyspace = lpeg.S" \t\r\n"
lit.spot = (1 - lit.anyspace)
lit.any = (lit.spot + lit.space)
lit.yaml_header = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
lit.yaml_footer = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
lit.yaml_body = -lit.yaml_footer * lit.any^0 * lit.newline
lit.yamlheader = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
lit.yamlfooter = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
lit.yamlbody = -lit.yamlfooter * lit.any^0 * lit.newline
lit.id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
lit.ref = lpeg.P"#" * lit.id
@ -31,7 +130,7 @@ lit.ref = lpeg.P"#" * lit.id
lit.g.block = lpeg.P {
"Block";
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
YAML = lpeg.C(lit.yaml_header * lit.yaml_body^0 * lit.yaml_footer);
YAML = lpeg.C(lit.yamlheader * lit.yamlbody^0 * lit.yamlfooter);
Code = lpeg.C((lit.any + lit.newline)^0);
}
@ -52,8 +151,17 @@ function lit.eval(code)
end
]]--
function lit.debug_level(str)
function lit.assert(e, status)
if status == false then
lit.puts("Aborted due previous errors", "ERROR")
os.exit(1)
end
return e
end
function lit.debuglevel(str)
if str == "ERROR" then
lit.status = false
return 2
elseif str == "WARNING" then
return 1
@ -62,46 +170,46 @@ function lit.debug_level(str)
end
end
function lit.puts(msg, level)
local verbosity = lit.debug_level(PANDOC_STATE.verbosity)
level = lit.debug_level(level)
function lit.puts(msg, kind)
local kind = kind or "INFO"
local verbosity = lit.debuglevel(PANDOC_STATE.verbosity)
level = lit.debuglevel(kind)
if level >= verbosity then
print("[" .. PANDOC_STATE.verbosity .. "] " .. msg)
print("[" .. kind .. "] [LIT] " .. msg)
end
end
function lit.add_error(err)
end
function lit.check_yaml(raw_yaml)
local yaml = io.popen("pandoc -t json <<<\"" .. raw_yaml .. "\" 2>&1")
function lit.parseyaml(rawyaml)
-- TODO: avoid popen?
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
if yaml:read("*l"):sub(1, 1) == "{" then
yaml = pandoc.read(raw_yaml)
return yaml
yaml = pandoc.read(rawyaml).meta
if pandoc.utils.stringify(yaml):isempty() then
lit.puts("Empty YAML", "ERROR")
else
return lit.setmeta(yaml)
end
else
lit.add_error(err)
lit.puts("Invalid YAML", "ERROR")
return nil
end
end
function lit.check_block(parsed)
function lit.parseblock(parsed)
lit.puts("Parsing " .. table.concat(parsed, "\n"):linearize())
local yaml = lit.check_yaml(parsed[1])
local yaml = lit.parseyaml(parsed[1])
-- TODO: parsecode
return "TODO"
end
function lit.parse_blocks(codeblock)
function lit.getblock(codeblock)
local parsed = lpeg.match(lit.g.block, codeblock.text)
local valid = (parsed ~= nil and lit.check_block(parsed) or false)
return codeblock
parsed = (parsed ~= nil and lit.parseblock(parsed) or parsed)
return codeblock, lit.status
end
function lit.parse_inserts(code)
-- print(code)
return code
function lit.getinsert(code)
return code, lit.status
end
return lit

View File

@ -2,7 +2,7 @@
local nat = {}
function nat.parse(str)
function nat.get(str)
return str
end

View File

@ -1,22 +1,35 @@
------------------------------------ PANDOC -----------------------------------
local litstatus = true
return {
{
-- Parses and evals literate blocks
CodeBlock = function(codeblock)
return lit.parse_blocks(codeblock)
codeblock, litstatus = lit.getblock(codeblock)
return codeblock
end,
-- Asserts literate
Pandoc = function(pandoc)
return lit.assert(pandoc, litstatus)
end,
},
{
-- Parses literate inserts
Code = function(code)
return lit.parse_inserts(code)
code, litstatus = lit.getinsert(code)
return code
end,
-- Asserts literate
Pandoc = function(pandoc)
return lit.assert(pandoc, litstatus)
end,
}, {
-- Parses and evals natural programming
-- TODO
Inlines = function(inlines)
md = pandoc.utils.stringify(inlines)
md = nat.parse(md)
md = nat.get(md)
return inlines
end,
}

View File

@ -16,24 +16,11 @@ Empty YAML:
...
1 + 2 + 3
Empty code:
---
id: fn1
...
Empty YAML and code:
---
...
Misses arg:
---
id: fn1
...
#a + #b
Misses id:
---
@ -48,7 +35,7 @@ Wrong id (doesn't starts with `%a`):
...
1 + 2 + 3
Wrong id (doesn't follows with `%w`):
Wrong id (doesn't follows with `[_%w]`):
---
id: f-1
@ -62,17 +49,23 @@ Wrong id (more than 1 word):
...
1 + 2 + 3
Uknown cmd:
Invalid value:
---
id: fn1
cmd: pyton -E -X utf8
args:
n: 2
shift: "true"
...
#n + #n
Invalid shift value
Unknown key:
Invalid path:
---
id: fn1
dump: invalid/path.txt
...
Invalid dump value
Extra key:
---
id: fn1
@ -83,13 +76,18 @@ Unknown key:
...
(* #a #b)
Invalid value:
Empty code:
---
id: fn1
shift: "true"
...
"The literate block is shifted by its eval result."
Misses arg:
---
id: fn1
...
#a + #b
Invalid code:
@ -98,12 +96,22 @@ Invalid code:
...
false + false
Uknown cmd:
---
id: fn1
cmd: piton -E -X utf8
args:
n: 2
...
#n + #n
Infinite loop:
---
id: fn1
args:
x: 1
x: 1
...
#fn1(2) * #x

View File

@ -54,7 +54,7 @@ With cmd (ignores lang):
...
#n + #n
With shift (nil by default):
With shift:
---
id: fn6
@ -62,7 +62,7 @@ With shift (nil by default):
...
"The literate block is shifted by its eval result."
With wipe (nil by default):
With wipe:
---
id: fn7
@ -70,7 +70,7 @@ With wipe (nil by default):
...
"This evals but it is wipe from doc."
With typed (nil by default):
With typed:
---
id: fn4
@ -81,7 +81,7 @@ With typed (nil by default):
...
#a * #b
With link (nil by default) and alt (optional):
With link and alt:
---
id: fn8
@ -95,7 +95,7 @@ With link (nil by default) and alt (optional):
c -> a;
}
With img (nil by default) and alt (optional):
With img and alt:
---
id: fn9
@ -109,10 +109,20 @@ With img (nil by default) and alt (optional):
c -> a;
}
With inner function:
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
...
@ -121,7 +131,7 @@ With inner function:
With inner function with args:
---
id: fn11
id: fn12
args:
y: 1
z: 2
@ -131,7 +141,7 @@ With inner function with args:
With inner inner function:
---
id: fn12
id: fn13
args:
a: 1
...

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ Override:
---
id: fn3
args:
- a: 1
a: 1
...
7 + 8 + 9
@ -28,7 +28,7 @@ Override:
lang: python
cmd: python -E -X utf8
args:
- n: 2
n: 2
...
#n * #n
@ -36,6 +36,5 @@ Override:
---
id: fn5
lang: echo
...
"Lang becomes cmd."

View File

@ -1 +1 @@
{"pandoc-api-version":[1,23],"meta":{},"blocks":[{"t":"Header","c":[1,["block-override",[],[]],[{"t":"Str","c":"Block"},{"t":"Space"},{"t":"Str","c":"Override"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn1\n...\n1 + 2 + 3\n\n---\nid: fn1\n...\n4 + 5 + 6"]},{"t":"Header","c":[1,["block-with-unused-argument",[],[]],[{"t":"Str","c":"Block"},{"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...\n7 + 8 + 9"]},{"t":"Header","c":[1,["block-with-language-and-command",[],[]],[{"t":"Str","c":"Block"},{"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\nargs:\n- n: 2\n...\n#n * #n"]},{"t":"Header","c":[1,["block-with-unknown-language",[],[]],[{"t":"Str","c":"Block"},{"t":"Space"},{"t":"Str","c":"with"},{"t":"Space"},{"t":"Str","c":"Unknown"},{"t":"Space"},{"t":"Str","c":"Language"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn5\nlang: echo\n...\n\"Lang becomes cmd.\""]}]}
{"pandoc-api-version":[1,23],"meta":{},"blocks":[{"t":"Header","c":[1,["block-override",[],[]],[{"t":"Str","c":"Block"},{"t":"Space"},{"t":"Str","c":"Override"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn1\n...\n1 + 2 + 3"]},{"t":"Para","c":[{"t":"Str","c":"Override:"}]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn1\n...\n4 + 5 + 6"]},{"t":"Header","c":[1,["block-with-unused-argument",[],[]],[{"t":"Str","c":"Block"},{"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...\n7 + 8 + 9"]},{"t":"Header","c":[1,["block-with-language-and-command",[],[]],[{"t":"Str","c":"Block"},{"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\nargs:\n n: 2\n...\n#n * #n"]},{"t":"Header","c":[1,["block-with-unknown-language",[],[]],[{"t":"Str","c":"Block"},{"t":"Space"},{"t":"Str","c":"with"},{"t":"Space"},{"t":"Str","c":"Unknown"},{"t":"Space"},{"t":"Str","c":"Language"}]]},{"t":"CodeBlock","c":[["",[],[]],"---\nid: fn5\n...\n\"Lang becomes cmd.\""]}]}