Skip to content

Commit 716c2f8

Browse files
committed
refactor fory compiler
1 parent 1cf3827 commit 716c2f8

31 files changed

Lines changed: 2402 additions & 747 deletions

File tree

compiler/fory_compiler/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
"""FDL (Fory Definition Language) compiler for Apache Fory."""
18+
"""Fory IDL compiler for Apache Fory."""
1919

2020
__version__ = "0.1.0"
2121

22-
from fory_compiler.parser.ast import Schema, Message, Enum, Field, EnumValue, Import
23-
from fory_compiler.parser.parser import Parser
24-
from fory_compiler.parser.lexer import Lexer
22+
from fory_compiler.ir.ast import Schema, Message, Enum, Field, EnumValue, Import
23+
from fory_compiler.frontend.fdl import FDLFrontend
24+
from fory_compiler.frontend.proto import ProtoFrontend
2525

2626
__all__ = [
2727
"Schema",
@@ -30,6 +30,6 @@
3030
"Field",
3131
"EnumValue",
3232
"Import",
33-
"Parser",
34-
"Lexer",
33+
"FDLFrontend",
34+
"ProtoFrontend",
3535
]

compiler/fory_compiler/cli.py

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
"""CLI entry point for the FDL compiler."""
18+
"""CLI entry point for the Fory IDL compiler."""
1919

2020
import argparse
2121
import sys
2222
from pathlib import Path
2323
from typing import Dict, List, Optional, Set
2424

25-
from fory_compiler.parser.lexer import Lexer, LexerError
26-
from fory_compiler.parser.parser import Parser, ParseError
27-
from fory_compiler.parser.ast import Schema
25+
from fory_compiler.frontend.base import FrontendError
26+
from fory_compiler.frontend.fdl import FDLFrontend
27+
from fory_compiler.frontend.proto import ProtoFrontend
28+
from fory_compiler.ir.ast import Schema
29+
from fory_compiler.ir.emitter import FDLEmitter
2830
from fory_compiler.generators.base import GeneratorOptions
2931
from fory_compiler.generators import GENERATORS
3032

@@ -35,13 +37,19 @@ class ImportError(Exception):
3537
pass
3638

3739

38-
def parse_fdl_file(file_path: Path) -> Schema:
39-
"""Parse a single FDL file and return its schema."""
40-
source = file_path.read_text()
41-
lexer = Lexer(source, str(file_path))
42-
tokens = lexer.tokenize()
43-
parser = Parser(tokens)
44-
return parser.parse()
40+
def get_frontend(file_path: Path):
41+
"""Select the correct frontend for a file."""
42+
frontends = [FDLFrontend(), ProtoFrontend()]
43+
for frontend in frontends:
44+
if frontend.supports_file(file_path):
45+
return frontend
46+
raise ValueError(f"Unsupported file extension: {file_path.suffix}")
47+
48+
49+
def parse_idl_file(file_path: Path) -> Schema:
50+
"""Parse a single IDL file and return its schema."""
51+
frontend = get_frontend(file_path)
52+
return frontend.parse_file(file_path)
4553

4654

