Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,10 @@ const MAX_CONNECTIONS = 100
const API_VERSION = "1.2.3"

//export_php:const
const STATUS_OK = iota

//export_php:const
const STATUS_ERROR = iota
const (
STATUS_OK = iota
STATUS_ERROR
)
```

#### Class Constants
Expand All @@ -429,13 +429,11 @@ const STATUS_INACTIVE = 0
const ROLE_ADMIN = "admin"

//export_php:classconst Order
const STATE_PENDING = iota

//export_php:classconst Order
const STATE_PROCESSING = iota

//export_php:classconst Order
const STATE_COMPLETED = iota
const (
STATE_PENDING = iota
STATE_PROCESSING
STATE_COMPLETED
)
```

Class constants are accessible using the class name scope in PHP:
Expand Down
20 changes: 9 additions & 11 deletions docs/fr/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,10 @@ const MAX_CONNECTIONS = 100
const API_VERSION = "1.2.3"

//export_php:const
const STATUS_OK = iota

//export_php:const
const STATUS_ERROR = iota
const (
STATUS_OK = iota
STATUS_ERROR
)
```

#### Constantes de Classe
Expand All @@ -425,13 +425,11 @@ const STATUS_INACTIVE = 0
const ROLE_ADMIN = "admin"

//export_php:classconst Order
const STATE_PENDING = iota

//export_php:classconst Order
const STATE_PROCESSING = iota

//export_php:classconst Order
const STATE_COMPLETED = iota
const (
STATE_PENDING = iota
STATE_PROCESSING
STATE_COMPLETED
)
```

Les constantes de classe sont accessibles en utilisant la portée du nom de classe en PHP :
Expand Down
88 changes: 85 additions & 3 deletions internal/extgen/constparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e
expectClassConstDecl := false
currentClassName := ""
currentConstantValue := 0
inConstBlock := false
exportAllInBlock := false
lastConstValue := ""
lastConstWasIota := false

for scanner.Scan() {
lineNumber++
Expand All @@ -55,7 +59,26 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e
continue
}

if (expectConstDecl || expectClassConstDecl) && strings.HasPrefix(line, "const ") {
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]
Expand All @@ -72,10 +95,11 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e
constant.PhpType = determineConstantType(value)

if constant.IsIota {
// affect a default value because user didn't give one
constant.Value = fmt.Sprintf("%d", currentConstantValue)
constant.PhpType = phpInt
currentConstantValue++
lastConstWasIota = true
lastConstValue = constant.Value
}

