Skip to content

Commit 7300c70

Browse files
committed
support protobuf option style config
1 parent 4b92b53 commit 7300c70

File tree

7 files changed

+989
-103
lines changed

7 files changed

+989
-103
lines changed

compiler/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,46 @@ message Example {
220220
}
221221
```
222222

223+
### Fory Extension Options
224+
225+
FDL supports protobuf-style extension options using the `(fory)` prefix:
226+
227+
**File-level options:**
228+
229+
```fdl
230+
option (fory).use_record_for_java_message = true;
231+
option (fory).polymorphism = true;
232+
```
233+
234+
**Message/Enum options:**
235+
236+
```fdl
237+
message MyMessage {
238+
option (fory).id = 100;
239+
option (fory).compatible = true;
240+
option (fory).use_record_for_java = true;
241+
string name = 1;
242+
}
243+
244+
enum Status {
245+
option (fory).id = 101;
246+
UNKNOWN = 0;
247+
ACTIVE = 1;
248+
}
249+
```
250+
251+
**Field options:**
252+
253+
```fdl
254+
message Example {
255+
MyType friend = 1 [(fory).ref = true];
256+
string nickname = 2 [(fory).nullable = true];
257+
MyType data = 3 [(fory).ref = true, (fory).nullable = true];
258+
}
259+
```
260+
261+
See `extension/fory_options.proto` for the complete list of available options.
262+
223263
## Architecture
224264

225265
```
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
// Fory Options for FDL (Fory Definition Language)
19+
// @author: chaokunyang
20+
//
21+
// This file defines custom options that can be used in FDL schemas
22+
// to control Fory serialization behavior. Copy this file into your
23+
// project to use these options.
24+
//
25+
// Usage Examples:
26+
//
27+
// File-level options:
28+
// option (fory).use_record_for_java_message = true;
29+
//
30+
// Message options:
31+
// message MyMessage {
32+
// option (fory).id = 100;
33+
// option (fory).compatible = true;
34+
// option (fory).use_record_for_java = true;
35+
// }
36+
//
37+
// Enum options:
38+
// enum Status {
39+
// option (fory).id = 101;
40+
// }
41+
//
42+
// Field options:
43+
// message Example {
44+
// MyType field = 1 [(fory).ref = true, (fory).nullable = true];
45+
// }
46+
47+
syntax = "proto3";
48+
49+
package fory;
50+
51+
import "google/protobuf/descriptor.proto";
52+
53+
// ===========================================================================
54+
// File-Level Options
55+
// ===========================================================================
56+
// These options apply to the entire FDL file and affect all generated code.
57+
58+
message ForyFileOptions {
59+
// Generate Java records instead of classes for all messages in this file.
60+
// Only applies to Java code generation.
61+
// Default: false (generates traditional POJOs)
62+
optional bool use_record_for_java_message = 1;
63+
64+
// Enable polymorphism support for all types in this file.
65+
// When true, type metadata is included in serialization for polymorphic dispatch.
66+
// Default: false
67+
optional bool polymorphism = 2;
68+
}
69+
70+
extend google.protobuf.FileOptions {
71+
optional ForyFileOptions fory = 50001;
72+
}
73+
74+
// ===========================================================================
75+
// Message-Level Options
76+
// ===========================================================================
77+
// These options apply to individual message definitions.
78+
79+
message ForyMessageOptions {
80+
// Unique type ID for cross-language registration.
81+
// Used for efficient type lookup during deserialization.
82+
// Must be a positive integer unique within the schema.
83+
// If not specified, namespace-based registration is used.
84+
optional int32 id = 1;
85+
86+
// Enable schema compatibility mode for this message.
87+
// When true, fields can be added/removed without breaking compatibility.
88+
// Default: false (strict schema matching)
89+
optional bool compatible = 2;
90+
91+
// Generate a Java record instead of a class for this specific message.
92+
// Overrides file-level use_record_for_java_message setting.
93+
// Only applies to Java code generation.
94+
optional bool use_record_for_java = 3;
95+
96+
// Mark this message as deprecated.
97+
// Generates appropriate deprecation annotations in target languages.
98+
optional bool deprecated = 4;
99+
100+
// Custom namespace for type registration.
101+
// Overrides the default package-based namespace.
102+
optional string namespace = 5;
103+
}
104+
105+
extend google.protobuf.MessageOptions {
106+
optional ForyMessageOptions fory = 50001;
107+
}
108+
109+
// ===========================================================================
110+
// Enum-Level Options
111+
// ===========================================================================
112+
// These options apply to individual enum definitions.
113+
114+
message ForyEnumOptions {
115+
// Unique type ID for cross-language registration.
116+
// Used for efficient type lookup during deserialization.
117+
// Must be a positive integer unique within the schema.
118+
optional int32 id = 1;
119+
120+
// Mark this enum as deprecated.
121+
optional bool deprecated = 2;
122+
}
123+
124+
extend google.protobuf.EnumOptions {
125+
optional ForyEnumOptions fory = 50001;
126+
}
127+
128+
// ===========================================================================
129+
// Field-Level Options
130+
// ===========================================================================
131+
// These options apply to individual field definitions.
132+
133+
message ForyFieldOptions {
134+
// Enable reference tracking for this field.
135+
// When true, Fory tracks object references to handle:
136+
// - Circular references (e.g., tree structures with parent pointers)
137+
// - Shared references (same object referenced multiple times)
138+
// Default: false
139+
optional bool ref = 1;
140+
141+
// Mark this field as nullable.
142+
// When true, the field can be null/None/nil.
143+
// Default: false (field must have a value)
144+
optional bool nullable = 2;
145+
146+
// Mark this field as deprecated.
147+
optional bool deprecated = 3;
148+
}
149+
150+
extend google.protobuf.FieldOptions {
151+
optional ForyFieldOptions fory = 50001;
152+
}

