Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
134 changes: 134 additions & 0 deletions compiler/fory_compiler/generators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,137 @@ def get_license_header(self, comment_prefix: str = "//") -> str:
return "\n".join(
f"{comment_prefix} {line}" if line else comment_prefix for line in lines
)

def wrap_line(
self,
line: str,
max_width: int = 80,
indent: str = "",
continuation_indent: str = None,
) -> List[str]:
"""Wrap a long line into multiple lines with max_width characters.

Args:
line: The line to wrap
max_width: Maximum width per line (default 80)
indent: The indentation to preserve for the first line
continuation_indent: Extra indentation for continuation lines.
If None, uses indent + 4 spaces.

Returns:
List of wrapped lines
"""
if continuation_indent is None:
continuation_indent = indent + " "

# If line is already short enough, return as is
if len(line) <= max_width:
return [line]

# Don't wrap C++ preprocessor directives (macros)
stripped = line.lstrip()
if stripped.startswith("#"):
return [line]

# Don't wrap comment lines (license headers, etc.)
if (
stripped.startswith("//")
or stripped.startswith("/*")
or stripped.startswith("*")
or stripped.startswith("#")
):
return [line]

# Extract the leading indent
leading_spaces = line[: len(line) - len(line.lstrip())]

# Get the content without indent
content = line[len(leading_spaces) :]

# If it's still too short after considering indent, don't wrap
if len(leading_spaces) + len(content) <= max_width:
return [line]

# Find good break points (prefer breaking at spaces, commas, operators)
result = []
current = content
first_line = True

while len(leading_spaces) + len(current) > max_width:
# Calculate available width
if first_line:
available = max_width - len(leading_spaces)
else:
available = max_width - len(continuation_indent)

if available <= 0:
# Can't wrap reasonably, return original
return [line]

# Find the best break point
break_point = -1

# Look for break points in order of preference
search_text = current[:available]

# Try to break at common delimiters (working backwards)
for delimiter in [
", ",
" && ",
" || ",
" + ",
" - ",
" * ",
" / ",
" = ",
" ",
",",
]:
idx = search_text.rfind(delimiter)
if idx > 0:
break_point = idx + len(delimiter)
break

# If no good break point found, just break at max width
if break_point <= 0:
break_point = available

# Add the line segment
if first_line:
result.append(leading_spaces + current[:break_point].rstrip())
first_line = False
else:
result.append(continuation_indent + current[:break_point].rstrip())

# Continue with the rest
current = current[break_point:].lstrip()

# Add the remaining content
if current:
if first_line:
result.append(leading_spaces + current)
else:
result.append(continuation_indent + current)

return result if result else [line]

def wrap_lines(
self, lines: List[str], max_width: int = 80, preserve_blank: bool = True
) -> List[str]:
"""Wrap multiple lines, handling each line's indentation.

Args:
lines: List of lines to wrap
max_width: Maximum width per line
preserve_blank: If True, preserve blank lines as-is

Returns:
List of wrapped lines
"""
result = []
for line in lines:
if preserve_blank and not line.strip():
result.append(line)
else:
result.extend(self.wrap_line(line, max_width))
return result
5 changes: 4 additions & 1 deletion compiler/fory_compiler/generators/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,12 @@ def generate_header(self) -> GeneratedFile:
lines.append(f"#endif // {guard_name}")
lines.append("")

# Wrap long lines at 80 characters (careful with C++ macros)
wrapped_lines = self.wrap_lines(lines, max_width=80)

return GeneratedFile(
path=f"{self.get_header_name()}.h",
content="\n".join(lines),
content="\n".join(wrapped_lines),
)

def collect_message_includes(self, message: Message, includes: Set[str]):
Expand Down
59 changes: 52 additions & 7 deletions compiler/fory_compiler/generators/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,49 @@ def message_has_unions(self, message: Message) -> bool:
PrimitiveKind.ANY: "any",
}

def wrap_line(
Comment thread
lavisha4193 marked this conversation as resolved.
self,
line: str,
max_width: int = 80,
indent: str = "",
continuation_indent: str = None,
) -> List[str]:
"""Override base wrap_line to handle Go-specific syntax.

Go has specific constructs that should not be wrapped:
1. Function signatures - must not split between params and return type
2. Struct field tags - backtick strings must not be split
3. Single-line function bodies - must not split "{ return ... }"
"""
if continuation_indent is None:
continuation_indent = indent + " "

