Skip to content

Commit 24a1178

Browse files
committed
update to handle tagging specific functions
Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent ab3f06d commit 24a1178

6 files changed

Lines changed: 195 additions & 153 deletions

File tree

docs/extensions.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -272,26 +272,28 @@ This design ensures that your Go code has complete control over how the object's
272272
The generator supports defining module initialization and shutdown functions using the `//export_php:module` directive.
273273
This allows you to perform setup and cleanup operations when your extension is loaded and unloaded.
274274

275-
```go
276-
//export_php:module init=initializeModule, shutdown=cleanupModule
277-
```
278-
279-
The `init` parameter specifies a function that will be called when the module is initialized, and the `shutdown` parameter specifies a function that will be called when the module is shut down. Both parameters are optional; you can specify either one, both, or none.
280-
281-
The specified functions should be defined in your Go code:
275+
To define an initialization function, tag it with `//export_php:module init`:
282276

283277
```go
278+
//export_php:module init
284279
func initializeModule() {
285-
// Perform initialization tasks
286-
// For example, set up global resources, initialize data structures, etc.
280+
// Perform initialization tasks
281+
// For example, set up global resources, initialize data structures, etc.
287282
}
283+
```
284+
285+
To define a shutdown function, tag it with `//export_php:module shutdown`:
288286

287+
```go
288+
//export_php:module shutdown
289289
func cleanupModule() {
290-
// Perform cleanup tasks
291-
// For example, free resources, close connections, etc.
290+
// Perform cleanup tasks
291+
// For example, free resources, close connections, etc.
292292
}
293293
```
294294

295+
You can define either one, both, or none of these functions. The initialization function will be called when the PHP module is loaded, and the shutdown function will be called when the PHP module is unloaded.
296+
295297
### Declaring Constants
296298

297299
The generator supports exporting Go constants to PHP using two directives: `//export_php:const` for global constants and `//export_php:classconstant` for class constants. This allows you to share configuration values, status codes, and other constants between Go and PHP code.

internal/extgen/gofile.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type goTemplateData struct {
2525
InternalFunctions []string
2626
Functions []phpFunction
2727
Classes []phpClass
28+
Module *phpModule
2829
}
2930

3031
func (gg *GoFileGenerator) generate() error {
@@ -62,6 +63,7 @@ func (gg *GoFileGenerator) buildContent() (string, error) {
6263
InternalFunctions: internalFunctions,
6364
Functions: gg.generator.Functions,
6465
Classes: classes,
66+
Module: gg.generator.Module,
6567
})
6668

6769
if err != nil {

internal/extgen/moduleParser.go

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ package extgen
22

33
import (
44
"bufio"
5+
"fmt"
56
"os"
67
"regexp"
78
"strings"
89
)
910

10-
var phpModuleParser = regexp.MustCompile(`//\s*export_php:module\s*(.*)`)
11+
var phpModuleParser = regexp.MustCompile(`//\s*export_php:module\s+(init|shutdown)`)
1112

1213
// phpModule represents a PHP module with optional init and shutdown functions
1314
type phpModule struct {
1415
InitFunc string // Name of the init function
16+
InitCode string // Code of the init function
1517
ShutdownFunc string // Name of the shutdown function
18+
ShutdownCode string // Code of the shutdown function
1619
}
1720

1821
// ModuleParser parses PHP module directives from Go source files
@@ -27,47 +30,93 @@ func (mp *ModuleParser) parse(filename string) (*phpModule, error) {
2730
defer file.Close()
2831

2932
scanner := bufio.NewScanner(file)
33+
module := &phpModule{}
34+
var currentDirective string
35+
var lineNumber int
36+
3037
for scanner.Scan() {
38+
lineNumber++
3139
line := strings.TrimSpace(scanner.Text())
40+
3241
if matches := phpModuleParser.FindStringSubmatch(line); matches != nil {
33-
moduleInfo := strings.TrimSpace(matches[1])
34-
return mp.parseModuleInfo(moduleInfo)
42+
directiveType := matches[1]
43+
currentDirective = directiveType
44+
continue
45+
}
46+
47+
// If we have a current directive and encounter a non-comment line
48+
// that doesn't start with "func ", reset the current directive
49+
if currentDirective != "" && (line == "" || (!strings.HasPrefix(line, "//") && !strings.HasPrefix(line, "func "))) {
50+
currentDirective = ""
51+
continue
52+
}
53+
54+
if currentDirective != "" && strings.HasPrefix(line, "func ") {
55+
funcName, funcCode, err := mp.extractGoFunction(scanner, line)
56+
if err != nil {
57+
return nil, fmt.Errorf("extracting Go function at line %d: %w", lineNumber, err)
58+
}
59+
60+
switch currentDirective {
61+
case "init":
62+
module.InitFunc = funcName
63+
module.InitCode = funcCode
64+
case "shutdown":
65+
module.ShutdownFunc = funcName
66+
module.ShutdownCode = funcCode
67+
}
68+
69+
currentDirective = ""
3570
}
3671
}
3772

38-
// No module directive found
39-
return nil, nil
73+
// If we found no module functions, return nil
74+
if module.InitFunc == "" && module.ShutdownFunc == "" {
75+
return nil, nil
76+
}
77+
78+
return module, scanner.Err()
4079
}
4180

42-
// parseModuleInfo parses the module info string to extract init and shutdown function names
43-
func (mp *ModuleParser) parseModuleInfo(moduleInfo string) (*phpModule, error) {
44-
module := &phpModule{}
81+
// extractGoFunction extracts the function name and code from a function declaration
82+
func (mp *ModuleParser) extractGoFunction(scanner *bufio.Scanner, firstLine string) (string, string, error) {
83+
// Extract function name from the first line
84+
funcNameRegex := regexp.MustCompile(`func\s+([a-zA-Z0-9_]+)`)
85+
matches := funcNameRegex.FindStringSubmatch(firstLine)
86+
if len(matches) < 2 {
87+
return "", "", fmt.Errorf("could not extract function name from line: %s", firstLine)
88+
}
89+
funcName := matches[1]
4590

46-
// Split the module info by commas
47-
parts := strings.Split(moduleInfo, ",")
91+
// Collect the function code
92+
goFunc := firstLine + "\n"
93+
braceCount := 0
4894

49-
for _, part := range parts {
50-
part = strings.TrimSpace(part)
51-
if part == "" {
52-
continue
95+
// Count opening braces in the first line
96+
for _, char := range firstLine {
97+
if char == '{' {
98+
braceCount++
5399
}
100+
}
101+
102+
// Continue reading until we find the matching closing brace
103+
for braceCount > 0 && scanner.Scan() {
104+
line := scanner.Text()
105+
goFunc += line + "\n"
54106

55-
// Split each part by equals sign
56-
keyValue := strings.SplitN(part, "=", 2)
57-
if len(keyValue) != 2 {
58-
continue
107+
for _, char := range line {
108+
switch char {
109+
case '{':
110+
braceCount++
111+
case '}':
112+
braceCount--
113+
}
59114
}
60115

61-
key := strings.TrimSpace(keyValue[0])
62-
value := strings.TrimSpace(keyValue[1])
63-
64-
switch key {
65-
case "init":
66-
module.InitFunc = value
67-
case "shutdown":
68-
module.ShutdownFunc = value
116+
if braceCount == 0 {
117+
break
69118
}
70119
}
71120

72-
return module, nil
121+
return funcName, goFunc, nil
73122
}

0 commit comments

Comments
 (0)