Skip to content

Commit 4ca8fb8

Browse files
committed
use [key=value] for messsage id
1 parent 3212508 commit 4ca8fb8

File tree

15 files changed

+322
-132
lines changed

15 files changed

+322
-132
lines changed

compiler/README.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ Create a `.fdl` file:
3737
```fdl
3838
package demo;
3939
40-
enum Color @101 {
40+
enum Color [id=101] {
4141
GREEN = 0;
4242
RED = 1;
4343
BLUE = 2;
4444
}
4545
46-
message Dog @102 {
46+
message Dog [id=102] {
4747
optional string name = 1;
4848
int32 age = 2;
4949
}
5050
51-
message Cat @103 {
51+
message Cat [id=103] {
5252
ref Dog friend = 1;
5353
optional string name = 2;
5454
repeated string tags = 3;
@@ -125,7 +125,7 @@ Imports are resolved relative to the importing file. All types from imported fil
125125
// common.fdl
126126
package common;
127127
128-
message Address @100 {
128+
message Address [id=100] {
129129
string street = 1;
130130
string city = 2;
131131
}
@@ -136,7 +136,7 @@ message Address @100 {
136136
package user;
137137
import "common.fdl";
138138
139-
message User @101 {
139+
message User [id=101] {
140140
string name = 1;
141141
Address address = 2; // Uses imported type
142142
}
@@ -145,7 +145,7 @@ message User @101 {
145145
### Enum Definition
146146

147147
```fdl
148-
enum Status @100 {
148+
enum Status [id=100] {
149149
PENDING = 0;
150150
ACTIVE = 1;
151151
INACTIVE = 2;
@@ -155,22 +155,23 @@ enum Status @100 {
155155
### Message Definition
156156

157157
```fdl
158-
message User @101 {
158+
message User [id=101] {
159159
string name = 1;
160160
int32 age = 2;
161161
optional string email = 3;
162162
}
163163
```
164164

165-
### Type ID vs Name-based Registration
165+
### Type Options
166166

167-
Types with `@<id>` use numeric type IDs for efficient serialization:
167+
Types can have options specified in brackets after the name:
168168

169169
```fdl
170-
message User @101 { ... } // Registered with type ID 101
170+
message User [id=101] { ... } // Registered with type ID 101
171+
message User [id=101, deprecated=true] { ... } // Multiple options
171172
```
172173

173-
Types without `@<id>` use namespace-based registration:
174+
Types without `[id=...]` use namespace-based registration:
174175

175176
```fdl
176177
message Config { ... } // Registered as "package.Config"

compiler/examples/common.fdl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
package common;
2020

2121
// Status enum used across multiple domains
22-
enum Status @100 {
22+
enum Status [id=100] {
2323
PENDING = 0;
2424
ACTIVE = 1;
2525
COMPLETED = 2;
2626
CANCELLED = 3;
2727
}
2828

2929
// Address type for shared use
30-
message Address @101 {
30+
message Address [id=101] {
3131
string street = 1;
3232
string city = 2;
3333
string state = 3;
@@ -36,7 +36,7 @@ message Address @101 {
3636
}
3737

3838
// Contact information
39-
message Contact @102 {
39+
message Contact [id=102] {
4040
optional string email = 1;
4141
optional string phone = 2;
4242
}

compiler/examples/demo.fdl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22

33
package demo;
44

5-
enum Color @101 {
5+
enum Color [id=101] {
66
GREEN = 0;
77
RED = 1;
88
BLUE = 2;
99
WHITE = 3;
1010
}
1111

12-
message Item @102 {
12+
message Item [id=102] {
1313
string name = 1;
1414
}
1515

16-
message Dog @103 {
16+
message Dog [id=103] {
1717
optional string name = 1;
1818
int32 age = 2;
1919
}
2020

21-
message Cat @104 {
21+
message Cat [id=104] {
2222
ref Dog friend = 1;
2323
optional string name = 2;
2424
repeated string tags = 3;
@@ -27,7 +27,7 @@ message Cat @104 {
2727
}
2828

2929
// Demonstrates primitive arrays for numeric types
30-
message SensorData @105 {
30+
message SensorData [id=105] {
3131
string sensor_id = 1;
3232
repeated int32 readings = 2;
3333
repeated float64 temperatures = 3;

compiler/examples/user.fdl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ package user;
2222
import "common.fdl";
2323

2424
// User profile using imported types
25-
message User @200 {
25+
message User [id=200] {
2626
string id = 1;
2727
string name = 2;
2828
optional Address home_address = 3;
@@ -32,7 +32,7 @@ message User @200 {
3232
}
3333

3434
// User preferences
35-
message UserPreferences @201 {
35+
message UserPreferences [id=201] {
3636
ref User user = 1;
3737
string language = 2;
3838
string timezone = 3;

compiler/fory_compiler/parser/lexer.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ class TokenType(Enum):
4747
IDENT = auto()
4848
INT = auto()
4949
STRING = auto() # "quoted string"
50-
TYPE_ID = auto() # @123
5150

5251
# Punctuation
5352
LBRACE = auto() # {
@@ -215,20 +214,6 @@ def read_number(self) -> str:
215214
self.advance()
216215
return self.source[start : self.pos]
217216

218-
def read_type_id(self) -> str:
219-
"""Read a type ID (@123)."""
220-
self.advance() # consume @
221-
if not self.peek().isdigit():
222-
raise LexerError(
223-
f"Expected digit after '@', got '{self.peek()}'",
224-
self.line,
225-
self.column,
226-
)
227-
start = self.pos
228-
while not self.at_end() and self.peek().isdigit():
229-
self.advance()
230-
return self.source[start : self.pos]
231-
232217
def read_string(self) -> str:
233218
"""Read a quoted string literal."""
234219
quote_char = self.advance() # consume opening quote
@@ -279,11 +264,6 @@ def next_token(self) -> Token:
279264
value = self.read_string()
280265
return Token(TokenType.STRING, value, line, column)
281266

282-
# Type ID: @123
283-
if ch == "@":
284-
value = self.read_type_id()
285-
return Token(TokenType.TYPE_ID, value, line, column)
286-
287267
# Punctuation
288268
if ch in self.PUNCTUATION:
289269
self.advance()

compiler/fory_compiler/parser/parser.py

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
"edition_defaults",
4646
"features",
4747
}
48+
49+
# Known type-level options (for messages and enums)
50+
KNOWN_TYPE_OPTIONS: Set[str] = {
51+
"id",
52+
"deprecated",
53+
}
4854
from fory_compiler.parser.ast import (
4955
Schema,
5056
Message,
@@ -256,15 +262,17 @@ def parse_import(self) -> Import:
256262
)
257263

258264
def parse_enum(self) -> Enum:
259-
"""Parse an enum: enum Color @101 { ... }"""
265+
"""Parse an enum: enum Color [id=101] { ... }"""
260266
start = self.current()
261267
self.consume(TokenType.ENUM)
262268
name = self.consume(TokenType.IDENT, "Expected enum name").value
263269

264-
# Optional type ID: @101
270+
# Optional type options: [id=101, deprecated=true]
265271
type_id = None
266-
if self.match(TokenType.TYPE_ID):
267-
type_id = int(self.previous().value)
272+
if self.check(TokenType.LBRACKET):
273+
options = self.parse_type_options(name)
274+
if "id" in options:
275+
type_id = options["id"]
268276

269277
self.consume(TokenType.LBRACE, "Expected '{' after enum name")
270278

@@ -407,7 +415,7 @@ def parse_enum_value(self) -> EnumValue:
407415
)
408416

409417
def parse_message(self) -> Message:
410-
"""Parse a message: message Dog @102 { ... }
418+
"""Parse a message: message Dog [id=102] { ... }
411419
412420
Supports nested messages and enums:
413421
message Outer {
@@ -420,10 +428,12 @@ def parse_message(self) -> Message:
420428
self.consume(TokenType.MESSAGE)
421429
name = self.consume(TokenType.IDENT, "Expected message name").value
422430

423-
# Optional type ID: @102
431+
# Optional type options: [id=102, deprecated=true]
424432
type_id = None
425-
if self.match(TokenType.TYPE_ID):
426-
type_id = int(self.previous().value)
433+
if self.check(TokenType.LBRACKET):
434+
options = self.parse_type_options(name)
435+
if "id" in options:
436+
type_id = options["id"]
427437

428438
self.consume(TokenType.LBRACE, "Expected '{' after message name")
429439

@@ -544,6 +554,65 @@ def parse_field_options(self, field_name: str):
544554

545555
self.consume(TokenType.RBRACKET, "Expected ']' after field options")
546556

557+
def parse_type_options(self, type_name: str) -> dict:
558+
"""Parse type options: [id=100, deprecated=true]
559+
560+
Returns a dict of option names to values.
561+
Warns about unknown options.
562+
"""
563+
self.consume(TokenType.LBRACKET)
564+
options = {}
565+
566+
while True:
567+
# Parse option name
568+
name_token = self.consume(TokenType.IDENT, "Expected option name")
569+
option_name = name_token.value
570+
571+
self.consume(TokenType.EQUALS, "Expected '=' after option name")
572+
573+
# Parse option value (can be string, bool, int, or identifier)
574+
if self.check(TokenType.STRING):
575+
option_value = self.advance().value
576+
elif self.check(TokenType.TRUE):
577+
self.advance()
578+
option_value = True
579+
elif self.check(TokenType.FALSE):
580+
self.advance()
581+
option_value = False
582+
elif self.check(TokenType.INT):
583+
option_value = int(self.advance().value)
584+
elif self.check(TokenType.IDENT):
585+
option_value = self.advance().value
586+
else:
587+
raise self.error(f"Expected option value, got {self.current().type.name}")
588+
589+
# Validate 'id' option must be a positive integer
590+
if option_name == "id":
591+
if not isinstance(option_value, int):
592+
raise self.error(f"Type option 'id' must be an integer, got {type(option_value).__name__}")
593+
if option_value <= 0:
594+
raise self.error(f"Type option 'id' must be a positive integer, got {option_value}")
595+
596+
# Warn about unknown type options
597+
if option_name not in KNOWN_TYPE_OPTIONS:
598+
warnings.warn(
599+
f"Line {name_token.line}: ignoring unknown type option '{option_name}' on type '{type_name}'",
600+
stacklevel=2
601+
)
602+
603+
options[option_name] = option_value
604+
605+
# Check for comma (more options) or closing bracket (end)
606+
if self.check(TokenType.COMMA):
607+
self.advance()
608+
elif self.check(TokenType.RBRACKET):
609+
break
610+
else:
611+
raise self.error("Expected ',' or ']' in type options")
612+
613+
self.consume(TokenType.RBRACKET, "Expected ']' after type options")
614+
return options
615+
547616
def parse_type(self) -> FieldType:
548617
"""Parse a type: int32, string, map<K, V>, Parent.Child, or a named type."""
549618
if self.check(TokenType.MAP):

0 commit comments

Comments
 (0)