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
18 changes: 16 additions & 2 deletions tests/lit/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,19 @@
if filecheck_bin:
break
if not filecheck_bin:
raise FileNotFoundError("FileCheck binary not found in PATH")
config.substitutions.append(("%filecheck", f"{filecheck_bin}"))
print("WARNING: FileCheck binary not found in PATH. Substituting with a command to save output to /tmp/fc_output_*.txt and print to stdout.")
# Construct a command that uses the test file name (%s) to create a unique output file.
# lit will replace %s with the current test file path. We need just the basename for the output file.
# Using tee to both save to file and pass to stdout (which lit captures).
# The actual FileCheck options will be passed to this command string by lit,
# so the receiving command ('cat' in this case, after tee) should ideally ignore them.
# Using "cat -" to ensure it reads from stdin piped from tee.
filecheck_replacement = (
"sh -c 'mkdir -p /tmp/xlang_lit_outputs && "
"OUTPUT_FILE=/tmp/xlang_lit_outputs/$(basename %s .xl).actual.txt && "
"echo \"--- Output for %s --- \" > $OUTPUT_FILE && "
"tee -a $OUTPUT_FILE | cat -'"
)
config.substitutions.append(("%filecheck", filecheck_replacement))
else:
config.substitutions.append(("%filecheck", f"{filecheck_bin}"))
60 changes: 60 additions & 0 deletions tests/lit/structs.xl
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,63 @@ func main() {
// CHECK: false
print(s.b);
}

//---------------------------------------

struct Point {
x: i32,
y: i32 = 10,
}

func main() {
var p1: Point = Point{x: 5, y: 20};
// CHECK: 5
print(p1.x);
// CHECK: 20
print(p1.y);

var p2: Point = Point{x: 7};
// CHECK: 7
print(p2.x);
// CHECK: 10
print(p2.y);

var p3: Point = Point{y: 30, x: 1}; // test different order
// CHECK: 1
print(p3.x);
// CHECK: 30
print(p3.y);

const five: i32 = 5;
var p4: Point = Point{x: five * 2, y: p1.y - p2.y};
// CHECK: 10
print(p4.x);
// CHECK: 10
print(p4.y);
}

//---------------------------------------
// Test with trailing comma
struct A { a: i32, }
func main() {
var a_val: A = A{a: 1,};
// CHECK: 1
print(a_val.a);
}

