{ package parser import ( "strconv" "go.elara.ws/salix/ast" ) func toAnySlice(v any) []any { if v == nil { return nil } return v.([]any) } func toNodeSlice(v any) []ast.Node { if v == nil { return nil } return v.([]ast.Node) } func getPos(c *current) ast.Position { return ast.Position{ Name: c.globalStore["name"].(string), Line: c.pos.line, Col: c.pos.col, } } func toExpr(c *current, first, rest any) ast.Node { restSlice := toAnySlice(rest) if len(restSlice) == 0 { return first.(ast.Node) } out := ast.Expr{First: first.(ast.Node), Position: getPos(c)} for _, restValue := range restSlice { valueSlice := toAnySlice(restValue) out.Rest = append(out.Rest, ast.Expr{ Operator: valueSlice[1].(ast.Operator), First: valueSlice[3].(ast.Node), Position: valueSlice[3].(ast.Node).Pos(), }) } return out } } Root = items:(Tag / ExprTag / EndTag / Text)* { itemSlice := toAnySlice(items) out := make([]ast.Node, len(itemSlice)) for i, item := range itemSlice{ out[i] = item.(ast.Node) } return out, nil } Tag = '#' name:Ident params:ParamList? body:':'? { return ast.Tag{ Name: name.(ast.Ident), Params: toNodeSlice(params), HasBody: body != nil, Position: getPos(c), }, nil } EndTag = "#!" name:Ident { return ast.EndTag{ Name: name.(ast.Ident), Position: getPos(c), }, nil } ExprTag = '#' ignoreErr:'?'? '(' item:Expr ')' { return ast.ExprTag{ Value: item.(ast.Node), IgnoreError: ignoreErr != nil, Position: getPos(c), }, nil } Expr = Assignment / TernaryExpr Assignable = TernaryExpr TernaryExpr = _ cond:LogicalExpr vals:(_ '?' _ Value _ ':' _ Value)? { if vals == nil { return cond, nil } else { s := toAnySlice(vals) return ast.Ternary{ Condition: cond.(ast.Node), IfTrue: s[3].(ast.Node), Else: s[7].(ast.Node), }, nil } } LogicalExpr = _ first:ComparisonExpr rest:(_ LogicalOp _ ComparisonExpr)* _ { return toExpr(c, first, rest), nil } ComparisonExpr = _ first:ArithmeticExpr rest:(_ ComparisonOp _ ArithmeticExpr)* _ { return toExpr(c, first, rest), nil } ArithmeticExpr = _ first:Value rest:(_ ArithmeticOp _ Value)* _ { return toExpr(c, first, rest), nil } ParenExpr = '(' expr:Expr ')' { return expr, nil } ParamList = '(' params:(Expr ( ',' _ Expr )* )? ')' { paramSlice := toAnySlice(params) if len(paramSlice) == 0 { return []ast.Node{}, nil } out := []ast.Node{paramSlice[0].(ast.Node)} restSlice := toAnySlice(paramSlice[1]) for _, value := range restSlice { valueSlice := toAnySlice(value) out = append(out, valueSlice[2].(ast.Node)) } return out, nil } Value = not:"!"? node:(MethodCall / FieldAccess / Index / String / RawString / Float / Integer / Bool / FuncCall / VariableOr / Ident / ParenExpr / Array / Map) { return ast.Value{ Node: node.(ast.Node), Not: not != nil, }, nil } Map = '{' _ fpair:(Assignable _ ':' _ Assignable)? _ pairs:(',' _ Assignable _ ':' _ Assignable _)* _ ','? _ '}' { out := ast.Map{ Map: map[ast.Node]ast.Node{}, Position: getPos(c), } fpairSlice := toAnySlice(fpair) if fpairSlice == nil { return out, nil } else { out.Map[fpairSlice[0].(ast.Node)] = fpairSlice[4].(ast.Node) for _, pair := range toAnySlice(pairs) { pairSlice := toAnySlice(pair) out.Map[pairSlice[2].(ast.Node)] = pairSlice[6].(ast.Node) } } return out, nil } Array = '[' _ fval:Assignable? _ vals:(',' _ Assignable _)* ','? _ ']' { out := ast.Array{Position: getPos(c)} if fval == nil { return out, nil } else { out.Array = append(out.Array, fval.(ast.Node)) for _, val := range toAnySlice(vals) { valSlice := toAnySlice(val) out.Array = append(out.Array, valSlice[2].(ast.Node)) } } return out, nil } VariableOr = variable:Ident _ '|' _ or:Assignable { return ast.VariableOr{ Variable: variable.(ast.Ident), Or: or.(ast.Node), }, nil } Assignment = name:Ident _ '=' _ value:Assignable { return ast.Assignment{ Name: name.(ast.Ident), Value: value.(ast.Node), Position: getPos(c), }, nil } MethodCall = value:Value '.' name:Ident params:ParamList { return ast.MethodCall{ Value: value.(ast.Node), Name: name.(ast.Ident), Params: toNodeSlice(params), Position: getPos(c), }, nil } Index = value:Value '[' index:Value ']' { return ast.Index{ Value: value.(ast.Node), Index: index.(ast.Node), Position: getPos(c), }, nil } FieldAccess = value:Value '.' name:Ident { return ast.FieldAccess{ Value: value.(ast.Node), Name: name.(ast.Ident), Position: getPos(c), }, nil } Ident = [a-z]i [a-z0-9_]i* { return ast.Ident{ Value: string(c.text), Position: getPos(c), }, nil } FuncCall = name:Ident params:ParamList { return ast.FuncCall{ Name: name.(ast.Ident), Params: toNodeSlice(params), Position: getPos(c), }, nil } Integer = '-'? ("0x" [0-9a-f]i+ / "0o" [0-7]+ / "0b" [01]+ / [0-9]+) { i, err := strconv.ParseInt(string(c.text), 0, 64) return ast.Integer{ Value: i, Position: getPos(c), }, err } Float = '-'? value:([0-9]+ '.' [0-9]+) { f, err := strconv.ParseFloat(string(c.text), 64) return ast.Float{ Value: f, Position: getPos(c), }, err } String = '"' value:[^"]* '"' { s, err := strconv.Unquote(string(c.text)) return ast.String{ Value: s, Position: getPos(c), }, err } RawString = '`' value:[^`]* '`' { s, err := strconv.Unquote(string(c.text)) return ast.String{ Value: s, Position: getPos(c), }, err } Bool = ("true"i / "false"i) { b, err := strconv.ParseBool(string(c.text)) return ast.Bool{ Value: b, Position: getPos(c), }, err } LogicalOp = ("||" / "&&") { return ast.Operator{ Value: string(c.text), Position: getPos(c), }, nil } ComparisonOp = ("==" / "!=" / "<=" / ">=" / '<' / '>' / "in"i) { return ast.Operator{ Value: string(c.text), Position: getPos(c), }, nil } ArithmeticOp = ('+' / '-' / '/' / '*' / '%') { return ast.Operator{ Value: string(c.text), Position: getPos(c), }, nil } Text = . [^#]* { return ast.Text{Data: c.text, Position: getPos(c)}, nil } _ "whitespace" ← [ \t\r\n]*