# If line is already short enough, return as is
if len(line) <= max_width:
return [line]

stripped = line.lstrip()

# Don't wrap comments
if (
stripped.startswith("//")
or stripped.startswith("/*")
or stripped.startswith("*")
):
return [line]

# Don't wrap lines with backticks (struct tags)
if "`" in line:
return [line]

# Don't wrap function definitions (including one-liners like "func Foo() Type { return ... }")
# Detect by checking if line starts with "func " and contains "{"
if stripped.startswith("func ") and "{" in line:
return [line]

# For all other cases, use base class wrapping
return super().wrap_line(line, max_width, indent, continuation_indent)

def generate(self) -> List[GeneratedFile]:
"""Generate Go files for the schema."""
files = []
Expand Down Expand Up @@ -449,9 +492,12 @@ def generate_file(self) -> GeneratedFile:
lines.extend(self.generate_fory_helpers())
lines.append("")

# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)

return GeneratedFile(
path=f"{self.get_file_name()}.go",
content="\n".join(lines),
content="\n".join(wrapped_lines),
)

def collect_message_imports(self, message: Message, imports: Set[str]):
Expand Down Expand Up @@ -583,15 +629,13 @@ def generate_union(
lines.append(f"\tcase {case_type}{case_name}:")
lines.append(f"\t\tv, ok := u.value.({case_type_name})")
lines.append("\t\tif !ok {")
lines.append(
f'\t\t\treturn fmt.Errorf("corrupted {type_name}: case={case_name} but invalid value")'
)
lines.append(f'\t\t\treturn fmt.Errorf("corrupted {type_name}: " +')
lines.append(f'\t\t\t\t"case={case_name} but invalid value")')
lines.append("\t\t}")
if case_type_name.startswith("*"):
lines.append("\t\tif v == nil {")
lines.append(
f'\t\t\treturn fmt.Errorf("corrupted {type_name}: case={case_name} but nil value")'
)
lines.append(f'\t\t\treturn fmt.Errorf("corrupted {type_name}: " +')
lines.append(f'\t\t\t\t"case={case_name} but nil value")')
lines.append("\t\t}")
lines.append(f"\t\tif visitor.{case_name} != nil {{")
lines.append(f"\t\t\treturn visitor.{case_name}(v)")
Expand Down Expand Up @@ -861,6 +905,7 @@ def generate_field(

if tags:
tag_str = ",".join(tags)
# Concatenate parts to ensure single line in output
lines.append(f'{field_name} {go_type} `fory:"{tag_str}"`')
else:
lines.append(f"{field_name} {go_type}")
Expand Down
26 changes: 19 additions & 7 deletions compiler/fory_compiler/generators/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,9 @@ def generate_enum_file(self, enum: Enum) -> GeneratedFile:
else:
path = f"{enum.name}.java"

return GeneratedFile(path=path, content="\n".join(lines))
# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)
return GeneratedFile(path=path, content="\n".join(wrapped_lines))

def generate_union_file(self, union: Union) -> GeneratedFile:
"""Generate a Java union class file."""
Expand Down Expand Up @@ -397,7 +399,9 @@ def generate_union_file(self, union: Union) -> GeneratedFile:
else:
path = f"{union.name}.java"

return GeneratedFile(path=path, content="\n".join(lines))
# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)
return GeneratedFile(path=path, content="\n".join(wrapped_lines))

def generate_message_file(self, message: Message) -> GeneratedFile:
"""Generate a Java class file for a message."""
Expand Down Expand Up @@ -486,7 +490,9 @@ def generate_message_file(self, message: Message) -> GeneratedFile:
else:
path = f"{message.name}.java"

return GeneratedFile(path=path, content="\n".join(lines))
# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)
return GeneratedFile(path=path, content="\n".join(wrapped_lines))

def generate_outer_class_file(self, outer_classname: str) -> GeneratedFile:
"""Generate a single Java file with all types as inner classes of an outer class.
Expand Down Expand Up @@ -559,7 +565,9 @@ def generate_outer_class_file(self, outer_classname: str) -> GeneratedFile:
else:
path = f"{outer_classname}.java"

return GeneratedFile(path=path, content="\n".join(lines))
# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)
return GeneratedFile(path=path, content="\n".join(wrapped_lines))

def collect_message_imports(self, message: Message, imports: Set[str]):
"""Collect imports for a message and all its nested types recursively."""
Expand Down Expand Up @@ -650,7 +658,8 @@ def generate_union_class(
lines.append(f"{ind} return {type_id_expr};")
lines.append(f"{ind} default:")
lines.append(
f'{ind} throw new IllegalStateException("Unknown {union.name} case id: " + caseId);'
f'{ind} throw new IllegalStateException("Unknown " + '
f'"{union.name} case id: " + caseId);'
)
lines.append(f"{ind} }}")
lines.append(f"{ind} }}")
Expand Down Expand Up @@ -686,7 +695,8 @@ def generate_union_class(
lines.append(f"{ind} return {case_enum}.{case_enum_name};")
lines.append(f"{ind} default:")
lines.append(
f'{ind} throw new IllegalStateException("Unknown {union.name} case id: " + index);'
f'{ind} throw new IllegalStateException("Unknown " + '
f'"{union.name} case id: " + index);'
)
lines.append(f"{ind} }}")
lines.append(f"{ind} }}")
Expand Down Expand Up @@ -1447,7 +1457,9 @@ def generate_registration_file(
else:
path = f"{class_name}.java"

return GeneratedFile(path=path, content="\n".join(lines))
# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)
return GeneratedFile(path=path, content="\n".join(wrapped_lines))

def generate_enum_registration(
self, lines: List[str], enum: Enum, parent_path: str
Expand Down
63 changes: 62 additions & 1 deletion compiler/fory_compiler/generators/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,64 @@ class PythonGenerator(BaseGenerator):
PrimitiveKind.ANY: "None",
}

def wrap_line(
Comment thread
lavisha4193 marked this conversation as resolved.
self,
line: str,
max_width: int = 80,
indent: str = "",
continuation_indent: str = None,
) -> List[str]:
"""Override base wrap_line to handle Python-specific syntax.

Python has specific constructs that should not be wrapped:
1. Assignment statements - must not split between variable and value
2. Conditional statements with logical operators (and, or, not)
3. Raise statements with string literals
"""
if continuation_indent is None:
continuation_indent = indent + " "

# If line is already short enough, return as is
if len(line) <= max_width:
return [line]

stripped = line.lstrip()

# Don't wrap comments
if stripped.startswith("#"):
return [line]

# Don't wrap raise statements (they often have long string literals)
if stripped.startswith("raise "):
return [line]

# Don't wrap assignment statements (lines containing " = " at the statement level)
# This prevents splitting like: _threadsafe_fory = pyfory.ThreadSafeFory(...)
# Only check for simple assignments (not comparisons or keyword args)
if (
" = " in line
and not line.strip().startswith("if ")
and not line.strip().startswith("elif ")
and not line.strip().startswith("while ")
):
# Check if this looks like a simple assignment (has = but not == or <= or >=)
# and the = comes before any opening parentheses
eq_pos = line.find(" = ")
paren_pos = line.find("(")
if eq_pos > 0 and (paren_pos < 0 or eq_pos < paren_pos):
# This is likely an assignment statement, don't wrap it
return [line]

# Don't wrap conditional statements (if/elif/while) with logical operators
# This prevents splitting like: if condition and not\n isinstance(...)
if stripped.startswith(("if ", "elif ", "while ")) and (
" and " in line or " or " in line
):
return [line]

# For all other cases, use base class wrapping
return super().wrap_line(line, max_width, indent, continuation_indent)

def safe_name(self, name: str) -> str:
"""Return a Python-safe identifier."""
if keyword.iskeyword(name):
Expand Down Expand Up @@ -323,9 +381,12 @@ def generate_module(self) -> GeneratedFile:
lines.extend(self.generate_fory_helpers())
lines.append("")

# Wrap long lines at 80 characters
wrapped_lines = self.wrap_lines(lines, max_width=80)

return GeneratedFile(
path=f"{self.get_module_name()}.py",
content="\n".join(lines),
content="\n".join(wrapped_lines),
)

def collect_message_imports(self, message: Message, imports: Set[str]):
Expand Down
Loading
Loading