263 lines
5.7 KiB
Go
263 lines
5.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ParseParams splits a line into space-separated parameters, respecting quoted strings
|
|
func ParseParams(s string) ([]string, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return []string{}, nil
|
|
}
|
|
|
|
var params []string
|
|
var current strings.Builder
|
|
inString := false
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
ch := s[i]
|
|
|
|
if ch == '"' {
|
|
inString = !inString
|
|
current.WriteByte(ch)
|
|
continue
|
|
}
|
|
|
|
if !inString && (ch == ' ' || ch == '\t') {
|
|
if current.Len() > 0 {
|
|
params = append(params, current.String())
|
|
current.Reset()
|
|
}
|
|
} else {
|
|
current.WriteByte(ch)
|
|
}
|
|
}
|
|
|
|
if current.Len() > 0 {
|
|
params = append(params, current.String())
|
|
}
|
|
|
|
if inString {
|
|
return nil, fmt.Errorf("unterminated string")
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// NormalizeSpaces reduces multiple spaces to single space, respecting quoted strings
|
|
func NormalizeSpaces(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
var result strings.Builder
|
|
inString := false
|
|
lastWasSpace := false
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
ch := s[i]
|
|
|
|
if ch == '"' {
|
|
inString = !inString
|
|
result.WriteByte(ch)
|
|
lastWasSpace = false
|
|
continue
|
|
}
|
|
|
|
if !inString {
|
|
if ch == ' ' || ch == '\t' {
|
|
if !lastWasSpace {
|
|
result.WriteByte(' ')
|
|
lastWasSpace = true
|
|
}
|
|
} else {
|
|
result.WriteByte(ch)
|
|
lastWasSpace = false
|
|
}
|
|
} else {
|
|
result.WriteByte(ch)
|
|
lastWasSpace = false
|
|
}
|
|
}
|
|
|
|
return result.String()
|
|
}
|
|
|
|
// IsStringLiteral checks if s is a quoted string
|
|
func IsStringLiteral(s string) bool {
|
|
l := len(s)
|
|
return l >= 2 && s[0] == '"' && s[l-1] == '"'
|
|
}
|
|
|
|
// StripQuotes removes surrounding quotes from a string literal
|
|
func StripQuotes(s string) string {
|
|
if IsStringLiteral(s) {
|
|
return s[1 : len(s)-1]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ToUpper converts string to uppercase
|
|
func ToUpper(s string) string {
|
|
return strings.ToUpper(s)
|
|
}
|
|
|
|
// ValidateIdentifier checks if s is a valid identifier (starts with letter/underscore, continues with alphanumeric/underscore)
|
|
func ValidateIdentifier(s string) bool {
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
|
|
first := s[0]
|
|
if !((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_') {
|
|
return false
|
|
}
|
|
|
|
for i := 1; i < len(s); i++ {
|
|
ch := s[i]
|
|
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ConstantLookup is a function type for looking up constant values by name
|
|
type ConstantLookup func(name string) (value int64, found bool)
|
|
|
|
// EvaluateExpression evaluates a simple left-to-right expression
|
|
// Supports:
|
|
// - Decimal: 123
|
|
// - Hex: $FF
|
|
// - Binary: !11111111
|
|
// - Constants: MAXVAL (via lookup function)
|
|
// - Operators: +, -, *, /, | (binary OR), & (binary AND)
|
|
//
|
|
// Returns value and error. If lookup is nil, constants are not supported.
|
|
func EvaluateExpression(expr string, lookup ConstantLookup) (int64, error) {
|
|
expr = strings.TrimSpace(expr)
|
|
if expr == "" {
|
|
return 0, fmt.Errorf("empty expression")
|
|
}
|
|
|
|
// Split expression into terms and operators
|
|
var terms []string
|
|
var operators []rune
|
|
var currentTerm strings.Builder
|
|
|
|
for i := 0; i < len(expr); i++ {
|
|
ch := expr[i]
|
|
|
|
if isOperator(ch) {
|
|
// Save current term
|
|
if currentTerm.Len() > 0 {
|
|
terms = append(terms, strings.TrimSpace(currentTerm.String()))
|
|
currentTerm.Reset()
|
|
}
|
|
operators = append(operators, rune(ch))
|
|
} else {
|
|
currentTerm.WriteByte(ch)
|
|
}
|
|
}
|
|
|
|
// Save final term
|
|
if currentTerm.Len() > 0 {
|
|
terms = append(terms, strings.TrimSpace(currentTerm.String()))
|
|
}
|
|
|
|
if len(terms) == 0 {
|
|
return 0, fmt.Errorf("no terms in expression")
|
|
}
|
|
|
|
if len(operators) != len(terms)-1 {
|
|
return 0, fmt.Errorf("mismatched operators and terms")
|
|
}
|
|
|
|
// Evaluate first term
|
|
result, err := evaluateTerm(terms[0], lookup)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("term %q: %w", terms[0], err)
|
|
}
|
|
|
|
// Apply operators left-to-right
|
|
for i, op := range operators {
|
|
nextVal, err := evaluateTerm(terms[i+1], lookup)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("term %q: %w", terms[i+1], err)
|
|
}
|
|
|
|
switch op {
|
|
case '+':
|
|
result = result + nextVal
|
|
case '-':
|
|
result = result - nextVal
|
|
case '*':
|
|
result = result * nextVal
|
|
case '/':
|
|
if nextVal == 0 {
|
|
return 0, fmt.Errorf("division by zero")
|
|
}
|
|
result = result / nextVal
|
|
case '|':
|
|
result = result | nextVal
|
|
case '&':
|
|
result = result & nextVal
|
|
default:
|
|
return 0, fmt.Errorf("unknown operator %q", op)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// isOperator checks if a character is an operator
|
|
func isOperator(ch byte) bool {
|
|
return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '|' || ch == '&'
|
|
}
|
|
|
|
// evaluateTerm evaluates a single term (number or constant)
|
|
func evaluateTerm(term string, lookup ConstantLookup) (int64, error) {
|
|
term = strings.TrimSpace(term)
|
|
if term == "" {
|
|
return 0, fmt.Errorf("empty term")
|
|
}
|
|
|
|
// Check for hex: $FF
|
|
if strings.HasPrefix(term, "$") {
|
|
val, err := strconv.ParseInt(term[1:], 16, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid hex number: %w", err)
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// Check for binary: !11111111
|
|
if strings.HasPrefix(term, "!") {
|
|
val, err := strconv.ParseInt(term[1:], 2, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid binary number: %w", err)
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// Check for identifier (constant)
|
|
first := term[0]
|
|
if (first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_' {
|
|
if lookup == nil {
|
|
return 0, fmt.Errorf("constant %q not supported (no lookup function)", term)
|
|
}
|
|
val, found := lookup(term)
|
|
if !found {
|
|
return 0, fmt.Errorf("constant %q not found", term)
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// Decimal number
|
|
val, err := strconv.ParseInt(term, 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid decimal number: %w", err)
|
|
}
|
|
return val, nil
|
|
}
|