mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
* feat(extgent): support for mixed type
* refactor: use unsafe.Pointer
* Revert "refactor: use unsafe.Pointer"
This reverts commit 8a0b9c1beb.
* fix docs
* fix docs
* cleanup template
* fix template
* fix tests
184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
package extgen
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var phpFuncRegex = regexp.MustCompile(`//\s*export_php:function\s+([^{}\n]+)(?:\s*{\s*})?`)
|
|
var signatureRegex = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
|
|
var typeNameRegex = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
|
|
|
|
type FuncParser struct{}
|
|
|
|
func (fp *FuncParser) parse(filename string) (functions []phpFunction, 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)
|
|
var currentPHPFunc *phpFunction
|
|
validator := Validator{}
|
|
|
|
lineNumber := 0
|
|
for scanner.Scan() {
|
|
lineNumber++
|
|
line := strings.TrimSpace(scanner.Text())
|
|
|
|
if matches := phpFuncRegex.FindStringSubmatch(line); matches != nil {
|
|
signature := strings.TrimSpace(matches[1])
|
|
phpFunc, err := fp.parseSignature(signature)
|
|
if err != nil {
|
|
fmt.Printf("Warning: Error parsing signature '%s': %v\n", signature, err)
|
|
|
|
continue
|
|
}
|
|
|
|
if err := validator.validateFunction(*phpFunc); err != nil {
|
|
fmt.Printf("Warning: Invalid function '%s': %v\n", phpFunc.Name, err)
|
|
|
|
continue
|
|
}
|
|
|
|
if err := validator.validateTypes(*phpFunc); err != nil {
|
|
fmt.Printf("Warning: Function '%s' uses unsupported types: %v\n", phpFunc.Name, err)
|
|
|
|
continue
|
|
}
|
|
|
|
phpFunc.lineNumber = lineNumber
|
|
currentPHPFunc = phpFunc
|
|
}
|
|
|
|
if currentPHPFunc != nil && strings.HasPrefix(line, "func ") {
|
|
goFunc, err := fp.extractGoFunction(scanner, line)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("extracting Go function: %w", err)
|
|
}
|
|
|
|
currentPHPFunc.GoFunction = goFunc
|
|
|
|
if err := validator.validateGoFunctionSignatureWithOptions(*currentPHPFunc, false); err != nil {
|
|
fmt.Printf("Warning: Go function signature mismatch for %q: %v\n", currentPHPFunc.Name, err)
|
|
currentPHPFunc = nil
|
|
|
|
continue
|
|
}
|
|
|
|
functions = append(functions, *currentPHPFunc)
|
|
currentPHPFunc = nil
|
|
}
|
|
}
|
|
|
|
if currentPHPFunc != nil {
|
|
return nil, fmt.Errorf("//export_php function directive at line %d is not followed by a function declaration", currentPHPFunc.lineNumber)
|
|
}
|
|
|
|
return functions, scanner.Err()
|
|
}
|
|
|
|
func (fp *FuncParser) extractGoFunction(scanner *bufio.Scanner, firstLine string) (string, error) {
|
|
goFunc := firstLine + "\n"
|
|
braceCount := 1
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
goFunc += line + "\n"
|
|
|
|
for _, char := range line {
|
|
switch char {
|
|
case '{':
|
|
braceCount++
|
|
case '}':
|
|
braceCount--
|
|
}
|
|
}
|
|
|
|
if braceCount == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return goFunc, nil
|
|
}
|
|
|
|
func (fp *FuncParser) parseSignature(signature string) (*phpFunction, error) {
|
|
matches := signatureRegex.FindStringSubmatch(signature)
|
|
|
|
if len(matches) != 4 {
|
|
return nil, fmt.Errorf("invalid signature format")
|
|
}
|
|
|
|
name := matches[1]
|
|
paramsStr := strings.TrimSpace(matches[2])
|
|
returnTypeStr := strings.TrimSpace(matches[3])
|
|
|
|
isReturnNullable := strings.HasPrefix(returnTypeStr, "?")
|
|
returnType := strings.TrimPrefix(returnTypeStr, "?")
|
|
|
|
var params []phpParameter
|
|
if paramsStr != "" {
|
|
paramParts := strings.SplitSeq(paramsStr, ",")
|
|
for part := range paramParts {
|
|
param, err := fp.parseParameter(strings.TrimSpace(part))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing parameter '%s': %w", part, err)
|
|
}
|
|
params = append(params, param)
|
|
}
|
|
}
|
|
|
|
return &phpFunction{
|
|
Name: name,
|
|
Signature: signature,
|
|
Params: params,
|
|
ReturnType: phpType(returnType),
|
|
IsReturnNullable: isReturnNullable,
|
|
}, nil
|
|
}
|
|
|
|
func (fp *FuncParser) parseParameter(paramStr string) (phpParameter, error) {
|
|
parts := strings.Split(paramStr, "=")
|
|
typePart := strings.TrimSpace(parts[0])
|
|
|
|
param := phpParameter{HasDefault: len(parts) > 1}
|
|
|
|
if param.HasDefault {
|
|
param.DefaultValue = fp.sanitizeDefaultValue(strings.TrimSpace(parts[1]))
|
|
}
|
|
|
|
matches := typeNameRegex.FindStringSubmatch(typePart)
|
|
|
|
if len(matches) < 3 {
|
|
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
|
|
}
|
|
|
|
typeStr := strings.TrimSpace(matches[1])
|
|
param.Name = strings.TrimSpace(matches[2])
|
|
param.IsNullable = strings.HasPrefix(typeStr, "?")
|
|
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
|
|
|
|
return param, nil
|
|
}
|
|
|
|
func (fp *FuncParser) sanitizeDefaultValue(value string) string {
|
|
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
|
|
return value
|
|
}
|
|
if strings.ToLower(value) == "null" {
|
|
return "null"
|
|
}
|
|
|
|
return strings.Trim(value, `'"`)
|
|
}
|