Skip to content

Commit 7fbde3e

Browse files
refactor(extgen): share signature and parameter parsing helpers
1 parent af07c17 commit 7fbde3e

5 files changed

Lines changed: 88 additions & 130 deletions

File tree

internal/extgen/classparser.go

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313

1414
var phpClassRegex = regexp.MustCompile(`//\s*export_php:class\s+(\w+)`)
1515
var phpMethodRegex = regexp.MustCompile(`//\s*export_php:method\s+(\w+)::([^{}\n]+)(?:\s*{\s*})?`)
16-
var methodSignatureRegex = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
17-
var methodParamTypeNameRegex = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
1816

1917
type exportDirective struct {
2018
line int
@@ -292,79 +290,22 @@ func (cp *classParser) parseMethods(filename string) (methods []phpClassMethod,
292290
}
293291

294292
func (cp *classParser) parseMethodSignature(className, signature string) (*phpClassMethod, error) {
295-
matches := methodSignatureRegex.FindStringSubmatch(signature)
296-
297-
if len(matches) != 4 {
298-
return nil, fmt.Errorf("invalid method signature format")
299-
}
300-
301-
methodName := matches[1]
302-
paramsStr := strings.TrimSpace(matches[2])
303-
returnTypeStr := strings.TrimSpace(matches[3])
304-
305-
isReturnNullable := strings.HasPrefix(returnTypeStr, "?")
306-
returnType := strings.TrimPrefix(returnTypeStr, "?")
307-
308-
var params []phpParameter
309-
if paramsStr != "" {
310-
paramParts := strings.SplitSeq(paramsStr, ",")
311-
for part := range paramParts {
312-
param, err := cp.parseMethodParameter(strings.TrimSpace(part))
313-
if err != nil {
314-
return nil, fmt.Errorf("parsing parameter '%s': %w", part, err)
315-
}
316-
317-
params = append(params, param)
318-
}
293+
name, params, returnType, nullable, err := parseSignatureParams(signature)
294+
if err != nil {
295+
return nil, err
319296
}
320297

321298
return &phpClassMethod{
322-
Name: methodName,
323-
PhpName: methodName,
299+
Name: name,
300+
PhpName: name,
324301
ClassName: className,
325302
Signature: signature,
326303
Params: params,
327304
ReturnType: phpType(returnType),
328-
isReturnNullable: isReturnNullable,
305+
isReturnNullable: nullable,
329306
}, nil
330307
}
331308

332-
func (cp *classParser) parseMethodParameter(paramStr string) (phpParameter, error) {
333-
parts := strings.Split(paramStr, "=")
334-
typePart := strings.TrimSpace(parts[0])
335-
336-
param := phpParameter{HasDefault: len(parts) > 1}
337-
338-
if param.HasDefault {
339-
param.DefaultValue = cp.sanitizeDefaultValue(strings.TrimSpace(parts[1]))
340-
}
341-
342-
matches := methodParamTypeNameRegex.FindStringSubmatch(typePart)
343-
344-
if len(matches) < 3 {
345-
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
346-
}
347-
348-
typeStr := strings.TrimSpace(matches[1])
349-
param.Name = strings.TrimSpace(matches[2])
350-
param.IsNullable = strings.HasPrefix(typeStr, "?")
351-
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
352-
353-
return param, nil
354-
}
355-
356-
func (cp *classParser) sanitizeDefaultValue(value string) string {
357-
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
358-
return value
359-
}
360-
361-
if strings.ToLower(value) == "null" {
362-
return "null"
363-
}
364-
365-
return strings.Trim(value, `'"`)
366-
}
367-
368309
func (cp *classParser) extractGoMethodFunction(scanner *bufio.Scanner, firstLine string) (string, error) {
369310
goFunc := firstLine + "\n"
370311
braceCount := 1

internal/extgen/classparser_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,9 @@ func TestMethodParameterParsing(t *testing.T) {
244244
},
245245
}
246246

247-
parser := classParser{}
248247
for _, tt := range tests {
249248
t.Run(tt.name, func(t *testing.T) {
250-
param, err := parser.parseMethodParameter(tt.paramStr)
249+
param, err := parseParameter(tt.paramStr)
251250

252251
if tt.expectError {
253252
assert.Error(t, err, "Expected error for parameter '%s', but got none", tt.paramStr)

internal/extgen/funcparser.go

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
)
1010

1111
var phpFuncRegex = regexp.MustCompile(`//\s*export_php:function\s+([^{}\n]+)(?:\s*{\s*})?`)
12-
var signatureRegex = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
13-
var typeNameRegex = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
1412

1513
type FuncParser struct{}
1614

@@ -113,71 +111,16 @@ func (fp *FuncParser) extractGoFunction(scanner *bufio.Scanner, firstLine string
113111
}
114112

115113
func (fp *FuncParser) parseSignature(signature string) (*phpFunction, error) {
116-
matches := signatureRegex.FindStringSubmatch(signature)
117-
118-
if len(matches) != 4 {
119-
return nil, fmt.Errorf("invalid signature format")
120-
}
121-
122-
name := matches[1]
123-
paramsStr := strings.TrimSpace(matches[2])
124-
returnTypeStr := strings.TrimSpace(matches[3])
125-
126-
isReturnNullable := strings.HasPrefix(returnTypeStr, "?")
127-
returnType := strings.TrimPrefix(returnTypeStr, "?")
128-
129-
var params []phpParameter
130-
if paramsStr != "" {
131-
paramParts := strings.SplitSeq(paramsStr, ",")
132-
for part := range paramParts {
133-
param, err := fp.parseParameter(strings.TrimSpace(part))
134-
if err != nil {
135-
return nil, fmt.Errorf("parsing parameter '%s': %w", part, err)
136-
}
137-
params = append(params, param)
138-
}
114+
name, params, returnType, nullable, err := parseSignatureParams(signature)
115+
if err != nil {
116+
return nil, err
139117
}
140118

141119
return &phpFunction{
142120
Name: name,
143121
Signature: signature,
144122
Params: params,
145123
ReturnType: phpType(returnType),
146-
IsReturnNullable: isReturnNullable,
124+
IsReturnNullable: nullable,
147125
}, nil
148126
}
149-
150-
func (fp *FuncParser) parseParameter(paramStr string) (phpParameter, error) {
151-
parts := strings.Split(paramStr, "=")
152-
typePart := strings.TrimSpace(parts[0])
153-
154-
param := phpParameter{HasDefault: len(parts) > 1}
155-
156-
if param.HasDefault {
157-
param.DefaultValue = fp.sanitizeDefaultValue(strings.TrimSpace(parts[1]))
158-
}
159-
160-
matches := typeNameRegex.FindStringSubmatch(typePart)
161-
162-
if len(matches) < 3 {
163-
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
164-
}
165-
166-
typeStr := strings.TrimSpace(matches[1])
167-
param.Name = strings.TrimSpace(matches[2])
168-
param.IsNullable = strings.HasPrefix(typeStr, "?")
169-
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
170-
171-
return param, nil
172-
}
173-
174-
func (fp *FuncParser) sanitizeDefaultValue(value string) string {
175-
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
176-
return value
177-
}
178-
if strings.ToLower(value) == "null" {
179-
return "null"
180-
}
181-
182-
return strings.Trim(value, `'"`)
183-
}

internal/extgen/funcparser_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,9 @@ func TestParameterParsing(t *testing.T) {
270270
},
271271
}
272272

273-
parser := &FuncParser{}
274273
for _, tt := range tests {
275274
t.Run(tt.name, func(t *testing.T) {
276-
param, err := parser.parseParameter(tt.paramStr)
275+
param, err := parseParameter(tt.paramStr)
277276

278277
if tt.expectError {
279278
assert.Error(t, err, "parseParameter() expected error but got none")

internal/extgen/signature.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package extgen
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
)
8+
9+
// Shared patterns for both function and method signatures.
10+
var (
11+
signaturePattern = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
12+
paramPattern = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
13+
)
14+
15+
// parseSignatureParams splits a "name(params): returnType" signature into its parts.
16+
// Returns name, slice of parameters, return type (without leading "?") and whether it was nullable.
17+
func parseSignatureParams(signature string) (name string, params []phpParameter, returnType string, nullable bool, err error) {
18+
matches := signaturePattern.FindStringSubmatch(signature)
19+
if len(matches) != 4 {
20+
return "", nil, "", false, fmt.Errorf("invalid signature format")
21+
}
22+
23+
name = matches[1]
24+
paramsStr := strings.TrimSpace(matches[2])
25+
returnTypeStr := strings.TrimSpace(matches[3])
26+
27+
nullable = strings.HasPrefix(returnTypeStr, "?")
28+
returnType = strings.TrimPrefix(returnTypeStr, "?")
29+
30+
if paramsStr != "" {
31+
for part := range strings.SplitSeq(paramsStr, ",") {
32+
param, perr := parseParameter(strings.TrimSpace(part))
33+
if perr != nil {
34+
return "", nil, "", false, fmt.Errorf("parsing parameter '%s': %w", part, perr)
35+
}
36+
params = append(params, param)
37+
}
38+
}
39+
40+
return name, params, returnType, nullable, nil
41+
}
42+
43+
// parseParameter parses a single PHP parameter declaration like "?int $name = 42".
44+
func parseParameter(paramStr string) (phpParameter, error) {
45+
parts := strings.SplitN(paramStr, "=", 2)
46+
typePart := strings.TrimSpace(parts[0])
47+
48+
param := phpParameter{HasDefault: len(parts) > 1}
49+
if param.HasDefault {
50+
param.DefaultValue = sanitizeDefaultValue(strings.TrimSpace(parts[1]))
51+
}
52+
53+
matches := paramPattern.FindStringSubmatch(typePart)
54+
if len(matches) < 3 {
55+
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
56+
}
57+
58+
typeStr := strings.TrimSpace(matches[1])
59+
param.Name = strings.TrimSpace(matches[2])
60+
param.IsNullable = strings.HasPrefix(typeStr, "?")
61+
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
62+
63+
return param, nil
64+
}
65+
66+
// sanitizeDefaultValue normalizes a PHP default value literal: keeps array literals,
67+
// preserves "null", and strips surrounding quotes for scalar strings.
68+
func sanitizeDefaultValue(value string) string {
69+
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
70+
return value
71+
}
72+
if strings.EqualFold(value, "null") {
73+
return "null"
74+
}
75+
return strings.Trim(value, `'"`)
76+
}

0 commit comments

Comments
 (0)