Files
archived-frankenphp/internal/extgen/constparser.go
Alexandre Daubois c6b2b02277 fix(extgen): correctly handle const blocks to declare iota constants (#2086)
While continuing the work on #2011, I realized that constant
declarations have a problem when using `iota`. I mean, it technically
works, but const *blocks* we not supported which means that setting all
constants to `iota` as shown in the documentation was non-sensical, as
`iota` resets every time outside of const blocks.

So, this is between the bug fix and the feature. To me, it's a bug fix
as the behavior wasn't the one intended when creating extgen.
2026-01-12 15:44:46 +01:00

204 lines
5.1 KiB
Go

package extgen
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
var constRegex = regexp.MustCompile(`//\s*export_php:const$`)
var classConstRegex = regexp.MustCompile(`//\s*export_php:classconst\s+(\w+)$`)
var constDeclRegex = regexp.MustCompile(`const\s+(\w+)\s*=\s*(.+)`)
type ConstantParser struct{}
func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
e := file.Close()
if err == nil {
err = e
}
}()
scanner := bufio.NewScanner(file)
lineNumber := 0
expectConstDecl := false
expectClassConstDecl := false
currentClassName := ""
currentConstantValue := 0
inConstBlock := false
exportAllInBlock := false
lastConstValue := ""
lastConstWasIota := false
for scanner.Scan() {
lineNumber++
line := strings.TrimSpace(scanner.Text())
if constRegex.MatchString(line) {
expectConstDecl = true
expectClassConstDecl = false
currentClassName = ""
continue
}
if matches := classConstRegex.FindStringSubmatch(line); len(matches) == 2 {
expectClassConstDecl = true
expectConstDecl = false
currentClassName = matches[1]
continue
}
if strings.HasPrefix(line, "const (") {
inConstBlock = true
if expectConstDecl || expectClassConstDecl {
exportAllInBlock = true
}
continue
}
if inConstBlock && line == ")" {
inConstBlock = false
exportAllInBlock = false
expectConstDecl = false
expectClassConstDecl = false
currentClassName = ""
lastConstValue = ""
lastConstWasIota = false
continue
}
if (expectConstDecl || expectClassConstDecl) && strings.HasPrefix(line, "const ") && !inConstBlock {
matches := constDeclRegex.FindStringSubmatch(line)
if len(matches) == 3 {
name := matches[1]
value := strings.TrimSpace(matches[2])
constant := phpConstant{
Name: name,
Value: value,
IsIota: value == "iota",
lineNumber: lineNumber,
ClassName: currentClassName,
}
constant.PhpType = determineConstantType(value)
if constant.IsIota {
constant.Value = fmt.Sprintf("%d", currentConstantValue)
constant.PhpType = phpInt
currentConstantValue++
lastConstWasIota = true
lastConstValue = constant.Value
}
constants = append(constants, constant)
} else {
return nil, fmt.Errorf("invalid constant declaration at line %d: %s", lineNumber, line)
}
expectConstDecl = false
expectClassConstDecl = false
} else if inConstBlock && (expectConstDecl || expectClassConstDecl || exportAllInBlock) {
constBlockDeclRegex := regexp.MustCompile(`^(\w+)\s*=\s*(.+)$`)
if matches := constBlockDeclRegex.FindStringSubmatch(line); len(matches) == 3 {
name := matches[1]
value := strings.TrimSpace(matches[2])
constant := phpConstant{
Name: name,
Value: value,
IsIota: value == "iota",
lineNumber: lineNumber,
ClassName: currentClassName,
}
constant.PhpType = determineConstantType(value)
if constant.IsIota {
constant.Value = fmt.Sprintf("%d", currentConstantValue)
constant.PhpType = phpInt
currentConstantValue++
lastConstWasIota = true
lastConstValue = constant.Value
} else {
lastConstWasIota = false
lastConstValue = value
}
constants = append(constants, constant)
expectConstDecl = false
expectClassConstDecl = false
} else {
constNameRegex := regexp.MustCompile(`^(\w+)$`)
if matches := constNameRegex.FindStringSubmatch(line); len(matches) == 2 {
name := matches[1]
constant := phpConstant{
Name: name,
Value: "",
IsIota: lastConstWasIota,
lineNumber: lineNumber,
ClassName: currentClassName,
}
if lastConstWasIota {
constant.Value = fmt.Sprintf("%d", currentConstantValue)
constant.PhpType = phpInt
currentConstantValue++
lastConstValue = constant.Value
} else {
constant.Value = lastConstValue
constant.PhpType = determineConstantType(lastConstValue)
}
constants = append(constants, constant)
expectConstDecl = false
expectClassConstDecl = false
}
}
} else if (expectConstDecl || expectClassConstDecl) && !strings.HasPrefix(line, "//") && line != "" && !inConstBlock {
// we expected a const declaration but found something else, reset
expectConstDecl = false
expectClassConstDecl = false
currentClassName = ""
}
}
return constants, scanner.Err()
}
// determineConstantType analyzes the value and determines its type
func determineConstantType(value string) phpType {
value = strings.TrimSpace(value)
if (strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`)) ||
(strings.HasPrefix(value, "`") && strings.HasSuffix(value, "`")) {
return phpString
}
if value == "true" || value == "false" {
return phpBool
}
// check for integer literals, including hex, octal, binary
if _, err := strconv.ParseInt(value, 0, 64); err == nil {
return phpInt
}
if _, err := strconv.ParseFloat(value, 64); err == nil {
return phpFloat
}
return phpInt
}