Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
182 changes: 182 additions & 0 deletions compiler/fory_compiler/generators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,185 @@ 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

def format_long_line(
self,
prefix: str,
content: str,
suffix: str = "",
max_width: int = 80,
continuation_indent: str = " ",
) -> List[str]:
"""Format a long line with semantic understanding.

This is for semantic line wrapping where we know the structure.
Use this when generating code to avoid breaking in wrong places.

Args:
prefix: The prefix part (e.g., "public class ")
content: The main content that might be long
suffix: The suffix part (e.g., " {")
max_width: Maximum line width
continuation_indent: Indentation for continuation lines

Returns:
List of formatted lines

Example:
format_long_line("public class ", "VeryLongClassName", " {")
=> ["public class VeryLongClassName {"] # if fits
=> ["public class", " VeryLongClassName {"] # if too long
"""
full_line = prefix + content + suffix
if len(full_line) <= max_width:
return [full_line]

# If content alone with suffix fits on next line
if len(continuation_indent + content + suffix) <= max_width:
return [prefix.rstrip(), continuation_indent + content + suffix]

# Content is too long even on its own line
# Put each part on separate lines
lines = [prefix.rstrip()]
if len(continuation_indent + content) <= max_width:
lines.append(continuation_indent + content)
else:
# Content itself needs wrapping
lines.append(continuation_indent + content)
if suffix:
lines.append(suffix)
return lines
65 changes: 58 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(
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 @@ -521,7 +564,16 @@ def generate_union(
lines.append(")")
lines.append("")

lines.append(f"type {type_name} struct {{")
# Type declaration with semantic wrapping for Go
type_decl = f"type {type_name} struct {{"
if len(type_decl) > 80:
lines.extend(
self.format_long_line(
"type ", type_name, " struct {", continuation_indent="\t"
)
)
else:
lines.append(type_decl)
lines.append(f"\tcase_ {case_type}")
lines.append("\tvalue any")
lines.append("}")
Expand Down Expand Up @@ -583,15 +635,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 +911,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
33 changes: 25 additions & 8 deletions compiler/fory_compiler/generators/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,9 @@ def generate_message_file(self, message: Message) -> GeneratedFile:
lines.append(f"import {imp};")
lines.append("")

# Class declaration
lines.append(f"public class {message.name} {{")
# Class declaration with semantic line wrapping
class_lines = self.format_long_line("public class ", message.name, " {")
lines.extend(class_lines)

# Generate nested enums as static inner classes
for nested_enum in message.nested_enums:
Expand Down Expand Up @@ -669,7 +670,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 @@ -705,7 +707,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 @@ -1074,14 +1077,28 @@ def generate_getter_setter(self, field: Field) -> List[str]:
field_name = self.to_camel_case(field.name)
pascal_name = self.to_pascal_case(field.name)

# Getter
lines.append(f"public {java_type} get{pascal_name}() {{")
# Getter with semantic line wrapping
getter_sig = f"public {java_type} get{pascal_name}()"
if len(getter_sig) > 80:
getter_lines = self.format_long_line(
"public ", f"{java_type} get{pascal_name}()", " {"
)
else:
getter_lines = [f"{getter_sig} {{"]
lines.extend(getter_lines)
lines.append(f" return {field_name};")
lines.append("}")
lines.append("")

# Setter
lines.append(f"public void set{pascal_name}({java_type} {field_name}) {{")
# Setter with semantic line wrapping
setter_sig = f"public void set{pascal_name}({java_type} {field_name})"
if len(setter_sig) > 80:
setter_lines = self.format_long_line(
"public void ", f"set{pascal_name}({java_type} {field_name})", " {"
)
else:
setter_lines = [f"{setter_sig} {{"]
lines.extend(setter_lines)
lines.append(f" this.{field_name} = {field_name};")
lines.append("}")
lines.append("")
Expand Down
Loading
Loading