compiler/fory_compiler/parser/ast.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class Field:
108108
number: int
109109
optional: bool = False
110110
ref: bool = False
111+
options: dict = field(default_factory=dict)
111112
line: int = 0
112113
column: int = 0
113114

@@ -118,7 +119,8 @@ def __repr__(self) -> str:
118119
if self.ref:
119120
modifiers.append("ref")
120121
mod_str = " ".join(modifiers) + " " if modifiers else ""
121-
return f"Field({mod_str}{self.field_type} {self.name} = {self.number})"
122+
opts_str = f" [{self.options}]" if self.options else ""
123+
return f"Field({mod_str}{self.field_type} {self.name} = {self.number}{opts_str})"
122124

123125

124126
@dataclass
@@ -155,15 +157,17 @@ class Message:
155157
fields: List[Field] = field(default_factory=list)
156158
nested_messages: List["Message"] = field(default_factory=list)
157159
nested_enums: List["Enum"] = field(default_factory=list)
160+
options: dict = field(default_factory=dict)
158161
line: int = 0
159162
column: int = 0
160163

161164
def __repr__(self) -> str:
162-
id_str = f" @{self.type_id}" if self.type_id is not None else ""
165+
id_str = f" [id={self.type_id}]" if self.type_id is not None else ""
163166
nested_str = ""
164167
if self.nested_messages or self.nested_enums:
165168
nested_str = f", nested={len(self.nested_messages)}msg+{len(self.nested_enums)}enum"
166-
return f"Message({self.name}{id_str}, fields={self.fields}{nested_str})"
169+
opts_str = f", options={len(self.options)}" if self.options else ""
170+
return f"Message({self.name}{id_str}, fields={self.fields}{nested_str}{opts_str})"
167171

168172
def get_nested_type(self, name: str) -> Optional[Union["Message", "Enum"]]:
169173
"""Look up a nested type by name."""
@@ -183,12 +187,14 @@ class Enum:
183187
name: str
184188
type_id: Optional[int]
185189
values: List[EnumValue] = field(default_factory=list)
190+
options: dict = field(default_factory=dict)
186191
line: int = 0
187192
column: int = 0
188193

189194
def __repr__(self) -> str:
190-
id_str = f" @{self.type_id}" if self.type_id is not None else ""
191-
return f"Enum({self.name}{id_str}, values={self.values})"
195+
id_str = f" [id={self.type_id}]" if self.type_id is not None else ""
196+
opts_str = f", options={len(self.options)}" if self.options else ""
197+
return f"Enum({self.name}{id_str}, values={self.values}{opts_str})"
192198

193199

194200
@dataclass

compiler/fory_compiler/parser/lexer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class TokenType(Enum):
5353
RBRACE = auto() # }
5454
LBRACKET = auto() # [
5555
RBRACKET = auto() # ]
56+
LPAREN = auto() # (
57+
RPAREN = auto() # )
5658
LANGLE = auto() # <
5759
RANGLE = auto() # >
5860
SEMI = auto() # ;
@@ -113,6 +115,8 @@ class Lexer:
113115
"}": TokenType.RBRACE,
114116
"[": TokenType.LBRACKET,
115117
"]": TokenType.RBRACKET,
118+
"(": TokenType.LPAREN,
119+
")": TokenType.RPAREN,
116120
"<": TokenType.LANGLE,
117121
">": TokenType.RANGLE,
118122
";": TokenType.SEMI,

0 commit comments

Comments
 (0)