heroagent/pkg/heroscript/playbook/parser.go
2025-04-23 04:18:28 +02:00

213 lines
5.2 KiB
Go

package playbook
import (
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
"github.com/freeflowuniverse/heroagent/pkg/tools"
)
// State represents the parser state
type State int
const (
StateStart State = iota
StateCommentForActionMaybe
StateAction
StateOtherText
)
// PlayBookOptions contains options for creating a new PlayBook
type PlayBookOptions struct {
Text string
Path string
GitURL string
GitPull bool
GitBranch string
GitReset bool
Priority int
}
// AddText adds heroscript text to the playbook
func (p *PlayBook) AddText(text string, priority int) error {
// Normalize text
text = strings.ReplaceAll(text, "\t", " ")
var state State = StateStart
var action *Action
var comments []string
var paramsData []string
// Process each line
lines := strings.Split(text, "\n")
for _, line := range lines {
lineStrip := strings.TrimSpace(line)
if lineStrip == "" {
continue
}
// Handle action state
if state == StateAction {
if !strings.HasPrefix(line, " ") || lineStrip == "" || strings.HasPrefix(lineStrip, "!") {
state = StateStart
// End of action, parse params
if len(paramsData) > 0 {
params := strings.Join(paramsData, "\n")
err := action.Params.Parse(params)
if err != nil {
return err
}
// Remove ID from params if present
delete(action.Params.GetAll(), "id")
}
comments = []string{}
paramsData = []string{}
action = nil
} else {
paramsData = append(paramsData, line)
}
}
// Handle comment state
if state == StateCommentForActionMaybe {
if strings.HasPrefix(lineStrip, "//") {
comments = append(comments, strings.TrimLeft(lineStrip, "/ "))
} else {
if strings.HasPrefix(lineStrip, "!") {
state = StateStart
} else {
state = StateStart
p.OtherText += strings.Join(comments, "\n")
if !strings.HasSuffix(p.OtherText, "\n") {
p.OtherText += "\n"
}
comments = []string{}
}
}
}
// Handle start state
if state == StateStart {
if strings.HasPrefix(lineStrip, "!") && !strings.HasPrefix(lineStrip, "![") {
// Start a new action
state = StateAction
// Create new action
action = &Action{
ID: p.NrActions + 1,
Priority: priority,
Params: paramsparser.New(),
Result: paramsparser.New(),
}
p.NrActions++
// Set comments
action.Comments = strings.Join(comments, "\n")
comments = []string{}
paramsData = []string{}
// Parse action name
actionName := lineStrip
if strings.Contains(lineStrip, " ") {
actionName = strings.TrimSpace(strings.Split(lineStrip, " ")[0])
params := strings.TrimSpace(strings.Join(strings.Split(lineStrip, " ")[1:], " "))
if params != "" {
paramsData = append(paramsData, params)
}
}
// Determine action type
if strings.HasPrefix(actionName, "!!!!!") {
return ErrInvalidActionPrefix
} else if strings.HasPrefix(actionName, "!!!!") {
action.ActionType = ActionTypeWAL
} else if strings.HasPrefix(actionName, "!!!") {
action.ActionType = ActionTypeMacro
} else if strings.HasPrefix(actionName, "!!") {
action.ActionType = ActionTypeSAL
} else if strings.HasPrefix(actionName, "!") {
action.ActionType = ActionTypeDAL
}
// Remove prefix
actionName = strings.TrimLeft(actionName, "!")
// Split into actor and action name
parts := strings.Split(actionName, ".")
if len(parts) == 1 {
action.Actor = "core"
action.Name = tools.NameFix(parts[0])
} else if len(parts) == 2 {
action.Actor = tools.NameFix(parts[0])
action.Name = tools.NameFix(parts[1])
} else {
return ErrInvalidActionName
}
// Add action to playbook
p.Actions = append(p.Actions, action)
continue
} else if strings.HasPrefix(lineStrip, "//") {
state = StateCommentForActionMaybe
comments = append(comments, strings.TrimLeft(lineStrip, "/ "))
}
}
}
// Process the last action if needed
if state == StateAction && action != nil && action.ID != 0 {
if len(paramsData) > 0 {
params := strings.Join(paramsData, "\n")
err := action.Params.Parse(params)
if err != nil {
return err
}
// Remove ID from params if present
delete(action.Params.GetAll(), "id")
}
}
// Process the last comment if needed
if state == StateCommentForActionMaybe && len(comments) > 0 {
p.OtherText += strings.Join(comments, "\n")
}
return nil
}
// NewFromFile creates a new PlayBook from a file
func NewFromFile(path string, priority int) (*PlayBook, error) {
// This is a simplified version - in a real implementation, you'd read the file
// and handle different file types (md, hero, etc.)
// For now, we'll just create an empty playbook
pb := New()
// TODO: Implement file reading and parsing
return pb, nil
}
// Errors
var (
ErrInvalidActionPrefix = NewError("invalid action prefix")
ErrInvalidActionName = NewError("invalid action name")
)
// NewError creates a new error
func NewError(msg string) error {
return &PlayBookError{msg}
}
// PlayBookError represents a playbook error
type PlayBookError struct {
Msg string
}
// Error returns the error message
func (e *PlayBookError) Error() string {
return e.Msg
}