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

[language] Add multiple assignments, assignment and definitions are now function calls

This commit is contained in:
Étienne Fildadut 2024-01-11 01:34:20 +01:00
parent 760181eaf9
commit b6473de4d2
36 changed files with 180 additions and 172 deletions

View file

@ -6,6 +6,7 @@ local operator_priority = require("anselme.common").operator_priority
local ArgumentTuple
ArgumentTuple = ast.abstract.Node {
type = "argument tuple",
hide_in_stacktrace = true,
arguments = nil,

View file

@ -1,39 +0,0 @@
local ast = require("anselme.ast")
local Nil
local operator_priority = require("anselme.common").operator_priority
local Assignment = ast.abstract.Node {
type = "assignment",
identifier = nil,
expression = nil,
init = function(self, identifier, expression)
self.identifier = identifier
self.expression = expression
end,
_format = function(self, ...)
return self.identifier:format(...).." = "..self.expression:format_right(...)
end,
_format_priority = function(self)
return operator_priority["_=_"]
end,
traverse = function(self, fn, ...)
fn(self.identifier, ...)
fn(self.expression, ...)
end,
_eval = function(self, state)
local val = self.expression:eval(state)
state.scope:set(self.identifier, val)
return Nil:new()
end,
}
package.loaded[...] = Assignment
Nil = ast.Nil
return Assignment

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast")
local Identifier
local Identifier, Quote, ArgumentTuple
local regular_operators = require("anselme.common").regular_operators
local operator_priority = require("anselme.common").operator_priority
@ -24,6 +24,9 @@ Call = ast.abstract.Node {
self.func = func
self.arguments = arguments
end,
from_operator = function(self, operator, left, right)
return Call:new(Identifier:new(operator), ArgumentTuple:new(left, right))
end,
_format = function(self, ...)
if self.arguments.arity == 0 then
@ -67,6 +70,14 @@ Call = ast.abstract.Node {
return operator_priority["_!"]
end,
is_infix = function(self, operator)
return Identifier:is(self.func) and self.func.name == operator
and self.arguments.arity == 2 and #self.arguments.positional == 2
end,
is_simple_assignment = function(self)
return self:is_infix("_=_") and Quote:is(self.arguments.positional[1]) and Identifier:is(self.arguments.positional[1].expression)
end,
traverse = function(self, fn, ...)
fn(self.func, ...)
fn(self.arguments, ...)
@ -81,6 +92,6 @@ Call = ast.abstract.Node {
}
package.loaded[...] = Call
Identifier = ast.Identifier
Identifier, Quote, ArgumentTuple = ast.Identifier, ast.Quote, ast.ArgumentTuple
return Call

View file

@ -1,46 +0,0 @@
local ast = require("anselme.ast")
local Nil, Overloadable, Overload
local operator_priority = require("anselme.common").operator_priority
local Definition = ast.abstract.Node {
type = "definition",
symbol = nil,
expression = nil,
init = function(self, symbol, expression)
self.symbol = symbol
self.expression = expression
end,
_format = function(self, ...)
return self.symbol:format(...).." = "..self.expression:format_right(...)
end,
_format_priority = function(self)
return operator_priority["_=_"]
end,
traverse = function(self, fn, ...)
fn(self.symbol, ...)
fn(self.expression, ...)
end,
_eval = function(self, state)
local symbol = self.symbol:eval(state)
local val = self.expression:eval(state)
if Overloadable:issub(val) or Overload:is(val) then
state.scope:define_overloadable(symbol, val)
else
state.scope:define(symbol, val)
end
return Nil:new()
end,
}
package.loaded[...] = Definition
Nil, Overloadable, Overload = ast.Nil, ast.abstract.Overloadable, ast.Overload
return Definition

View file

@ -14,7 +14,10 @@ ParameterTuple = ast.abstract.Node {
eval_depth = 0, -- scope deth where this parametertuple was evaluated, used as secondary specificity
init = function(self, ...)
self.list = {...}
self.list = {}
for _, param in ipairs{...} do
self:insert(param)
end
end,
insert = function(self, val) -- only for construction
assert(not self.assignment, "can't add new parameters after assignment parameter was added")

View file

@ -222,7 +222,7 @@ Node = class {
local r = {}
if Struct:is(tr) then
for context, context_tr in tr:iter() do
table.insert(r, indent..Call:new(Identifier:new("_#_"), ArgumentTuple:new(context, Identifier:new("_"))):format():gsub(" _$", ""))
table.insert(r, indent..Call:from_operator("_#_", context, Identifier:new("_")):format():gsub(" _$", ""))
table.insert(r, build_str(context_tr, level+1))
end
elseif Tuple:is(tr) then
@ -232,7 +232,7 @@ Node = class {
end
else
table.insert(r, indent.."/* "..tr.source.." */")
table.insert(r, indent..Call:new(Identifier:new("_->_"), ArgumentTuple:new(tr, tr)):format())
table.insert(r, indent..Call:from_operator("_->_", tr, tr):format())
end
return table.concat(r, "\n")
end

View file

@ -43,6 +43,7 @@ local common = {
infixes = {
{ ";", 1 },
{ "#", 2 }, { "->", 2 },
{ "=", 3 },
{ "&", 5 }, { "|", 5 },
{ "==", 6 }, { "!=", 6 }, { ">=", 6 }, { "<=", 6 }, { "<", 6 }, { ">", 6 },
{ "+", 7 }, { "-", 7 },
@ -58,7 +59,6 @@ local common = {
[";_"] = 1,
["$_"] = 2,
["_,_"] = 2,
["_=_"] = 3,
["_implicit*_"] = 9, -- just aboce _*_
["_!_"] = 12,
["_()"] = 13 -- just above _!

View file

@ -7,7 +7,7 @@ local expression_to_ast = require("anselme.parser.expression.to_ast")
local escape = require("anselme.common").escape
local ast = require("anselme.ast")
local Symbol, Definition, Function, ParameterTuple = ast.Symbol, ast.Definition, ast.Function, ast.ParameterTuple
local Symbol, Call, Quote, Function, ParameterTuple = ast.Symbol, ast.Call, ast.Quote, ast.Function, ast.ParameterTuple
local regular_operators = require("anselme.common").regular_operators
local prefixes = regular_operators.prefixes
@ -198,7 +198,7 @@ return primary {
-- return function
local fn = Function:with_return_boundary(parameters, right):set_source(source_start)
return Definition:new(symbol, fn):set_source(source_start), rem
return Call:from_operator("_=_", Quote:new(symbol), fn):set_source(source_start), rem
end
end
}

View file

@ -1,12 +1,9 @@
local infix = require("anselme.parser.expression.secondary.infix.infix")
local infix_quote_left = require("anselme.parser.expression.secondary.infix.infix_quote_left")
local escape = require("anselme.common").escape
local operator_priority = require("anselme.common").operator_priority
local ast = require("anselme.ast")
local Identifier, Assignment = ast.Identifier, ast.Assignment
return infix {
return infix_quote_left {
operator = "=",
identifier = "_=_",
priority = operator_priority["_=_"],
@ -14,10 +11,6 @@ return infix {
-- return bool
match = function(self, str, current_priority, primary)
local escaped = escape(self.operator)
return self.priority > current_priority and str:match("^"..escaped) and Identifier:is(primary)
return self.priority > current_priority and str:match("^"..escaped)
end,
build_ast = function(self, left, right)
return Assignment:new(left, right)
end
}

View file

@ -16,15 +16,6 @@ for _, infix in ipairs(infixes) do
-- avoid a lot of unecessary trouble with <= & friends. why would you ever want to use i <= 7 as i = i < 7 anyway.
if not operator_priority["_"..compound_operator.."_"] then
table.insert(generated, assignment {
operator = compound_operator,
identifier = identifier,
build_ast = function(self, left, right)
right = Call:new(Identifier:new(infix_identifier), ArgumentTuple:new(left, right))
return assignment.build_ast(self, left, right)
end
})
table.insert(generated, assignment_call {
operator = compound_operator,
identifier = identifier,
@ -33,6 +24,15 @@ for _, infix in ipairs(infixes) do
return assignment_call.build_ast(self, left, right)
end
})
table.insert(generated, assignment {
operator = compound_operator,
identifier = identifier,
build_ast = function(self, left, right)
right = Call:new(Identifier:new(infix_identifier), ArgumentTuple:new(left, right))
return assignment.build_ast(self, left, right)
end
})
end
end

View file

@ -1,22 +0,0 @@
local infix = require("anselme.parser.expression.secondary.infix.infix")
local escape = require("anselme.common").escape
local operator_priority = require("anselme.common").operator_priority
local ast = require("anselme.ast")
local Definition, Symbol = ast.Definition, ast.Symbol
return infix {
operator = "=",
identifier = "_=_",
priority = operator_priority["_=_"],
match = function(self, str, current_priority, primary)
local escaped = escape(self.operator)
return self.priority > current_priority and str:match("^"..escaped) and Symbol:is(primary)
end,
build_ast = function(self, left, right)
return Definition:new(left, right)
end
}

View file

@ -0,0 +1,11 @@
local infix = require("anselme.parser.expression.secondary.infix.infix")
local ast = require("anselme.ast")
local Call, Identifier, ArgumentTuple, Quote = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.Quote
return infix {
build_ast = function(self, left, right)
left = Quote:new(left)
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
end
}

View file

@ -30,9 +30,8 @@ local secondaries = {
r("infix.call"),
r("infix.index_identifier"),
r("infix.index"),
r("infix.assignment"), -- deported after equal
r("infix.assignment_call"),
r("infix.definition"),
r("infix.assignment"), -- deported after equal
r("infix.pair"), -- deported after value_check
-- unary suffix operators

View file

@ -8,7 +8,7 @@ local struct = require("anselme.parser.expression.primary.struct")
local operator_priority = require("anselme.common").operator_priority
local ast = require("anselme.ast")
local Call, ArgumentTuple, Tuple, Assignment, Nil = ast.Call, ast.ArgumentTuple, ast.Tuple, ast.Assignment, ast.Nil
local Call, ArgumentTuple, Tuple, Nil = ast.Call, ast.ArgumentTuple, ast.Tuple, ast.Nil
return secondary {
priority = operator_priority["_()"],
@ -44,8 +44,9 @@ return secondary {
end
for _, v in ipairs(exp.list) do
if Assignment:is(v) then
args:add_named(v.identifier, v.expression)
if Call:is(v) and v:is_simple_assignment() then
local pos = v.arguments.positional
args:add_named(pos[1].expression, pos[2])
else
args:add_positional(v)
end

View file

@ -22,6 +22,7 @@ local resume_manager = class {
-- same as :push, but the resume will stop immediately after reaching the target or a node containing the target
-- (we will stop even if the node is not directly reached - this is used to run a specific line containing a node,
-- notably for Definition of exported variables)
-- TODO unused?
push_no_continue = function(self, state, target)
assert(ResumeTarget:issub(target), "can only resume to a resume target")
state.scope:push_partial(resume_target_identifier, resume_no_continue_identifier, resume_environment_identifier)

View file

@ -0,0 +1,58 @@
local ast = require("anselme.ast")
local Nil, Boolean, LuaCall, ParameterTuple, FunctionParameter, Identifier, Overloadable, Overload, Call, Quote = ast.Nil, ast.Boolean, ast.LuaCall, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.abstract.Overloadable, ast.Overload, ast.Call, ast.Quote
return {
{
"is tuple", "(exp)",
function(state, exp)
return Boolean:new(exp.type == "tuple")
end
},
-- for internal usage, user should'nt interact with quotes
{
"is quote", "(exp)",
function(state, exp)
return Boolean:new(exp.type == "quote")
end
},
-- for internal usage, user should'nt interact with quotes
{
"is quoted", "(type::($(x) x!type == \"string\"))",
function(state, type)
return LuaCall:make_function(state,
ParameterTuple:new(FunctionParameter:new(Identifier:new("quote"), nil, Identifier:new("is quote"))),
function(state, quote)
return Boolean:new(quote.expression.type == type.string)
end
)
end
},
{
"_=_", "(quote::is quoted(\"identifier\"), value)", function(state, quote, value)
state.scope:set(quote.expression, value)
return Nil:new()
end
},
{
"_=_", "(quote::is quoted(\"symbol\"), value)", function(state, quote, value)
local symbol = quote.expression:eval(state)
if Overloadable:issub(value) or Overload:is(value) then
state.scope:define_overloadable(symbol, value)
else
state.scope:define(symbol, value)
end
return Nil:new()
end
},
{
"_=_", "(quote::is quoted(\"tuple\"), value::is tuple)", function(state, quote, tuple)
assert(quote.expression:len() == tuple:len(), "left and right tuple do no have the same number of elements")
for i, left in quote.expression:iter() do
Call:from_operator("_=_", Quote:new(left), tuple:get(i)):eval(state)
end
return Nil:new()
end
},
}

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast")
local Nil, Boolean, Definition = ast.Nil, ast.Boolean, ast.Definition
local Nil, Boolean, Call, Quote = ast.Nil, ast.Boolean, ast.Call, ast.Quote
local assert0 = require("anselme.common").assert0
local parser = require("anselme.parser")
@ -36,7 +36,7 @@ return {
"_._", "(c::is environment, s::is symbol) = v",
function(state, env, s, v)
state.scope:push(env)
local r = Definition:new(s, v):eval(state)
local r = Call:from_operator("_=_", Quote:new(s), v):eval(state)
state.scope:pop()
return r
end
@ -46,7 +46,7 @@ return {
"import", "(env::is environment, symbol tuple::is tuple)",
function(state, env, l)
for _, sym in l:iter(state) do
Definition:new(sym, env:get(state, sym:to_identifier())):eval(state)
Call:from_operator("_=_", Quote:new(sym), env:get(state, sym:to_identifier())):eval(state)
end
return env
end
@ -54,7 +54,7 @@ return {
{
"import", "(env::is environment, symbol::is symbol)",
function(state, env, sym)
Definition:new(sym, env:get(state, sym:to_identifier())):eval(state)
Call:from_operator("_=_", Quote:new(sym), env:get(state, sym:to_identifier())):eval(state)
return env
end
},

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast")
local Nil, Boolean, Definition, Return, Overload, Overloadable = ast.Nil, ast.Boolean, ast.Definition, ast.Return, ast.Overload, ast.abstract.Overloadable
local Nil, Boolean, Call, Quote, Return, Overload, Overloadable = ast.Nil, ast.Boolean, ast.Call, ast.Quote, ast.Return, ast.Overload, ast.abstract.Overloadable
local assert0 = require("anselme.common").assert0
return {
@ -54,7 +54,7 @@ return {
"_._", "(c::is function, s::is symbol) = v",
function(state, c, s, v)
state.scope:push(c.scope)
local r = Definition:new(s, v):eval(state)
local r = Call:from_operator("_=_", Quote:new(s), v):eval(state)
state.scope:pop()
return r
end

View file

@ -28,6 +28,7 @@ return function(main_state)
"conditionals",
"base",
"typed",
"assignment",
"value check",
"number",
"string",

View file

@ -15,7 +15,6 @@ return [[
:@is text = is("text")
:@is sequence = $(x) x!type == "tuple" | x!type == "list"
:@is tuple = is("tuple")
:@is list = is("list")
:@is map = $(x) x!type == "struct" | x!type == "table"

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast")
local Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload, Assignment = ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload, ast.Assignment
local Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload = ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload
return {
{
@ -12,7 +12,7 @@ return {
if Call:is(exp) then
set_exp = Call:new(exp.func, exp.arguments:with_assignment(Identifier:new("value")))
elseif Identifier:is(exp) then
set_exp = Assignment:new(exp, Identifier:new("value"))
set_exp = Call:from_operator("_=_", q, Identifier:new("value"))
end
if set_exp then

View file

@ -27,22 +27,6 @@ Standard library.
Default arguments and initial variables values should pass the value check associated with the variable / parameter.
Issue: dispatch is decided before evaluating default values.
---
Syntax modifications:
* on the subject of assignments:
- multiple assignments:
:a, :b = 5, 6
a, b = list!($(l) l[3], l[6])
Easy by interpreting the left operand as a List. And also make this work for for loops.
- regular operator assignments:
Could interpret the left operand as a string when it is an identifier, like how _._ works.
Would feel good to have less nodes. But because we can doesn't mean we should. Also Assignment is reused in a few other places.
# Can be done later
Server API.
@ -55,6 +39,12 @@ Probably wise to look into how other do it. LSP: https://microsoft.github.io/lan
---
Return system.
Could be reused for exception handling or other purposes if accessible by the user.
---
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.

View file

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

View file

@ -1,7 +1,7 @@
--# run #--
--- error ---
trying to change the value of constant a
↳ from test/tests/constant variable.ans:5:3 in assignment: a = 52
↳ from test/tests/constant variable.ans:5:3 in call: a = 52
↳ from ? in block: ::a = 3…
--# saved #--
{}

View file

@ -5,7 +5,7 @@
| {}"" {}"type(12, \"kg\")" {}"" |
--- error ---
value check failure for weigh; 32 does not satisfy $(x) type(x) == t
↳ from test/tests/constrained variable assignement.ans:9:7 in assignment: weigh = 32
↳ from test/tests/constrained variable assignement.ans:9:7 in call: weigh = 32
↳ from ? in block: :weigh::is("kg") = type(5, "kg")…
--# saved #--
{}

View file

@ -1,7 +1,7 @@
--# run #--
--- error ---
a is already defined in the current scope
↳ from test/tests/define override function.ans:4:4 in definition: :a = 2
↳ from test/tests/define override function.ans:4:4 in call: :a = 2
↳ from ? in block: :a = ($() _)…
--# saved #--
{}

View file

@ -1,7 +1,7 @@
--# run #--
--- error ---
can't add an overload variant to non-overloadable variable a defined in the same scope
↳ from test/tests/define override variable.ans:3:1 in definition: :a = ($() _)
↳ from test/tests/define override variable.ans:3:1 in call: :a = ($() _)
↳ from ? in block: :a = 2…
--# saved #--
{}

View file

@ -1,7 +1,7 @@
--# run #--
--- error ---
a is already defined in the current scope
↳ from test/tests/define override.ans:3:4 in definition: :a = 2
↳ from test/tests/define override.ans:3:4 in call: :a = 2
↳ from ? in block: :a = 5…
--# saved #--
{}

View file

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

View file

@ -10,7 +10,7 @@
• $(s::is struct) (from stdlib/for.ans:2:1):
value check failure for parameter s
↳ from stdlib/for.ans:3:18 in call: iter(var)
↳ from stdlib/for.ans:3:12 in definition: :iterator = iter(var)
↳ from stdlib/for.ans:3:12 in call: :iterator = iter(var)
↳ from stdlib/for.ans:2:1 in block: :iterator = iter(var)…
↳ from stdlib/for.ans:2:71 in call: _
↳ from test/tests/for invalid iterator.ans:1:4 in call: for(:x, 42)

View file

@ -1,7 +1,7 @@
--# run #--
--- error ---
a function with parameters $(a, b) is already defined in the overload
↳ from test/tests/function conflict.ans:5:1 in definition: :f = ($(a, b) 0)
↳ from test/tests/function conflict.ans:5:1 in call: :f = ($(a, b) 0)
↳ from ? in block: :f = ($(a, b) 0)…
--# saved #--
{}

View file

@ -0,0 +1,9 @@
--# run #--
--- text ---
| {}"" {}"2" {}"" |
--- text ---
| {}"" {}"1" {}"" |
--- return ---
()
--# saved #--
{}

View file

@ -6,7 +6,7 @@
| {}"d=" {}"2" {}" (2)" |
--- error ---
trying to change the value of constant d
↳ from test/tests/symbol alias constant.ans:12:3 in assignment: d = 5
↳ from test/tests/symbol alias constant.ans:12:3 in call: d = 5
↳ from ? in block: :l = *[1, 2, 3]…
--# saved #--
{}

View file

@ -0,0 +1,7 @@
:a = 0
:b = 1
(a, b) = (3, 4)
|{a}
|{b}

View file

@ -0,0 +1,5 @@
(:a, :b) = (3, 4)
|{a}
|{b}

View file

@ -0,0 +1,8 @@
:a = 1
:b = 2
(a, b) = (b, a)
|{a}
|{b}