Skip to content

Commit 98dd3df

Browse files
committed
support protobuf option style config
1 parent 641d61a commit 98dd3df

File tree

7 files changed

+988
-103
lines changed

7 files changed

+988
-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: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
//
20+
// This file defines custom options that can be used in FDL schemas
21+
// to control Fory serialization behavior. Copy this file into your
22+
// project to use these options.
23+
//
24+
// Usage Examples:
25+
//
26+
// File-level options:
27+
// option (fory).use_record_for_java_message = true;
28+
//
29+
// Message options:
30+
// message MyMessage {
31+
// option (fory).id = 100;
32+
// option (fory).compatible = true;
33+
// option (fory).use_record_for_java = true;
34+
// }
35+
//
36+
// Enum options:
37+
// enum Status {
38+
// option (fory).id = 101;
39+
// }
40+
//
41+
// Field options:
42+
// message Example {
43+
// MyType field = 1 [(fory).ref = true, (fory).nullable = true];
44+
// }
45+
46+
syntax = "proto3";
47+
48+
package fory;
49+
50+
import "google/protobuf/descriptor.proto";
51+
52+
// ===========================================================================
53+
// File-Level Options
54+
// ===========================================================================
55+
// These options apply to the entire FDL file and affect all generated code.
56+
57+
message ForyFileOptions {
58+
// Generate Java records instead of classes for all messages in this file.
59+
// Only applies to Java code generation.
60+
// Default: false (generates traditional POJOs)
61+
optional bool use_record_for_java_message = 1;
62+
63+
// Enable polymorphism support for all types in this file.
64+
// When true, type metadata is included in serialization for polymorphic dispatch.
65+
// Default: false
66+
optional bool polymorphism = 2;
67+
}
68+
69+
extend google.protobuf.FileOptions {
70+
optional ForyFileOptions fory = 50001;
71+
}
72+
73+
// ===========================================================================
74+
// Message-Level Options
75+
// ===========================================================================
76+
// These options apply to individual message definitions.
77+
78+
message ForyMessageOptions {
79+
// Unique type ID for cross-language registration.
80+
// Used for efficient type lookup during deserialization.
81+
// Must be a positive integer unique within the schema.
82+
// If not specified, namespace-based registration is used.
83+
optional int32 id = 1;
84+
85+
// Enable schema compatibility mode for this message.
86+
// When true, fields can be added/removed without breaking compatibility.
87+
// Default: false (strict schema matching)
88+
optional bool compatible = 2;
89+
90+
// Generate a Java record instead of a class for this specific message.
91+
// Overrides file-level use_record_for_java_message setting.
92+
// Only applies to Java code generation.
93+
optional bool use_record_for_java = 3;
94+
95+
// Mark this message as deprecated.
96+
// Generates appropriate deprecation annotations in target languages.
97+
optional bool deprecated = 4;
98+
99+
// Custom namespace for type registration.
100+
// Overrides the default package-based namespace.
101+
optional string namespace = 5;
102+
}
103+
104+
extend google.protobuf.MessageOptions {
105+
optional ForyMessageOptions fory = 50001;
106+
}
107+
108+
// ===========================================================================
109+
// Enum-Level Options
110+
// ===========================================================================
111+
// These options apply to individual enum definitions.
112+
113+
message ForyEnumOptions {
114+
// Unique type ID for cross-language registration.
115+
// Used for efficient type lookup during deserialization.
116+
// Must be a positive integer unique within the schema.
117+
optional int32 id = 1;
118+
119+
// Mark this enum as deprecated.
120+
optional bool deprecated = 2;
121+
}
122+
123+
extend google.protobuf.EnumOptions {
124+
optional ForyEnumOptions fory = 50001;
125+
}
126+
127+
// ===========================================================================
128+
// Field-Level Options
129+
// ===========================================================================
130+
// These options apply to individual field definitions.
131+
132+
message ForyFieldOptions {
133+
// Enable reference tracking for this field.
134+
// When true, Fory tracks object references to handle:
135+
// - Circular references (e.g., tree structures with parent pointers)
136+
// - Shared references (same object referenced multiple times)
137+
// Default: false
138+
optional bool ref = 1;
139+
140+
// Mark this field as nullable.
141+
// When true, the field can be null/None/nil.
142+
// Default: false (field must have a value)
143+
optional bool nullable = 2;
144+
145+
// Mark this field as deprecated.
146+
optional bool deprecated = 3;
147+
}
148+
149+
extend google.protobuf.FieldOptions {
150+
optional ForyFieldOptions fory = 50001;
151+
}

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)