//---------------------------------------
// Test empty initializer (all defaults)
struct B { b: string = "default_b", }
func main() {
var b_val: B = B{};
// CHECK: default_b
print(b_val.b);
}
//---------------------------------------
// Test empty initializer with trailing comma
struct C { c: bool = true, }
func main() {
var c_val: C = C{,};
// CHECK: true
print(c_val.c);
}
5 changes: 4 additions & 1 deletion xlang/grammar.lark
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ CHAR_LITERAL: /'([^'\\]|\\[tnr'\\0])'/
array_access: "[" not_expr "]"
var_access: IDENTIFIER array_access? ("." (var_access|function_call))?

struct_initializer_member: IDENTIFIER ":" not_expr
struct_initializer: IDENTIFIER "{" (struct_initializer_member ("," struct_initializer_member)*)? ","? "}"

code_block: "{" statement* "}"

?statement: loop | if_statement | function_call ";" | variable_def | const_def | variable_dec | variable_assign | var_access ";" | control ";"
Expand All @@ -51,7 +54,7 @@ string_literal: STRING_LITERAL
char_literal: CHAR_LITERAL
float_constant: FLOAT
!boolean_literal: ("false" | "true")
?primary_expression: var_access | function_call | integer_constant | float_constant | string_literal | char_literal | boolean_literal | "(" not_expr ")"
?primary_expression: struct_initializer | var_access | function_call | integer_constant | float_constant | string_literal | char_literal | boolean_literal | "(" not_expr ")"

!?mul_div_expr: primary_expression | (mul_div_expr "*" primary_expression) | (mul_div_expr "/" primary_expression) | (mul_div_expr "%" primary_expression)
!?add_sub_expr: mul_div_expr | (add_sub_expr "+" mul_div_expr) | (add_sub_expr "-" mul_div_expr)
Expand Down
42 changes: 42 additions & 0 deletions xlang/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
UnaryOperation,
Constant,
BuiltinFunction,
StructInitializer,
)
from xlang.xl_builtins import (
BUILTIN_FUNCTIONS,
Expand Down Expand Up @@ -239,6 +240,47 @@ def expression(self, expression: BaseExpression):
return self.lookup_variable(expression)
elif isinstance(expression, Constant):
return self.value_from_constant(expression)
elif isinstance(expression, StructInitializer):
if expression.name not in self.global_scope.structs:
# This should be caught by validation pass
raise InternalCompilerError(
f"Unknown struct type: {expression.name}", expression.context
)
struct_def = self.global_scope.structs[expression.name]
struct_data = {}

# Initialize with provided values
initialized_members = set()
# Create a quick lookup for member definitions
struct_members_def_map = {
member.name: member for member in struct_def.members
}
for member_init in expression.members:
if member_init.name not in struct_members_def_map:
# This should ideally be caught by the validation pass
raise ContextException(
f"Struct '{expression.name}' has no member '{member_init.name}'",
member_init.context,
)
struct_data[member_init.name] = self.expression(member_init.value)
initialized_members.add(member_init.name)

# Initialize remaining members with defaults or type defaults
for member_def in struct_def.members:
if member_def.name not in initialized_members:
if member_def.default_value is not None:
struct_data[member_def.name] = self.expression(
member_def.default_value
)
else:
struct_data[member_def.name] = self.default_variable_value(
member_def.param_type
)
return Value(
type=ValueType.STRUCT,
value=struct_data,
type_name=expression.name,
)
elif isinstance(expression, MathOperation):
operand1_value = self.expression(expression.operand1)
operand2_value = self.expression(expression.operand2)
Expand Down
45 changes: 45 additions & 0 deletions xlang/transformer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import lark # Added import
from lark import Transformer, v_args

from xlang.exceptions import (
Expand All @@ -10,6 +11,8 @@
from xlang.xl_ast import (
ArrayAccess,
Break,
StructInitializer,
StructInitializerMember,
CompareOperation,
Constant,
ConstantType,
Expand Down Expand Up @@ -439,3 +442,45 @@ def var_access(self, variable, *args):
method_call=method_call,
variable_access=variable_access,
)

@v_args(inline=True)
def struct_initializer_member(self, identifier, value):
return StructInitializerMember(
name=identifier.value,
value=value,
context=ParseContext.from_token(identifier),
)

def struct_initializer(self, children):
name_token = children[0]
members_ast_nodes = []

# Iterate over all children after the name_token
# Children could be StructInitializerMember nodes or Token(',')
for child_node in children[1:]:
if isinstance(child_node, StructInitializerMember):
members_ast_nodes.append(child_node)
elif isinstance(child_node, list):
# This case handles if Lark groups (A ("," A)*) into a list.
# Should not happen with current grammar for the members part,
# but good to be defensive or aware.
for item in child_node:
if isinstance(item, StructInitializerMember):
members_ast_nodes.append(item)

# Check for duplicate members
member_names = set()
for member_node in members_ast_nodes:
if member_node.name in member_names:
raise ContextException(
f"Duplicate member '{member_node.name}' in struct initializer",
member_node.context,
)
member_names.add(member_node.name)

return StructInitializer(
type=None, # Type will be filled in by validation pass
name=name_token.value,
members=members_ast_nodes,
context=ParseContext.from_token(name_token),
)
58 changes: 58 additions & 0 deletions xlang/validation_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
Return,
Continue,
Break,
StructInitializer,
StructInitializerMember,
)
from xlang.xl_builtins import (
get_builtin_functions,
Expand Down Expand Up @@ -347,6 +349,62 @@ def expression(self, expression: BaseExpression):
raise InternalCompilerError(
f"Unknown unary operator: {expression.operator}"
)
elif isinstance(expression, StructInitializer):
if expression.name not in self.global_scope.structs:
raise TypeMismatchException(
f"Unknown struct type: {expression.name}", expression.context
)
struct_def = self.global_scope.structs[expression.name]
expression.type = VariableType(
variable_type=VariableTypeEnum.STRUCT, type_name=expression.name
)

# Create a map of member definitions for easy lookup
struct_members_def_map = {
member.name: member for member in struct_def.members
}

initialized_member_names = set()
for member_init in expression.members:
if member_init.name not in struct_members_def_map:
raise TypeMismatchException(
f"Struct '{expression.name}' has no member '{member_init.name}'",
member_init.context,
)
member_def = struct_members_def_map[member_init.name]
member_init_expr_type = self.expression(member_init.value)

if not is_type_compatible(
member_def.param_type, member_init_expr_type
):
raise TypeMismatchException(
f"Type mismatch for member '{member_init.name}' in struct "
f"'{expression.name}'. Expected {member_def.param_type}, "
f"got {member_init_expr_type}",
member_init.context,
)
initialized_member_names.add(member_init.name)

# Check if all members that don't have a default value are initialized
debug_initialized_names_str = ", ".join(sorted(list(initialized_member_names)))
debug_provided_members_str = ", ".join(sorted([f"{m.name}: {type(m.value).__name__}" for m in expression.members]))
debug_info = (
f"DEBUG_INFO Struct: {expression.name}, "
f"ContextLine: {expression.context.line if expression.context else 'N/A'}, "
f"ProvidedInits: {{{debug_provided_members_str}}}, "
f"InitializedNamesSet: {{{debug_initialized_names_str}}}. "
)

for member_def_loop_var in struct_def.members:
if (
member_def_loop_var.default_value is None
and member_def_loop_var.name not in initialized_member_names
):
raise TypeMismatchException(
f"{debug_info}Member '{member_def_loop_var.name}' of struct '{expression.name}' "
"must be initialized (it has no default value and was not provided).",
expression.context,
)
else:
raise InternalCompilerError("Unknown expression")
return expression.type
Expand Down
11 changes: 11 additions & 0 deletions xlang/xl_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ class Constant(BaseExpression):
value: Any


class StructInitializerMember(BaseModel):
name: str
value: BaseExpression
context: ParseContext


class StructInitializer(BaseExpression):
name: str
members: List[StructInitializerMember]


class MathOperation(BaseExpression):
operand1: BaseExpression
operand2: BaseExpression
Expand Down