constants = append(constants, constant)
Expand All @@ -84,7 +108,65 @@ func (cp *ConstantParser) parse(filename string) (constants []phpConstant, err e
}
expectConstDecl = false
expectClassConstDecl = false
} else if (expectConstDecl || expectClassConstDecl) && !strings.HasPrefix(line, "//") && line != "" {
} 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
Expand Down
175 changes: 174 additions & 1 deletion internal/extgen/constparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func TestConstantParserIotaSequence(t *testing.T) {
//export_php:const
const FirstIota = iota

//export_php:const
//export_php:const
const SecondIota = iota

//export_php:const
Expand All @@ -244,6 +244,179 @@ const ThirdIota = iota`
}
}

func TestConstantParserConstBlock(t *testing.T) {
input := `package main

const (
// export_php:const
STATUS_PENDING = iota

// export_php:const
STATUS_PROCESSING

// export_php:const
STATUS_COMPLETED
)`

tmpDir := t.TempDir()
fileName := filepath.Join(tmpDir, "test.go")
require.NoError(t, os.WriteFile(fileName, []byte(input), 0644))

parser := &ConstantParser{}
constants, err := parser.parse(fileName)
assert.NoError(t, err, "parse() error")

assert.Len(t, constants, 3, "Expected 3 constants")

expectedNames := []string{"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED"}
expectedValues := []string{"0", "1", "2"}

for i, c := range constants {
assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i])
assert.True(t, c.IsIota, "Expected constant %d to be iota", i)
assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i])
assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i)
}
}

func TestConstantParserConstBlockWithBlockLevelDirective(t *testing.T) {
input := `package main

// export_php:const
const (
STATUS_PENDING = iota
STATUS_PROCESSING
STATUS_COMPLETED
)`

tmpDir := t.TempDir()
fileName := filepath.Join(tmpDir, "test.go")
require.NoError(t, os.WriteFile(fileName, []byte(input), 0644))

parser := &ConstantParser{}
constants, err := parser.parse(fileName)
assert.NoError(t, err, "parse() error")

assert.Len(t, constants, 3, "Expected 3 constants")

expectedNames := []string{"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED"}
expectedValues := []string{"0", "1", "2"}

for i, c := range constants {
assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i])
assert.True(t, c.IsIota, "Expected constant %d to be iota", i)
assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i])
assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i)
}
}

func TestConstantParserMixedConstBlockAndIndividual(t *testing.T) {
input := `package main

// export_php:const
const INDIVIDUAL = 42

const (
// export_php:const
BLOCK_ONE = iota

// export_php:const
BLOCK_TWO
)

// export_php:const
const ANOTHER_INDIVIDUAL = "test"`

tmpDir := t.TempDir()
fileName := filepath.Join(tmpDir, "test.go")
require.NoError(t, os.WriteFile(fileName, []byte(input), 0644))

parser := &ConstantParser{}
constants, err := parser.parse(fileName)
assert.NoError(t, err, "parse() error")

assert.Len(t, constants, 4, "Expected 4 constants")

assert.Equal(t, "INDIVIDUAL", constants[0].Name)
assert.Equal(t, "42", constants[0].Value)
assert.Equal(t, phpInt, constants[0].PhpType)

assert.Equal(t, "BLOCK_ONE", constants[1].Name)
assert.Equal(t, "0", constants[1].Value)
assert.True(t, constants[1].IsIota)

assert.Equal(t, "BLOCK_TWO", constants[2].Name)
assert.Equal(t, "1", constants[2].Value)
assert.True(t, constants[2].IsIota)

assert.Equal(t, "ANOTHER_INDIVIDUAL", constants[3].Name)
assert.Equal(t, `"test"`, constants[3].Value)
assert.Equal(t, phpString, constants[3].PhpType)
}

func TestConstantParserClassConstBlock(t *testing.T) {
input := `package main

// export_php:classconst Config
const (
MODE_DEBUG = 1
MODE_PRODUCTION = 2
MODE_TEST = 3
)`

tmpDir := t.TempDir()
fileName := filepath.Join(tmpDir, "test.go")
require.NoError(t, os.WriteFile(fileName, []byte(input), 0644))

parser := &ConstantParser{}
constants, err := parser.parse(fileName)
assert.NoError(t, err, "parse() error")

assert.Len(t, constants, 3, "Expected 3 class constants")

expectedNames := []string{"MODE_DEBUG", "MODE_PRODUCTION", "MODE_TEST"}
expectedValues := []string{"1", "2", "3"}

for i, c := range constants {
assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i])
assert.Equal(t, "Config", c.ClassName, "Expected constant %d to belong to Config class", i)
assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i])
assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i)
}
}

func TestConstantParserClassConstBlockWithIota(t *testing.T) {
input := `package main

// export_php:classconst Status
const (
STATUS_PENDING = iota
STATUS_ACTIVE
STATUS_COMPLETED
)`

tmpDir := t.TempDir()
fileName := filepath.Join(tmpDir, "test.go")
require.NoError(t, os.WriteFile(fileName, []byte(input), 0644))

parser := &ConstantParser{}
constants, err := parser.parse(fileName)
assert.NoError(t, err, "parse() error")

assert.Len(t, constants, 3, "Expected 3 class constants")

expectedNames := []string{"STATUS_PENDING", "STATUS_ACTIVE", "STATUS_COMPLETED"}
expectedValues := []string{"0", "1", "2"}

for i, c := range constants {
assert.Equal(t, expectedNames[i], c.Name, "Expected constant %d name to be '%s'", i, expectedNames[i])
assert.Equal(t, "Status", c.ClassName, "Expected constant %d to belong to Status class", i)
assert.True(t, c.IsIota, "Expected constant %d to be iota", i)
assert.Equal(t, expectedValues[i], c.Value, "Expected constant %d value to be '%s'", i, expectedValues[i])
assert.Equal(t, phpInt, c.PhpType, "Expected constant %d to be phpInt type", i)
}
}

func TestConstantParserTypeDetection(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 1 addition & 0 deletions internal/extgen/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ func TestConstants(t *testing.T) {
[]string{
"TEST_MAX_RETRIES", "TEST_API_VERSION", "TEST_ENABLED", "TEST_PI",
"STATUS_PENDING", "STATUS_PROCESSING", "STATUS_COMPLETED",
"ONE", "TWO",
},
)
require.NoError(t, err, "all constants, functions, and classes should be accessible from PHP")
Expand Down
Loading
Loading