1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-28 09:09:31 +00:00

[language] allow multiline expression between (), [] and {}

This commit is contained in:
Étienne Fildadut 2024-01-15 20:02:40 +01:00
parent 1ea06c63eb
commit 78f3c6448e
44 changed files with 291 additions and 147 deletions

View file

@ -13,9 +13,11 @@ local common = {
trim = function(str) trim = function(str)
return str:match("^%s*(.-)%s*$") return str:match("^%s*(.-)%s*$")
end, end,
-- format ansi colored string
fmt = function(str, ...) fmt = function(str, ...)
return ansicolors(str):format(...) return ansicolors(str):format(...)
end, end,
-- generate a uuidv4
uuid = function() uuid = function()
return ("xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx") -- version 4 return ("xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx") -- version 4
:gsub("N", math.random(0x8, 0xb)) -- variant 1 :gsub("N", math.random(0x8, 0xb)) -- variant 1

View file

@ -0,0 +1,23 @@
local class = require("anselme.lib.class")
local options = { "limit_pattern", "allow_newlines" }
local Options
Options = class {
limit_pattern = nil,
allow_newlines = false,
with = function(self, t)
local r = Options:new()
for _, opt in ipairs(options) do
if t[opt] ~= nil then
r[opt] = t[opt]
else
r[opt] = self[opt]
end
end
return r
end
}
return Options

View file

@ -29,6 +29,13 @@ Source = class {
self:increment(utf8.len(capture:match("[^\n]*$"))) self:increment(utf8.len(capture:match("[^\n]*$")))
return ... return ...
end, end,
consume_leading_whitespace = function(self, options, str)
if options.allow_newlines then
return self:consume(str:match("^([ \t\n]*)(.*)$"))
else
return self:consume(str:match("^([ \t]*)(.*)$"))
end
end,
clone = function(self) clone = function(self)
return Source:new(self.name, self.line, self.position) return Source:new(self.name, self.line, self.position)

View file

@ -3,7 +3,7 @@ local expression_to_ast = require("anselme.parser.expression.to_ast")
local ast = require("anselme.ast") local ast = require("anselme.ast")
local PartialScope, Block, Flush, Call, Identifier = ast.PartialScope, ast.Block, ast.Flush, ast.Call, ast.Identifier local PartialScope, Block, Flush, Call, Identifier = ast.PartialScope, ast.Block, ast.Flush, ast.Call, ast.Identifier
local function block(source, str) local function block(source, options, str)
local start_source = source:clone() local start_source = source:clone()
if not str:match("^\n") then if not str:match("^\n") then
@ -45,7 +45,7 @@ local function block(source, str)
-- parse line -- parse line
local s, exp local s, exp
s, exp, rem = pcall(expression_to_ast, source, line) s, exp, rem = pcall(expression_to_ast, source, options, line)
if not s then error(("invalid expression in block: %s"):format(exp), 0) end if not s then error(("invalid expression in block: %s"):format(exp), 0) end
-- single implicit _: line was empty (e.g. single comment in the line) -- single implicit _: line was empty (e.g. single comment in the line)

View file

@ -5,7 +5,8 @@ comment = primary {
match = function(self, str) match = function(self, str)
return str:match("^%/%*") return str:match("^%/%*")
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local limit_pattern = options.limit_pattern
local rem = source:consume(str:match("^(%/%*)(.*)$")) local rem = source:consume(str:match("^(%/%*)(.*)$"))
local content_list = {} local content_list = {}
@ -27,7 +28,7 @@ comment = primary {
-- nested comment -- nested comment
if rem:match("^%/%*") then if rem:match("^%/%*") then
local subcomment local subcomment
subcomment, rem = comment:parse(source, rem, limit_pattern) subcomment, rem = comment:parse(source, options, rem)
table.insert(content_list, "/*") table.insert(content_list, "/*")
table.insert(content_list, subcomment) table.insert(content_list, subcomment)

View file

@ -13,25 +13,26 @@ return primary {
match = function(self, str) match = function(self, str)
return identifier:match(str) return identifier:match(str)
end, end,
parse = function(self, source, str, limit_pattern, no_default_value) parse = function(self, source, options, str, no_default_value)
local source_param = source:clone() local source_param = source:clone()
-- name -- name
local ident, rem = identifier:parse(source, str) local ident, rem = identifier:parse(source, options, str)
rem = source:consume_leading_whitespace(options, rem)
-- value check -- value check
local value_check local value_check
if rem:match("^[ \t]*::") then if rem:match("^::") then
local scheck = source:consume(rem:match("^([ \t]*::[ \t]*)(.*)$")) local scheck = source:consume(rem:match("^(::)(.*)$"))
value_check, rem = expression_to_ast(source, scheck, limit_pattern, value_check_priority) value_check, rem = expression_to_ast(source, options, scheck, value_check_priority)
end end
-- default value -- default value
local default local default
if not no_default_value then if not no_default_value then
if rem:match("^[ \t]*=") then if rem:match("^=") then
local sdefault = source:consume(rem:match("^([ \t]*=[ \t]*)(.*)$")) local sdefault = source:consume(rem:match("^(=)(.*)$"))
default, rem = expression_to_ast(source, sdefault, limit_pattern, assignment_priority) default, rem = expression_to_ast(source, options, sdefault, assignment_priority)
end end
end end

View file

@ -1,7 +1,7 @@
local function_parameter = require("anselme.parser.expression.contextual.function_parameter") local function_parameter = require("anselme.parser.expression.contextual.function_parameter")
return function_parameter { return function_parameter {
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
return function_parameter:parse(source, str, limit_pattern, true) return function_parameter:parse(source, options, str, true)
end end
} }

View file

@ -9,37 +9,42 @@ return primary {
match = function(self, str) match = function(self, str)
return str:match("^%(") return str:match("^%(")
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local parameters = ParameterTuple:new() local parameters = ParameterTuple:new()
local rem = source:consume(str:match("^(%()(.*)$")) local rem = source:consume(str:match("^(%()(.*)$"))
rem = source:consume_leading_whitespace(options, rem)
-- i would LOVE to reuse the regular list parsing code for this, but unfortunately the list parsing code -- i would LOVE to reuse the regular list parsing code for this, but unfortunately the list parsing code
-- itself depends on this and expect this to be available quite early, and it's ANNOYING -- itself depends on this and expect this to be available quite early, and it's ANNOYING
while not rem:match("^[ \t]*%)") do while not rem:match("^%)") do
-- parameter -- parameter
local func_param local func_param
func_param, rem = function_parameter:expect(source, rem, limit_pattern) func_param, rem = function_parameter:expect(source, options, rem)
rem = source:consume_leading_whitespace(options, rem)
-- next! comma separator -- next! comma separator
if not rem:match("^[ \t]*%)") then if not rem:match("^%)") then
if not rem:match("^[ \t]*,") then if not rem:match("^,") then
error(("unexpected %q at end of argument list"):format(rem:match("^[^\n]*")), 0) error(("unexpected %q at end of argument list"):format(rem:match("^[^\n]*")), 0)
end end
rem = source:consume(rem:match("^([ \t]*,)(.*)$")) rem = source:consume(rem:match("^(,)(.*)$"))
rem = source:consume_leading_whitespace(options, rem)
end end
-- add -- add
parameters:insert(func_param) parameters:insert(func_param)
end end
rem = rem:match("^[ \t]*%)(.*)$") rem = rem:match("^%)(.*)$")
rem = source:consume_leading_whitespace(options, rem)
-- assigment param -- assigment param
if rem:match("^[ \t]*=") then if rem:match("^=") then
rem = source:consume(rem:match("^([ \t]*=[ \t]*)(.*)$")) rem = source:consume(rem:match("^(=)(.*)$"))
rem = source:consume_leading_whitespace(options, rem)
local func_param local func_param
func_param, rem = function_parameter_no_default:expect(source, rem, limit_pattern) func_param, rem = function_parameter_no_default:expect(source, options, rem)
parameters:insert_assignment(func_param) parameters:insert_assignment(func_param)
end end

View file

@ -13,12 +13,12 @@ return primary {
return false return false
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local start_source = source:clone() local start_source = source:clone()
local rem = source:consume(str:match("^(#)(.-)$")) local rem = source:consume(str:match("^(#)(.-)$"))
local ident local ident
ident, rem = identifier:parse(source, rem) ident, rem = identifier:parse(source, options, rem)
return Anchor:new(ident.name):set_source(start_source), rem return Anchor:new(ident.name):set_source(start_source), rem
end end

View file

@ -8,7 +8,7 @@ return primary {
return str:match("^_") return str:match("^_")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local rem = source:consume(str:match("^(_)(.-)$")) local rem = source:consume(str:match("^(_)(.-)$"))
return Call:new(Identifier:new("_"), ArgumentTuple:new()):set_source(source_start), rem return Call:new(Identifier:new("_"), ArgumentTuple:new()):set_source(source_start), rem

View file

@ -26,18 +26,19 @@ local function_parameter_maybe_parenthesis = function_parameter_no_default {
return function_parameter_no_default:match(str) return function_parameter_no_default:match(str)
end end
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
if str:match("^%(") then if str:match("^%(") then
str = source:consume(str:match("^(%()(.*)$")) str = source:consume(str:match("^(%()(.*)$"))
local exp, rem = function_parameter_no_default:parse(source, str, limit_pattern) local exp, rem = function_parameter_no_default:parse(source, options, str)
rem = source:consume_leading_whitespace(options, rem)
if not rem:match("^[ \t]*%)") then error(("unexpected %q at end of parenthesis"):format(rem:match("^[^\n]*")), 0) end if not rem:match("^%)") then error(("unexpected %q at end of parenthesis"):format(rem:match("^[^\n]*")), 0) end
rem = source:consume(rem:match("^([ \t]*%))(.-)$")) rem = source:consume(rem:match("^(%))(.-)$"))
return exp, rem return exp, rem
else else
return function_parameter_no_default:parse(source, str, limit_pattern) return function_parameter_no_default:parse(source, options, str)
end end
end end
} }
@ -46,7 +47,7 @@ local function_parameter_maybe_parenthesis = function_parameter_no_default {
-- :$-parameter exp -- :$-parameter exp
-- returns symbol, parameter_tuple, rem if success -- returns symbol, parameter_tuple, rem if success
-- return nil otherwise -- return nil otherwise
local function search_prefix_signature(modifiers, source, str, limit_pattern) local function search_prefix_signature(modifiers, source, options, str)
for _, pfx in ipairs(prefixes) do for _, pfx in ipairs(prefixes) do
local prefix = pfx[1] local prefix = pfx[1]
local prefix_pattern = "[ \t]*"..escape(prefix).."[ \t]*" local prefix_pattern = "[ \t]*"..escape(prefix).."[ \t]*"
@ -57,7 +58,7 @@ local function search_prefix_signature(modifiers, source, str, limit_pattern)
-- parameters -- parameters
local parameter local parameter
parameter, rem = function_parameter_maybe_parenthesis:expect(source, rem, limit_pattern) parameter, rem = function_parameter_maybe_parenthesis:expect(source, options, rem)
local parameters = ParameterTuple:new() local parameters = ParameterTuple:new()
parameters:insert(parameter) parameters:insert(parameter)
@ -72,10 +73,10 @@ end
-- :$parameterA + parameterB exp -- :$parameterA + parameterB exp
-- returns symbol, parameter_tuple, rem if success -- returns symbol, parameter_tuple, rem if success
-- return nil otherwise -- return nil otherwise
local function search_infix_signature(modifiers, source, str, limit_pattern) local function search_infix_signature(modifiers, source, options, str)
if function_parameter_maybe_parenthesis:match(str) then if function_parameter_maybe_parenthesis:match(str) then
local src = source:clone() -- operate on clone source since search success is not yet guaranteed local src = source:clone() -- operate on clone source since search success is not yet guaranteed
local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, str, limit_pattern) local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, options, str)
local parameters = ParameterTuple:new() local parameters = ParameterTuple:new()
parameters:insert(parameter_a) parameters:insert(parameter_a)
@ -91,7 +92,7 @@ local function search_infix_signature(modifiers, source, str, limit_pattern)
-- parameters -- parameters
if function_parameter_maybe_parenthesis:match(rem) then if function_parameter_maybe_parenthesis:match(rem) then
local parameter_b local parameter_b
parameter_b, rem = function_parameter_maybe_parenthesis:parse(src, rem, limit_pattern) parameter_b, rem = function_parameter_maybe_parenthesis:parse(src, options, rem)
parameters:insert(parameter_b) parameters:insert(parameter_b)
@ -109,10 +110,10 @@ end
-- :$parameter! exp -- :$parameter! exp
-- returns symbol, parameter_tuple, rem if success -- returns symbol, parameter_tuple, rem if success
-- return nil otherwise -- return nil otherwise
local function search_suffix_signature(modifiers, source, str, limit_pattern) local function search_suffix_signature(modifiers, source, options, str)
if function_parameter_maybe_parenthesis:match(str) then if function_parameter_maybe_parenthesis:match(str) then
local src = source:clone() -- operate on clone source since search success is not yet guaranteed local src = source:clone() -- operate on clone source since search success is not yet guaranteed
local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, str, limit_pattern) local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, options, str)
local parameters = ParameterTuple:new() local parameters = ParameterTuple:new()
parameters:insert(parameter_a) parameters:insert(parameter_a)
@ -136,10 +137,10 @@ end
-- :$identifier(parameter_tuple, ...) exp -- :$identifier(parameter_tuple, ...) exp
-- returns symbol, parameter_tuple, rem if success -- returns symbol, parameter_tuple, rem if success
-- return nil otherwise -- return nil otherwise
local function search_function_signature(modifiers, source, str, limit_pattern) local function search_function_signature(modifiers, source, options, str)
if identifier:match(str) then if identifier:match(str) then
local name_source = source:clone() local name_source = source:clone()
local name, rem = identifier:parse(source, str, limit_pattern) local name, rem = identifier:parse(source, options, str)
-- name -- name
local symbol = name:to_symbol(modifiers):set_source(name_source) local symbol = name:to_symbol(modifiers):set_source(name_source)
@ -147,7 +148,7 @@ local function search_function_signature(modifiers, source, str, limit_pattern)
-- parse eventual parameters -- parse eventual parameters
local parameters local parameters
if parameter_tuple:match(rem) then if parameter_tuple:match(rem) then
parameters, rem = parameter_tuple:parse(source, rem) parameters, rem = parameter_tuple:parse(source, options, rem)
else else
parameters = ParameterTuple:new() parameters = ParameterTuple:new()
end end
@ -161,7 +162,7 @@ return primary {
return str:match("^%::?&?@?%$") return str:match("^%::?&?@?%$")
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local mod_const, mod_alias, mod_exported, rem = source:consume(str:match("^(%:(:?)(&?)(@?)%$)(.-)$")) local mod_const, mod_alias, mod_exported, rem = source:consume(str:match("^(%:(:?)(&?)(@?)%$)(.-)$"))
@ -174,16 +175,16 @@ return primary {
-- search for a valid signature -- search for a valid signature
local symbol, parameters local symbol, parameters
local s, p, r = search_prefix_signature(modifiers, source, rem, limit_pattern) local s, p, r = search_prefix_signature(modifiers, source, options, rem)
if s then symbol, parameters, rem = s, p, r if s then symbol, parameters, rem = s, p, r
else else
s, p, r = search_infix_signature(modifiers, source, rem, limit_pattern) s, p, r = search_infix_signature(modifiers, source, options, rem)
if s then symbol, parameters, rem = s, p, r if s then symbol, parameters, rem = s, p, r
else else
s, p, r = search_suffix_signature(modifiers, source, rem, limit_pattern) s, p, r = search_suffix_signature(modifiers, source, options, rem)
if s then symbol, parameters, rem = s, p, r if s then symbol, parameters, rem = s, p, r
else else
s, p, r = search_function_signature(modifiers, source, rem, limit_pattern) s, p, r = search_function_signature(modifiers, source, options, rem)
if s then symbol, parameters, rem = s, p, r end if s then symbol, parameters, rem = s, p, r end
end end
end end
@ -193,7 +194,7 @@ return primary {
if symbol then if symbol then
-- parse expression -- parse expression
local right local right
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, operator_priority["$_"]) s, right, rem = pcall(expression_to_ast, source, options, rem, operator_priority["$_"])
if not s then error(("invalid expression in function definition: %s"):format(right), 0) end if not s then error(("invalid expression in function definition: %s"):format(right), 0) end
-- return function -- return function

View file

@ -27,7 +27,7 @@ return primary {
return false return false
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
for _, pat in ipairs(identifier_patterns) do for _, pat in ipairs(identifier_patterns) do
if str:match("^"..pat) then if str:match("^"..pat) then
local start_source = source:clone() local start_source = source:clone()

View file

@ -8,7 +8,7 @@ return primary {
return str:match("^\n") return str:match("^\n")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
-- implicit _, do not consume the newline -- implicit _, do not consume the newline
local r = Call:new(Identifier:new("_"), ArgumentTuple:new()):set_source(source) local r = Call:new(Identifier:new("_"), ArgumentTuple:new()):set_source(source)
r.explicit = false r.explicit = false

View file

@ -33,14 +33,14 @@ local primaries = {
return { return {
-- returns exp, rem if expression found -- returns exp, rem if expression found
-- returns nil if no expression found -- returns nil if no expression found
search = function(self, source, str, limit_pattern) search = function(self, source, options, str)
str = source:consume(str:match("^([ \t]*)(.*)$")) str = source:consume_leading_whitespace(options, str)
-- if there is a comment, restart the parsing after the comment ends -- if there is a comment, restart the parsing after the comment ends
local c, c_rem = comment:search(source, str, limit_pattern) local c, c_rem = comment:search(source, options, str)
if c then return self:search(source, c_rem, limit_pattern) end if c then return self:search(source, options, c_rem) end
-- search primary -- search primary
for _, primary in ipairs(primaries) do for _, primary in ipairs(primaries) do
local exp, rem = primary:search(source, str, limit_pattern) local exp, rem = primary:search(source, options, str)
if exp then return exp, rem end if exp then return exp, rem end
end end
end end

View file

@ -6,7 +6,7 @@ return primary {
match = function(self, str) match = function(self, str)
return str:match("^%d*%.%d+") or str:match("^%d+") return str:match("^%d*%.%d+") or str:match("^%d+")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local start_source = source:clone() local start_source = source:clone()
local d, r = str:match("^(%d*%.%d+)(.*)$") local d, r = str:match("^(%d*%.%d+)(.*)$")
if not d then if not d then

View file

@ -11,20 +11,24 @@ return primary {
match = function(self, str) match = function(self, str)
return str:match("^%(") return str:match("^%(")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local start_source = source:clone() local start_source = source:clone()
local opts = options:with{ limit_pattern = "%)", allow_newlines = true }
local rem = source:consume(str:match("^(%()(.*)$")) local rem = source:consume(str:match("^(%()(.*)$"))
rem = source:consume_leading_whitespace(opts, rem)
local exp local exp
if rem:match("^%s*%)") then if rem:match("^%)") then
exp = Nil:new() exp = Nil:new()
else else
local s local s
s, exp, rem = pcall(expression_to_ast, source, rem, "%)") s, exp, rem = pcall(expression_to_ast, source, opts, rem)
if not s then error("invalid expression inside parentheses: "..exp, 0) end if not s then error("invalid expression inside parentheses: "..exp, 0) end
if not rem:match("^%s*%)") then error(("unexpected %q at end of parenthesis"):format(rem:match("^[^\n]*")), 0) end rem = source:consume_leading_whitespace(opts, rem)
if not rem:match("^%)") then error(("unexpected %q at end of parenthesis"):format(rem:match("^[^\n]*")), 0) end
end end
rem = source:consume(rem:match("^(%s*%))(.*)$")) rem = source:consume(rem:match("^(%))(.*)$"))
return exp:set_source(start_source), rem return exp:set_source(start_source), rem
end end

View file

@ -12,7 +12,7 @@ return prefix {
operator = "$", operator = "$",
priority = operator_priority["$_"], priority = operator_priority["$_"],
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)
local rem = source:consume(str:match("^("..escaped..")(.*)$")) local rem = source:consume(str:match("^("..escaped..")(.*)$"))
@ -20,14 +20,14 @@ return prefix {
-- parse eventual parameters -- parse eventual parameters
local parameters local parameters
if parameter_tuple:match(rem) then if parameter_tuple:match(rem) then
parameters, rem = parameter_tuple:parse(source, rem) parameters, rem = parameter_tuple:parse(source, options, rem)
else else
parameters = ParameterTuple:new() parameters = ParameterTuple:new()
end end
-- parse expression -- parse expression
local s, right local s, right
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, self.priority) s, right, rem = pcall(expression_to_ast, source, options, rem, self.priority)
if not s then error(("invalid expression after unop %q: %s"):format(self.operator, right), 0) end if not s then error(("invalid expression after unop %q: %s"):format(self.operator, right), 0) end
return Function:with_return_boundary(parameters, right):set_source(source_start), rem return Function:with_return_boundary(parameters, right):set_source(source_start), rem

View file

@ -17,12 +17,12 @@ return primary {
return str:match("^"..escaped) return str:match("^"..escaped)
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)
local sright = source:consume(str:match("^("..escaped..")(.*)$")) local sright = source:consume(str:match("^("..escaped..")(.*)$"))
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority) local s, right, rem = pcall(expression_to_ast, source, options, sright, self.priority)
if not s then error(("invalid expression after prefix operator %q: %s"):format(self.operator, right), 0) end if not s then error(("invalid expression after prefix operator %q: %s"):format(self.operator, right), 0) end
return self:build_ast(right):set_source(source_start), rem return self:build_ast(right):set_source(source_start), rem

View file

@ -6,12 +6,12 @@ local ast = require("anselme.ast")
local Nil = ast.Nil local Nil = ast.Nil
return prefix { return prefix {
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local source_start = source:clone() local source_start = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)
local sright = source:consume(str:match("^("..escaped..")(.*)$")) local sright = source:consume(str:match("^("..escaped..")(.*)$"))
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority) local s, right, rem = pcall(expression_to_ast, source, options, sright, self.priority)
if not s then if not s then
return self:build_ast(Nil:new()):set_source(source_start), sright return self:build_ast(Nil:new()):set_source(source_start), sright
else else

View file

@ -5,11 +5,11 @@ return class {
-- returns exp, rem if expression found -- returns exp, rem if expression found
-- returns nil if no expression found -- returns nil if no expression found
search = function(self, source, str, limit_pattern) search = function(self, source, options, str)
if not self:match(str) then if not self:match(str) then
return nil return nil
end end
return self:parse(source, str, limit_pattern) return self:parse(source, options, str)
end, end,
-- return bool -- return bool
-- (not needed if you redefined :search) -- (not needed if you redefined :search)
@ -18,15 +18,15 @@ return class {
end, end,
-- return AST, rem -- return AST, rem
-- (not needed if you redefined :search) -- (not needed if you redefined :search)
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
error("unimplemented") error("unimplemented")
end, end,
-- class helpers -- -- class helpers --
-- return AST, rem -- return AST, rem
expect = function(self, source, str, limit_pattern) expect = function(self, source, options, str)
local exp, rem = self:search(source, str, limit_pattern) local exp, rem = self:search(source, options, str)
if not exp then error(("expected %s but got %s"):format(self.type, str)) end if not exp then error(("expected %s but got %s"):format(self.type, str)) end
return exp, rem return exp, rem
end end

View file

@ -27,7 +27,8 @@ return primary {
match = function(self, str) match = function(self, str)
return str:match("^"..self.start_pattern) return str:match("^"..self.start_pattern)
end, end,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local limit_pattern = options.limit_pattern
local interpolation = self.interpolation:new() local interpolation = self.interpolation:new()
local stop_pattern = escape(self.stop_char) local stop_pattern = escape(self.stop_char)
@ -52,11 +53,13 @@ return primary {
-- interpolated expression -- interpolated expression
if rem:match("^%{") then if rem:match("^%{") then
local opts = options:with { limit_pattern = "%}", allow_newlines = false }
local ok, exp local ok, exp
ok, exp, rem = pcall(expression_to_ast, source, source:consume(rem:match("^(%{)(.*)$")), "%}") ok, exp, rem = pcall(expression_to_ast, source, opts, source:consume(rem:match("^(%{)(.*)$")))
if not ok then error("invalid expression inside interpolation: "..exp, 0) end if not ok then error("invalid expression inside interpolation: "..exp, 0) end
if not rem:match("^[ \t]*%}") then error(("unexpected %q at end of interpolation"):format(rem:match("^[^\n]*")), 0) end rem = source:consume_leading_whitespace(opts, rem)
rem = source:consume(rem:match("^([ \t]*%})(.*)$")) if not rem:match("^%}") then error(("unexpected %q at end of interpolation"):format(rem:match("^[^\n]*")), 0) end
rem = source:consume(rem:match("^(%})(.*)$"))
interpolation:insert(exp) interpolation:insert(exp)
-- escape sequence -- escape sequence
elseif rem:match("^\\") then elseif rem:match("^\\") then

View file

@ -9,8 +9,8 @@ return primary {
return str:match("^%{") return str:match("^%{")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local l, rem = tuple:parse_tuple(source, str, "{", '}') local l, rem = tuple:parse_tuple(source, options, str, "{", '}')
return Struct:from_tuple(l), rem return Struct:from_tuple(l), rem
end end

View file

@ -14,7 +14,7 @@ return primary {
return false return false
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
local mod_const, mod_alias, mod_export, rem = source:consume(str:match("^(%:(:?)(&?)(@?))(.-)$")) local mod_const, mod_alias, mod_export, rem = source:consume(str:match("^(%:(:?)(&?)(@?))(.-)$"))
local constant, alias, value_check_exp, exported local constant, alias, value_check_exp, exported
@ -25,13 +25,13 @@ return primary {
-- name -- name
local ident local ident
ident, rem = identifier:parse(source, rem) ident, rem = identifier:parse(source, options, rem)
-- value check -- value check
local nil_val = Nil:new() local nil_val = Nil:new()
if value_check:match(rem, 0, nil_val) then if value_check:match(rem, 0, nil_val) then
local exp local exp
exp, rem = value_check:parse(source, rem, nil, 0, nil_val) exp, rem = value_check:parse(source, options, rem, 0, nil_val)
value_check_exp = exp.arguments.positional[2] value_check_exp = exp.arguments.positional[2]
end end

View file

@ -10,9 +10,9 @@ return string {
allow_implicit_stop = true, allow_implicit_stop = true,
interpolation = TextInterpolation, interpolation = TextInterpolation,
parse = function(self, source, str, limit_pattern) parse = function(self, source, options, str)
local start_source = source:clone() local start_source = source:clone()
local interpolation, rem = string.parse(self, source, str, limit_pattern) local interpolation, rem = string.parse(self, source, options, str)
-- remove terminal space -- remove terminal space
local last = interpolation.list[#interpolation.list] local last = interpolation.list[#interpolation.list]

View file

@ -12,28 +12,32 @@ return primary {
return str:match("^%[") return str:match("^%[")
end, end,
parse = function(self, source, str) parse = function(self, source, options, str)
return self:parse_tuple(source, str, "[", "]") return self:parse_tuple(source, options, str, "[", "]")
end, end,
parse_tuple = function(self, source, str, start_char, end_char) parse_tuple = function(self, source, options, str, start_char, end_char)
local start_source = source:clone() local start_source = source:clone()
local opts = options:with{ limit_pattern = end_char, allow_newlines = true }
local rem = source:consume(str:match("^("..escape(start_char)..")(.*)$")) local rem = source:consume(str:match("^("..escape(start_char)..")(.*)$"))
rem = source:consume_leading_whitespace(opts, rem)
local end_match = escape(end_char) local end_match = escape(end_char)
local l local l
if not rem:match("^[ \t]*"..end_match) then if not rem:match("^"..end_match) then
local s local s
s, l, rem = pcall(expression_to_ast, source, rem, end_match, nil) s, l, rem = pcall(expression_to_ast, source, opts, rem)
if not s then error("invalid expression in list: "..l, 0) end if not s then error("invalid expression in list: "..l, 0) end
rem = source:consume_leading_whitespace(opts, rem)
end end
if not Tuple:is(l) or l.explicit then l = Tuple:new(l) end -- single or no element if not Tuple:is(l) or l.explicit then l = Tuple:new(l) end -- single or no element
if not rem:match("^[ \t]*"..end_match) then if not rem:match("^"..end_match) then
error(("unexpected %q at end of list"):format(rem:match("^[^\n]*")), 0) error(("unexpected %q at end of list"):format(rem:match("^[^\n]*")), 0)
end end
rem = source:consume(rem:match("^([ \t]*"..end_match..")(.*)$")) rem = source:consume(rem:match("^("..end_match..")(.*)$"))
l.explicit = true l.explicit = true
return l:set_source(start_source), rem return l:set_source(start_source), rem

View file

@ -14,6 +14,7 @@ return infix {
match = function(self, str, current_priority, primary) match = function(self, str, current_priority, primary)
local escaped = escape(self.operator) local escaped = escape(self.operator)
-- TODO: doesn't support newline between ! and identifier, event in multiline expression
return self.priority > current_priority and str:match("^"..escaped) and identifier:match(str:match("^"..escaped.."[ \t]*(.-)$")) return self.priority > current_priority and str:match("^"..escaped) and identifier:match(str:match("^"..escaped.."[ \t]*(.-)$"))
end, end,

View file

@ -15,9 +15,9 @@ return infix {
return self.priority > current_priority and identifier:match(str) return self.priority > current_priority and identifier:match(str)
end, end,
parse = function(self, source, str, limit_pattern, current_priority, primary) parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local right, rem = identifier:parse(source, str, limit_pattern) local right, rem = identifier:parse(source, options, str)
local r = Call:new(Identifier:new(self.identifier), ArgumentTuple:new(primary, right)):set_source(start_source) local r = Call:new(Identifier:new(self.identifier), ArgumentTuple:new(primary, right)):set_source(start_source)
r.explicit = false r.explicit = false
return r, rem return r, rem

View file

@ -17,12 +17,12 @@ return secondary {
end, end,
-- return AST, rem -- return AST, rem
parse = function(self, source, str, limit_pattern, current_priority, primary) parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)
local sright = source:consume(str:match("^("..escaped..")(.*)$")) local sright = source:consume(str:match("^("..escaped..")(.*)$"))
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority) local s, right, rem = pcall(expression_to_ast, source, options, sright, self.priority)
if not s then error(("invalid expression after infix operator %q: %s"):format(self.operator, right), 0) end if not s then error(("invalid expression after infix operator %q: %s"):format(self.operator, right), 0) end
return self:build_ast(primary, right):set_source(start_source), rem return self:build_ast(primary, right):set_source(start_source), rem

View file

@ -8,23 +8,23 @@ local expression_to_ast = require("anselme.parser.expression.to_ast")
return infix { return infix {
-- returns exp, rem if expression found -- returns exp, rem if expression found
-- returns nil if no expression found -- returns nil if no expression found
search = function(self, source, str, limit_pattern, current_priority, operating_on_primary) search = function(self, source, options, str, current_priority, operating_on_primary)
if not self:match(str, current_priority, operating_on_primary) then if not self:match(str, current_priority, operating_on_primary) then
return nil return nil
end end
return self:maybe_parse(source, str, limit_pattern, current_priority, operating_on_primary) return self:maybe_parse(source, options, str, current_priority, operating_on_primary)
end, end,
parse = function() error("no guaranteed parse for this operator") end, parse = function() error("no guaranteed parse for this operator") end,
-- return AST, rem -- return AST, rem
-- return nil -- return nil
maybe_parse = function(self, source, str, limit_pattern, current_priority, primary) maybe_parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)
local sright = source:consume(str:match("^("..escaped..")(.*)$")) local sright = source:consume(str:match("^("..escaped..")(.*)$"))
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority) local s, right, rem = pcall(expression_to_ast, source, options, sright, self.priority)
if not s then return nil end if not s then return nil end
return self:build_ast(primary, right):set_source(start_source), rem return self:build_ast(primary, right):set_source(start_source), rem

View file

@ -13,19 +13,20 @@ return infix {
priority = operator_priority["_,_"], priority = operator_priority["_,_"],
-- reminder: this :parse method is also called from primary.list as an helper to build list bracket litterals -- reminder: this :parse method is also called from primary.list as an helper to build list bracket litterals
parse = function(self, source, str, limit_pattern, current_priority, primary) parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local l = Tuple:new() local l = Tuple:new()
l:insert(primary) l:insert(primary)
local escaped = escape(self.operator) local escaped = escape(self.operator)
local rem = str local rem = str
while rem:match("^[ \t]*"..escaped) do while rem:match("^"..escaped) do
rem = source:consume(rem:match("^([ \t]*"..escaped..")(.*)$")) rem = source:consume(rem:match("^("..escaped..")(.*)$"))
local s, right local s, right
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, self.priority) s, right, rem = pcall(expression_to_ast, source, options, rem, self.priority)
if not s then error(("invalid expression after binop %q: %s"):format(self.operator, right), 0) end if not s then error(("invalid expression after binop %q: %s"):format(self.operator, right), 0) end
rem = source:consume_leading_whitespace(options, rem)
l:insert(right) l:insert(right)
end end

View file

@ -51,18 +51,18 @@ end
return { return {
-- returns exp, rem if expression found -- returns exp, rem if expression found
-- returns nil if no expression found -- returns nil if no expression found
search = function(self, source, str, limit_pattern, current_priority, primary) search = function(self, source, options, str, current_priority, primary)
str = source:consume(str:match("^([ \t]*)(.*)$")) str = source:consume_leading_whitespace( options,str)
-- if there is a comment, restart the parsing after the comment ends -- if there is a comment, restart the parsing after the comment ends
local c, c_rem = comment:search(source, str, limit_pattern) local c, c_rem = comment:search(source, options, str)
if c then if c then
local ce, ce_rem = self:search(source, c_rem, limit_pattern, current_priority, primary) local ce, ce_rem = self:search(source, options, c_rem, current_priority, primary)
if ce then return ce, ce_rem if ce then return ce, ce_rem
else return primary, c_rem end -- noop else return primary, c_rem end -- noop
end end
-- search secondary -- search secondary
for _, secondary in ipairs(secondaries) do for _, secondary in ipairs(secondaries) do
local exp, rem = secondary:search(source, str, limit_pattern, current_priority, primary) local exp, rem = secondary:search(source, options, str, current_priority, primary)
if exp then return exp, rem end if exp then return exp, rem end
end end
end end

View file

@ -5,11 +5,11 @@ return class {
-- returns exp, rem if expression found -- returns exp, rem if expression found
-- returns nil if no expression found -- returns nil if no expression found
search = function(self, source, str, limit_pattern, current_priority, operating_on_primary) search = function(self, source, options, str, current_priority, operating_on_primary)
if not self:match(str, current_priority, operating_on_primary) then if not self:match(str, current_priority, operating_on_primary) then
return nil return nil
end end
return self:parse(source, str, limit_pattern, current_priority, operating_on_primary) return self:parse(source, options, str, current_priority, operating_on_primary)
end, end,
-- return bool -- return bool
-- (not needed if you redefined :search) -- (not needed if you redefined :search)
@ -19,15 +19,15 @@ return class {
-- return AST, rem -- return AST, rem
-- (not needed if you redefined :search) -- (not needed if you redefined :search)
-- assumes that :match was checked before, and can not return nil (may error though) -- assumes that :match was checked before, and can not return nil (may error though)
parse = function(self, source, str, limit_pattern, current_priority, operating_on_primary) parse = function(self, source, options, str, current_priority, operating_on_primary)
error("unimplemented") error("unimplemented")
end, end,
-- class helpers -- -- class helpers --
-- return AST, rem -- return AST, rem
expect = function(self, source, str, limit_pattern, current_priority, operating_on_primary) expect = function(self, source, options, str, current_priority, operating_on_primary)
local exp, rem = self:search(source, str, limit_pattern, current_priority, operating_on_primary) local exp, rem = self:search(source, options, str, current_priority, operating_on_primary)
if not exp then error(("expected %s but got %s"):format(self.type, str)) end if not exp then error(("expected %s but got %s"):format(self.type, str)) end
return exp, rem return exp, rem
end end

View file

@ -17,17 +17,17 @@ return secondary {
return self.priority > current_priority and (parenthesis:match(str) or tuple:match(str) or struct:match(str)) return self.priority > current_priority and (parenthesis:match(str) or tuple:match(str) or struct:match(str))
end, end,
parse = function(self, source, str, limit_pattern, current_priority, primary) parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local args = ArgumentTuple:new() local args = ArgumentTuple:new()
local exp, rem local exp, rem
if parenthesis:match(str) then if parenthesis:match(str) then
exp, rem = parenthesis:parse(source, str, limit_pattern) exp, rem = parenthesis:parse(source, options, str)
if Nil:is(exp) then if Nil:is(exp) then
if str:match("^%([ \t]*%([ \t]*%)[ \t]*%)") then -- special case: single nil argument if str:match("^%([ \t\n]*%([ \t\n]*%)[ \t\n]*%)") then -- special case: single nil argument
exp = Tuple:new(Nil:new()) exp = Tuple:new(Nil:new())
else -- no arguments else -- no arguments
exp = Tuple:new() exp = Tuple:new()
@ -36,10 +36,10 @@ return secondary {
exp = Tuple:new(exp) exp = Tuple:new(exp)
end end
elseif tuple:match(str) then elseif tuple:match(str) then
exp, rem = tuple:parse(source, str, limit_pattern) exp, rem = tuple:parse(source, options, str)
exp = Tuple:new(exp) exp = Tuple:new(exp)
else else
exp, rem = struct:parse(source, str, limit_pattern) exp, rem = struct:parse(source, options, str)
exp = Tuple:new(exp) exp = Tuple:new(exp)
end end

View file

@ -16,7 +16,7 @@ return secondary {
return self.priority > current_priority and str:match("^"..escaped) return self.priority > current_priority and str:match("^"..escaped)
end, end,
parse = function(self, source, str, limit_pattern, current_priority, primary) parse = function(self, source, options, str, current_priority, primary)
local start_source = source:clone() local start_source = source:clone()
local escaped = escape(self.operator) local escaped = escape(self.operator)

View file

@ -4,11 +4,11 @@ local primary, secondary
-- parse an expression, starting from a secondary element operating on operating_on_primary -- parse an expression, starting from a secondary element operating on operating_on_primary
-- returns expr, remaining -- returns expr, remaining
local function from_secondary(source, s, limit_pattern, current_priority, operating_on_primary) local function from_secondary(source, options, s, current_priority, operating_on_primary)
current_priority = current_priority or 0 current_priority = current_priority or 0
-- secondary elements -- secondary elements
local exp, rem = secondary:search(source, s, limit_pattern, current_priority, operating_on_primary) local exp, rem = secondary:search(source, options, s, current_priority, operating_on_primary)
if exp then return from_secondary(source, rem, limit_pattern, current_priority, exp) end if exp then return from_secondary(source, options, rem, current_priority, exp) end
-- nothing to apply on primary -- nothing to apply on primary
return operating_on_primary, s return operating_on_primary, s
end end
@ -18,11 +18,11 @@ end
-- limit_pattern: set to a string pattern that will trigger the end of elements that would otherwise consume everything until end-of-line (pattern is not consumed) -- limit_pattern: set to a string pattern that will trigger the end of elements that would otherwise consume everything until end-of-line (pattern is not consumed)
-- fallback_exp: if no primary expression can be found, will return this instead. Used to avoid raising an error where an empty or comment-only expression is allowed. -- fallback_exp: if no primary expression can be found, will return this instead. Used to avoid raising an error where an empty or comment-only expression is allowed.
-- return expr, remaining -- return expr, remaining
local function expression_to_ast(source, s, limit_pattern, current_priority) local function expression_to_ast(source, options, s, current_priority)
current_priority = current_priority or 0 current_priority = current_priority or 0
-- primary elements -- primary elements
local exp, rem = primary:search(source, s, limit_pattern) local exp, rem = primary:search(source, options, s)
if exp then return from_secondary(source, rem, limit_pattern, current_priority, exp) end if exp then return from_secondary(source, options, rem, current_priority, exp) end
-- no valid primary expression -- no valid primary expression
error(("no valid expression after %q"):format(s), 0) error(("no valid expression after %q"):format(s), 0)
end end
@ -33,7 +33,7 @@ primary = require("anselme.parser.expression.primary")
secondary = require("anselme.parser.expression.secondary") secondary = require("anselme.parser.expression.secondary")
-- return expr, remaining -- return expr, remaining
return function(source, s, limit_pattern, current_priority, operating_on_primary) return function(source, options, s, current_priority, operating_on_primary)
if operating_on_primary then return from_secondary(source, s, limit_pattern, current_priority, operating_on_primary) if operating_on_primary then return from_secondary(source, options, s, current_priority, operating_on_primary)
else return expression_to_ast(source, s, limit_pattern, current_priority) end else return expression_to_ast(source, options, s, current_priority) end
end end

View file

@ -1,5 +1,6 @@
local block = require("anselme.parser.expression.block") local block = require("anselme.parser.expression.block")
local Source = require("anselme.parser.Source") local Source = require("anselme.parser.Source")
local Options = require("anselme.parser.Options")
local function expect_end(exp, rem) local function expect_end(exp, rem)
if rem:match("[^%s]") then if rem:match("[^%s]") then
@ -11,5 +12,5 @@ end
-- parse code (string) with the associated source (Source) -- parse code (string) with the associated source (Source)
-- the returned AST tree is stateless and can be stored/evaluated/etc as you please -- the returned AST tree is stateless and can be stored/evaluated/etc as you please
return function(code, source) return function(code, source)
return expect_end(block(Source:new(source, 1, 1), code)) return expect_end(block(Source:new(source, 1, 1), Options:new(), code))
end end

View file

@ -6,6 +6,7 @@ local ast = require("anselme.ast")
local to_anselme = require("anselme.common.to_anselme") local to_anselme = require("anselme.common.to_anselme")
local unpack = table.unpack or unpack local unpack = table.unpack or unpack
local Source, Options
local LuaCall, Environment, Node local LuaCall, Environment, Node
local parameter_tuple = require("anselme.parser.expression.contextual.parameter_tuple") local parameter_tuple = require("anselme.parser.expression.contextual.parameter_tuple")
@ -50,10 +51,10 @@ local ScopeStack = class {
-- if `raw_mode` is true, no anselme-to/from-lua conversion will be performed in the function -- if `raw_mode` is true, no anselme-to/from-lua conversion will be performed in the function
-- the function will receive the state followed by AST nodes as arguments, and is expected to return an AST node -- the function will receive the state followed by AST nodes as arguments, and is expected to return an AST node
define_lua = function(self, name, value, func, raw_mode) define_lua = function(self, name, value, func, raw_mode)
local source = require("anselme.parser.Source"):new() local source, options = Source:new(), Options:new()
local sym = symbol:parse(source, (":%s"):format(name)) local sym = symbol:parse(source, options, (":%s"):format(name))
if func then if func then
local parameters = parameter_tuple:parse(source, value) local parameters = parameter_tuple:parse(source, options, value)
if not raw_mode then if not raw_mode then
local original_func = func local original_func = func
func = function(state, ...) func = function(state, ...)
@ -151,5 +152,7 @@ local ScopeStack = class {
package.loaded[...] = ScopeStack package.loaded[...] = ScopeStack
LuaCall, Environment, Node = ast.LuaCall, ast.Environment, ast.abstract.Node LuaCall, Environment, Node = ast.LuaCall, ast.Environment, ast.abstract.Node
Source = require("anselme.parser.Source")
Options = require("anselme.parser.Options")
return ScopeStack return ScopeStack

View file

@ -10,12 +10,6 @@ Documentation:
--- ---
Translation.
Do some more fancy scope work to allow the translation to access variables defined in the translation file?
---
Standard library. Standard library.
* Text and string manipulation would make sense, but that would require a full UTF-8/Unicode support library like https://github.com/starwing/luautf8. * Text and string manipulation would make sense, but that would require a full UTF-8/Unicode support library like https://github.com/starwing/luautf8.
@ -27,8 +21,22 @@ Standard library.
Default arguments and initial variables values should pass the value check associated with the variable / parameter. Default arguments and initial variables values should pass the value check associated with the variable / parameter.
Issue: dispatch is decided before evaluating default values. Issue: dispatch is decided before evaluating default values.
---
Multiline string and comments?
---
Error on undefined struct/table key? I copied the Lua behavior but maybe not useful here.
# Can be done later # Can be done later
Translation.
Do some more fancy scope work to allow the translation to access variables defined in the translation file?
---
Server API. Server API.
To be able to use Anselme in another language, it would be nice to be able to access it over some form of IPC. To be able to use Anselme in another language, it would be nice to be able to access it over some form of IPC.
@ -45,6 +53,10 @@ Could be reused for exception handling or other purposes if accessible by the us
--- ---
Custom function for building text/string interpolation.
---
Reduce the number of AST node types ; try to merge similar node and make simpler individuals nodes if possible by composing them. Reduce the number of AST node types ; try to merge similar node and make simpler individuals nodes if possible by composing them.
Won't help with performance but make me feel better, and easier to extend. Anselme should be more minimal is possible. Won't help with performance but make me feel better, and easier to extend. Anselme should be more minimal is possible.
@ -56,15 +68,6 @@ To draw a graph of branches, keep track of used variables and prune the unused o
--- ---
Multiline expressions.
* add the ability to escape newlines
Issue: need a way to correctly track line numbers, the current parser assumes one expression = one source
* allow some expressions to run over several lines (the ones that expect a closing token, like paren/list/structs)
Issue: the line and expression parsing is completely separate
---
Performance: Performance:
* the most terribly great choice is the overload with parameter filtering. * the most terribly great choice is the overload with parameter filtering.

View file

@ -0,0 +1,13 @@
--# run #--
--- text ---
| {}"" {}"{1:1, 2:2, 3:4, 4:6}" {}"" |
--- text ---
| {}"" {}"{1:1, 2:2, 3:4, 4:6}" {}"" |
--- text ---
| {}"" {}"{}" {}"" |
--- text ---
| {}"" {}"{1:1, 2:2, 3:4, 4:3, 5:9, 6:6}" {}"" |
--- return ---
()
--# saved #--
{}

View file

@ -0,0 +1,13 @@
--# run #--
--- text ---
| {}"" {}"[1, 2, 4, 6]" {}"" |
--- text ---
| {}"" {}"[1, 2, 4, 6]" {}"" |
--- text ---
| {}"" {}"[]" {}"" |
--- text ---
| {}"" {}"[1, 2, 4, 3, 9, 6]" {}"" |
--- return ---
()
--# saved #--
{}

View file

@ -0,0 +1,13 @@
--# run #--
--- text ---
| {}"" {}"[1, 2, 4, 6]" {}"" |
--- text ---
| {}"" {}"[1, 2, 4, 6]" {}"" |
--- text ---
| {}"" {}"()" {}"" |
--- text ---
| {}"" {}"[1, 2, 4, 3, 9, 6]" {}"" |
--- return ---
()
--# saved #--
{}

View file

@ -0,0 +1,15 @@
|{{1,2,4,
6}}
|{{
1,2,4
,6}}
|{{
}}
|{{1,2,4,
/* hey */3,
9
/* hoy
,6}}

View file

@ -0,0 +1,15 @@
|{[1,2,4,
6]}
|{[
1,2,4
,6]}
|{[
]}
|{[1,2,4,
/* hey */3,
9
/* hoy
,6]}

View file

@ -0,0 +1,15 @@
|{(1,2,4,
6)}
|{(
1,2,4
,6)}
|{(
)}
|{(1,2,4,
/* hey */3,
9
/* hoy
,6)}