mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-02 18:00:18 +00:00
307 lines
7.2 KiB
Go
307 lines
7.2 KiB
Go
package workflow
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// Node represents a node in the expression tree.
|
||
// It is intentionally minimal – only the fields needed for the parser.
|
||
// Users can extend it with more information if required.
|
||
|
||
type Node interface {
|
||
String() string
|
||
}
|
||
|
||
// ValueNode represents a literal value (number, string, boolean, null) or a named value.
|
||
// The Kind field indicates the type.
|
||
// For named values the Value is nil.
|
||
|
||
type ValueNode struct {
|
||
Kind TokenKind
|
||
Value any
|
||
}
|
||
|
||
// FunctionNode represents a function call with arguments.
|
||
|
||
type FunctionNode struct {
|
||
Name string
|
||
Args []Node
|
||
}
|
||
|
||
// BinaryNode represents a binary operator.
|
||
|
||
type BinaryNode struct {
|
||
Op string
|
||
Left Node
|
||
Right Node
|
||
}
|
||
|
||
// UnaryNode represents a unary operator.
|
||
|
||
type UnaryNode struct {
|
||
Op string
|
||
Operand Node
|
||
}
|
||
|
||
// Parser holds the lexer and the stacks used by the shunting‑yard algorithm.
|
||
|
||
type Parser struct {
|
||
lexer *Lexer
|
||
tokens []Token
|
||
pos int
|
||
ops []OpToken
|
||
vals []Node
|
||
}
|
||
|
||
type OpToken struct {
|
||
Token
|
||
StartPos int
|
||
}
|
||
|
||
func precedence(tkn Token) int {
|
||
switch tkn.Kind {
|
||
case TokenKindStartGroup:
|
||
return 20
|
||
case TokenKindStartIndex, TokenKindStartParameters, TokenKindDereference:
|
||
return 19
|
||
case TokenKindLogicalOperator:
|
||
switch tkn.Raw {
|
||
case "!":
|
||
return 16
|
||
case ">", ">=", "<", "<=":
|
||
return 11
|
||
case "==", "!=":
|
||
return 10
|
||
case "&&":
|
||
return 6
|
||
case "||":
|
||
return 5
|
||
}
|
||
case TokenKindEndGroup, TokenKindEndIndex, TokenKindEndParameters, TokenKindSeparator:
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// Parse parses the expression and returns the root node.
|
||
func Parse(expression string) (Node, error) {
|
||
lexer := NewLexer(expression, 0)
|
||
p := &Parser{}
|
||
// Tokenise all tokens
|
||
if err := p.initWithLexer(lexer); err != nil {
|
||
return nil, err
|
||
}
|
||
return p.parse()
|
||
}
|
||
|
||
func (p *Parser) parse() (Node, error) {
|
||
// Shunting‑yard algorithm
|
||
for p.pos < len(p.tokens) {
|
||
tok := p.tokens[p.pos]
|
||
p.pos++
|
||
switch tok.Kind {
|
||
case TokenKindNumber, TokenKindString, TokenKindBoolean, TokenKindNull:
|
||
p.pushValue(&ValueNode{Kind: tok.Kind, Value: tok.Value})
|
||
case TokenKindNamedValue, TokenKindPropertyName, TokenKindWildcard:
|
||
p.pushValue(&ValueNode{Kind: tok.Kind, Value: tok.Raw})
|
||
case TokenKindFunction:
|
||
p.pushFunc(tok, len(p.vals))
|
||
case TokenKindStartParameters, TokenKindStartGroup, TokenKindStartIndex, TokenKindLogicalOperator, TokenKindDereference:
|
||
if err := p.pushOp(tok); err != nil {
|
||
return nil, err
|
||
}
|
||
case TokenKindSeparator:
|
||
if err := p.popGroup(TokenKindStartParameters); err != nil {
|
||
return nil, err
|
||
}
|
||
case TokenKindEndParameters:
|
||
if err := p.pushFuncValue(); err != nil {
|
||
return nil, err
|
||
}
|
||
case TokenKindEndGroup:
|
||
if err := p.popGroup(TokenKindStartGroup); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
p.ops = p.ops[:len(p.ops)-1]
|
||
case TokenKindEndIndex:
|
||
if err := p.popGroup(TokenKindStartIndex); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// pop the start parameters
|
||
p.ops = p.ops[:len(p.ops)-1]
|
||
right := p.vals[len(p.vals)-1]
|
||
p.vals = p.vals[:len(p.vals)-1]
|
||
left := p.vals[len(p.vals)-1]
|
||
p.vals = p.vals[:len(p.vals)-1]
|
||
p.vals = append(p.vals, &BinaryNode{Op: "[", Left: left, Right: right})
|
||
}
|
||
}
|
||
for len(p.ops) > 0 {
|
||
if err := p.popOp(); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
if len(p.vals) != 1 {
|
||
return nil, errors.New("invalid expression")
|
||
}
|
||
return p.vals[0], nil
|
||
}
|
||
|
||
func (p *Parser) pushFuncValue() error {
|
||
if err := p.popGroup(TokenKindStartParameters); err != nil {
|
||
return err
|
||
}
|
||
|
||
// pop the start parameters
|
||
p.ops = p.ops[:len(p.ops)-1]
|
||
// create function node
|
||
fnTok := p.ops[len(p.ops)-1]
|
||
if fnTok.Kind != TokenKindFunction {
|
||
return errors.New("expected function token")
|
||
}
|
||
p.ops = p.ops[:len(p.ops)-1]
|
||
// collect arguments
|
||
args := []Node{}
|
||
for len(p.vals) > fnTok.StartPos {
|
||
args = append([]Node{p.vals[len(p.vals)-1]}, args...)
|
||
p.vals = p.vals[:len(p.vals)-1]
|
||
}
|
||
p.pushValue(&FunctionNode{Name: fnTok.Raw, Args: args})
|
||
return nil
|
||
}
|
||
|
||
func (p *Parser) initWithLexer(lexer *Lexer) error {
|
||
p.lexer = lexer
|
||
for {
|
||
tok := lexer.Next()
|
||
if tok == nil {
|
||
break
|
||
}
|
||
if tok.Kind == TokenKindUnexpected {
|
||
return fmt.Errorf("unexpected token %s at position %d", tok.Raw, tok.Index)
|
||
}
|
||
p.tokens = append(p.tokens, *tok)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (p *Parser) popGroup(kind TokenKind) error {
|
||
for len(p.ops) > 0 && p.ops[len(p.ops)-1].Kind != kind {
|
||
if err := p.popOp(); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if len(p.ops) == 0 {
|
||
return errors.New("mismatched parentheses")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (p *Parser) pushValue(v Node) {
|
||
p.vals = append(p.vals, v)
|
||
}
|
||
|
||
func (p *Parser) pushOp(t Token) error {
|
||
for len(p.ops) > 0 {
|
||
top := p.ops[len(p.ops)-1]
|
||
if precedence(top.Token) >= precedence(t) &&
|
||
top.Kind != TokenKindStartGroup &&
|
||
top.Kind != TokenKindStartIndex &&
|
||
top.Kind != TokenKindStartParameters &&
|
||
top.Kind != TokenKindSeparator {
|
||
if err := p.popOp(); err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
p.ops = append(p.ops, OpToken{Token: t})
|
||
return nil
|
||
}
|
||
|
||
func (p *Parser) pushFunc(t Token, start int) {
|
||
p.ops = append(p.ops, OpToken{Token: t, StartPos: start})
|
||
}
|
||
|
||
func (p *Parser) popOp() error {
|
||
if len(p.ops) == 0 {
|
||
return nil
|
||
}
|
||
op := p.ops[len(p.ops)-1]
|
||
p.ops = p.ops[:len(p.ops)-1]
|
||
switch op.Kind {
|
||
case TokenKindLogicalOperator:
|
||
if op.Raw == "!" {
|
||
if len(p.vals) < 1 {
|
||
return errors.New("insufficient operands")
|
||
}
|
||
right := p.vals[len(p.vals)-1]
|
||
p.vals = p.vals[:len(p.vals)-1]
|
||
p.vals = append(p.vals, &UnaryNode{Op: op.Raw, Operand: right})
|
||
} else {
|
||
if len(p.vals) < 2 {
|
||
return errors.New("insufficient operands")
|
||
}
|
||
right := p.vals[len(p.vals)-1]
|
||
left := p.vals[len(p.vals)-2]
|
||
p.vals = p.vals[:len(p.vals)-2]
|
||
p.vals = append(p.vals, &BinaryNode{Op: op.Raw, Left: left, Right: right})
|
||
}
|
||
case TokenKindStartParameters:
|
||
// unary operator '!' handled elsewhere
|
||
case TokenKindDereference:
|
||
if len(p.vals) < 2 {
|
||
return errors.New("insufficient operands")
|
||
}
|
||
right := p.vals[len(p.vals)-1]
|
||
left := p.vals[len(p.vals)-2]
|
||
p.vals = p.vals[:len(p.vals)-2]
|
||
p.vals = append(p.vals, &BinaryNode{Op: ".", Left: left, Right: right})
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// String returns a string representation of the node.
|
||
func (n *ValueNode) String() string { return fmt.Sprintf("%v", n.Value) }
|
||
|
||
// String returns a string representation of the node.
|
||
func (n *FunctionNode) String() string {
|
||
return fmt.Sprintf("%s(%s)", n.Name, strings.Join(funcArgs(n.Args), ", "))
|
||
}
|
||
|
||
func funcArgs(args []Node) []string {
|
||
res := []string{}
|
||
for _, a := range args {
|
||
res = append(res, a.String())
|
||
}
|
||
return res
|
||
}
|
||
|
||
// String returns a string representation of the node.
|
||
func (n *BinaryNode) String() string {
|
||
return fmt.Sprintf("(%s %s %s)", n.Left.String(), n.Op, n.Right.String())
|
||
}
|
||
|
||
// String returns a string representation of the node.
|
||
func (n *UnaryNode) String() string { return fmt.Sprintf("(%s%s)", n.Op, n.Operand.String()) }
|
||
|
||
func VisitNode(exprNode Node, callback func(node Node)) {
|
||
callback(exprNode)
|
||
switch node := exprNode.(type) {
|
||
case *FunctionNode:
|
||
for _, arg := range node.Args {
|
||
VisitNode(arg, callback)
|
||
}
|
||
case *UnaryNode:
|
||
VisitNode(node.Operand, callback)
|
||
case *BinaryNode:
|
||
VisitNode(node.Left, callback)
|
||
VisitNode(node.Right, callback)
|
||
}
|
||
}
|