4755
def resolve_import_path(
@@ -117,7 +125,7 @@ def resolve_imports(
117125
visited.add(file_path)
118126

119127
# Parse the file
120-
schema = parse_fdl_file(file_path)
128+
schema = parse_idl_file(file_path)
121129

122130
# Process imports
123131
imported_enums = []
@@ -152,6 +160,9 @@ def resolve_imports(
152160
imports=schema.imports,
153161
enums=imported_enums + schema.enums,
154162
messages=imported_messages + schema.messages,
163+
options=schema.options,
164+
source_file=schema.source_file,
165+
source_format=schema.source_format,
155166
)
156167

157168
cache[file_path] = merged_schema
@@ -162,23 +173,23 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
162173
"""Parse command line arguments."""
163174
parser = argparse.ArgumentParser(
164175
prog="fory",
165-
description="FDL (Fory Definition Language) compiler",
176+
description="Fory IDL compiler",
166177
)
167178

168179
subparsers = parser.add_subparsers(dest="command", help="Available commands")
169180

170181
# compile command
171182
compile_parser = subparsers.add_parser(
172183
"compile",
173-
help="Compile FDL files to language-specific code",
184+
help="Compile IDL files (.fdl, .proto) to language-specific code",
174185
)
175186

176187
compile_parser.add_argument(
177188
"files",
178189
nargs="+",
179190
type=Path,
180191
metavar="FILE",
181-
help="FDL files to compile",
192+
help="IDL files to compile",
182193
)
183194

184195
compile_parser.add_argument(
@@ -264,6 +275,19 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
264275
help="Go nested type naming style: camelcase or underscore (default)",
265276
)
266277

278+
compile_parser.add_argument(
279+
"--emit-fdl",
280+
action="store_true",
281+
help="Emit translated FDL (for non-FDL inputs) for debugging",
282+
)
283+
284+
compile_parser.add_argument(
285+
"--emit-fdl-path",
286+
type=Path,
287+
default=None,
288+
help="Write translated FDL to this path (file or directory)",
289+
)
290+
267291
return parser.parse_args(args)
268292

269293

@@ -290,11 +314,13 @@ def compile_file(
290314
package_override: Optional[str] = None,
291315
import_paths: Optional[List[Path]] = None,
292316
go_nested_type_style: Optional[str] = None,
317+
emit_fdl: bool = False,
318+
emit_fdl_path: Optional[Path] = None,
293319
) -> bool:
294-
"""Compile a single FDL file with import resolution.
320+
"""Compile a single IDL file with import resolution.
295321
296322
Args:
297-
file_path: Path to the FDL file
323+
file_path: Path to the IDL file
298324
lang_output_dirs: Dictionary mapping language name to output directory
299325
package_override: Optional package name override
300326
import_paths: List of import search paths
@@ -307,7 +333,7 @@ def compile_file(
307333
except OSError as e:
308334
print(f"Error reading {file_path}: {e}", file=sys.stderr)
309335
return False
310-
except (LexerError, ParseError) as e:
336+
except (FrontendError, ValueError) as e:
311337
print(f"Error: {e}", file=sys.stderr)
312338
return False
313339
except ImportError as e:
@@ -318,6 +344,24 @@ def compile_file(
318344
if schema.imports:
319345
print(f" Resolved {len(schema.imports)} import(s)")
320346

347+
if emit_fdl:
348+
emitter = FDLEmitter(schema)
349+
fdl_content = emitter.emit()
350+
if emit_fdl_path:
351+
target = emit_fdl_path
352+
if target.exists() and target.is_dir():
353+
target = target / f"{file_path.stem}.fdl"
354+
elif str(target).endswith("/") or str(target).endswith("\\"):
355+
target.mkdir(parents=True, exist_ok=True)
356+
target = target / f"{file_path.stem}.fdl"
357+
target.parent.mkdir(parents=True, exist_ok=True)
358+
target.write_text(fdl_content)
359+
print(f" Emitted FDL: {target}")
360+
else:
361+
print("=== Translated FDL ===")
362+
print(fdl_content.rstrip())
363+
print("======================")
364+
321365
# Validate merged schema
322366
errors = schema.validate()
323367
if errors:
@@ -417,6 +461,8 @@ def cmd_compile(args: argparse.Namespace) -> int:
417461
args.package,
418462
import_paths,
419463
args.go_nested_type_style,
464+
args.emit_fdl,
465+
args.emit_fdl_path,
420466
):
421467
success = False
422468

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
"""Frontends for Fory IDL compilation."""
19+
20+
from fory_compiler.frontend.base import BaseFrontend, FrontendError
21+
22+
__all__ = ["BaseFrontend", "FrontendError"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
"""Frontend base classes for Fory IDL compilation."""
19+
20+
from abc import ABC, abstractmethod
21+
from pathlib import Path
22+
from typing import List
23+
24+
from fory_compiler.ir.ast import Schema
25+
26+
27+
class FrontendError(Exception):
28+
"""Error during frontend parsing or translation."""
29+
30+
def __init__(self, message: str, file: str, line: int, column: int):
31+
super().__init__(f"{file}:{line}:{column}: {message}")
32+
self.message = message
33+
self.file = file
34+
self.line = line
35+
self.column = column
36+
37+
38+
class BaseFrontend(ABC):
39+
"""Base class for all IDL frontends."""
40+
41+
extensions: List[str] = []
42+
43+
@abstractmethod
44+
def parse(self, source: str, filename: str = "<input>") -> Schema:
45+
"""Parse source and return a Fory IR schema."""
46+
47+
def parse_file(self, path: Path) -> Schema:
48+
"""Parse a file and return a Fory IR schema."""
49+
return self.parse(path.read_text(), str(path))
50+
51+
def supports_file(self, path: Path) -> bool:
52+
"""Return True if this frontend handles the file extension."""
53+
return path.suffix.lower() in self.extensions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
"""FDL frontend."""
19+
20+
from fory_compiler.frontend.base import BaseFrontend, FrontendError
21+
from fory_compiler.ir.ast import Schema
22+
from fory_compiler.frontend.fdl.lexer import Lexer, LexerError
23+
from fory_compiler.frontend.fdl.parser import Parser, ParseError
24+
25+
26+
class FDLFrontend(BaseFrontend):
27+
"""Frontend for Fory Definition Language (.fdl)."""
28+
29+
extensions = [".fdl"]
30+
31+
def parse(self, source: str, filename: str = "<input>") -> Schema:
32+
try:
33+
lexer = Lexer(source, filename)
34+
tokens = lexer.tokenize()
35+
parser = Parser(tokens)
36+
schema = parser.parse()
37+
except (LexerError, ParseError) as exc:
38+
raise FrontendError(exc.message, filename, exc.line, exc.column) from exc
39+
schema.source_file = filename
40+
schema.source_format = "fdl"
41+
return schema
42+
43+
44+
__all__ = ["FDLFrontend"]
File renamed without changes.

0 commit comments

Comments
 (0)