Skip to content

Commit 5b6dd30

Browse files
committed
feat: Add instance method compilation support
- Compile instance methods with self as first parameter - Instance methods named as ClassName$methodName (matching call convention) - Constructor special handling: no self param, returns Pointer<ClassName> - Properly distinguish extern vs non-extern for both TNew and FInstance: - Extern: $ClassName$method (runtime provided) - Non-extern: ClassName$method (compiled) - Add CustomClassTest demonstrating user-defined class compilation - Add haxe.exceptions exclusion to build configs Instance method bodies still reference fields as variables (e.g., "value") rather than field access (e.g., "self.value") - field access handling TBD.
1 parent bc36900 commit 5b6dd30

4 files changed

Lines changed: 124 additions & 65 deletions

File tree

reflaxe.zyntax/src/zyntax/ZyntaxCompiler.hx

Lines changed: 90 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -39,58 +39,86 @@ class ZyntaxCompiler extends GenericCompiler<AST.Module, AST.Enum, AST.Expr> {
3939
}
4040
}
4141

42-
// For now, only process static functions as top-level functions
42+
// Build class type for 'self' parameter in instance methods
43+
var classInstanceType = AST.ZType.Pointer(AST.ZType.Primitive(classType.name));
44+
4345
var functions = [];
4446

4547
for (func in funcFields) {
48+
var params: Array<AST.Param> = [];
49+
var funcName: String;
50+
var returnType: AST.ZType;
51+
var isConstructor = func.field.name == "new";
52+
4653
if (func.isStatic) {
47-
var params = func.args.map(arg -> {
54+
// Static method: use original name
55+
funcName = func.field.name;
56+
returnType = convertType(func.ret);
57+
} else if (isConstructor) {
58+
// Constructor: ClassName$new() -> returns Pointer<ClassName>
59+
// Constructors don't take self - they create and return a new instance
60+
funcName = classType.name + "$new";
61+
returnType = classInstanceType; // Returns Pointer<ClassName>
62+
} else {
63+
// Instance method: prefix with ClassName$ and add 'self' parameter
64+
funcName = classType.name + "$" + func.field.name;
65+
returnType = convertType(func.ret);
66+
67+
// Add 'self' as first parameter
68+
params.push({
69+
name: "self",
70+
ty: classInstanceType,
71+
pos: func.field.pos
72+
});
73+
}
74+
75+
// Add regular parameters
76+
for (arg in func.args) {
77+
params.push({
4878
name: arg.getName(),
4979
ty: convertType(arg.type),
5080
pos: func.field.pos
5181
});
82+
}
83+
var body = func.expr != null ? {
84+
expr: compileExpressionOrError(func.expr),
85+
ty: convertType(func.expr.t),
86+
pos: func.expr.pos
87+
} : null;
88+
89+
// Check if function is external
90+
var isExternal = isExternClass || func.expr == null;
91+
var linkName: Null<String> = null;
92+
93+
// For extern class functions, use the function name directly
94+
// unless overridden by @:native on the function itself
95+
if (isExternal) {
96+
linkName = func.field.name;
97+
}
5298

53-
var returnType = convertType(func.ret);
54-
var body = func.expr != null ? {
55-
expr: compileExpressionOrError(func.expr),
56-
ty: convertType(func.expr.t),
57-
pos: func.expr.pos
58-
} : null;
59-
60-
// Check if function is external
61-
var isExternal = isExternClass || func.expr == null;
62-
var linkName: Null<String> = null;
63-
64-
// For extern class functions, use the function name directly
65-
// unless overridden by @:native on the function itself
66-
if (isExternal) {
67-
linkName = func.field.name;
68-
}
69-
70-
// Check for @:native metadata on the function to override the link name
71-
if (func.field.meta != null) {
72-
for (meta in func.field.meta.get()) {
73-
if (meta.name == ":native" && meta.params != null && meta.params.length > 0) {
74-
switch (meta.params[0].expr) {
75-
case EConst(CString(s, _)):
76-
linkName = s;
77-
isExternal = true;
78-
default:
79-
}
99+
// Check for @:native metadata on the function to override the link name
100+
if (func.field.meta != null) {
101+
for (meta in func.field.meta.get()) {
102+
if (meta.name == ":native" && meta.params != null && meta.params.length > 0) {
103+
switch (meta.params[0].expr) {
104+
case EConst(CString(s, _)):
105+
linkName = s;
106+
isExternal = true;
107+
default:
80108
}
81109
}
82110
}
83-
84-
functions.push({
85-
name: func.field.name,
86-
params: params,
87-
return_type: returnType,
88-
body: body,
89-
is_external: isExternal,
90-
link_name: linkName,
91-
pos: func.field.pos
92-
});
93111
}
112+
113+
functions.push({
114+
name: funcName,
115+
params: params,
116+
return_type: returnType,
117+
body: body,
118+
is_external: isExternal,
119+
link_name: linkName,
120+
pos: func.field.pos
121+
});
94122
}
95123

96124
if (functions.length == 0) return null;
@@ -195,25 +223,25 @@ class ZyntaxCompiler extends GenericCompiler<AST.Module, AST.Enum, AST.Expr> {
195223
// Check if method is extern (no implementation)
196224
var isExtern = objTypeInfo.isExtern || field.expr() == null;
197225

198-
if (isExtern) {
199-
// Extern instance method: map.set(k,v) -> $StringMap$set(map, k, v)
200-
var stdlibFunc = "$" + objTypeInfo.name + "$" + methodName;
201-
var funcType = buildInstanceMethodType(obj.t, callee.t);
202-
var stdlibCallee: AST.ExprWithPos = {
203-
expr: AST.Expr.Variable(stdlibFunc),
204-
ty: funcType,
205-
pos: callee.pos
206-
};
207-
var objExpr = wrapExpr(obj);
208-
var argExprs = args.map(a -> wrapExpr(a));
209-
AST.Expr.Call(stdlibCallee, [objExpr].concat(argExprs));
210-
} else {
211-
// Non-extern instance method - request class compilation
226+
// Request class compilation for non-extern classes
227+
if (!isExtern) {
212228
addTypeForCompilation(TInst(objTypeInfo.clsRef, objTypeInfo.params));
213-
var calleeExpr = wrapExpr(callee);
214-
var argExprs = args.map(a -> wrapExpr(a));
215-
AST.Expr.Call(calleeExpr, argExprs);
216229
}
230+
231+
// Instance method call: obj.method(args) -> ClassName$method(obj, args)
232+
// Use $ prefix for extern (runtime), no prefix for compiled
233+
var funcName = isExtern
234+
? ("$" + objTypeInfo.name + "$" + methodName)
235+
: (objTypeInfo.name + "$" + methodName);
236+
var funcType = buildInstanceMethodType(obj.t, callee.t);
237+
var funcCallee: AST.ExprWithPos = {
238+
expr: AST.Expr.Variable(funcName),
239+
ty: funcType,
240+
pos: callee.pos
241+
};
242+
var objExpr = wrapExpr(obj);
243+
var argExprs = args.map(a -> wrapExpr(a));
244+
AST.Expr.Call(funcCallee, [objExpr].concat(argExprs));
217245
} else {
218246
// Fallback to regular call
219247
var calleeExpr = wrapExpr(callee);
@@ -300,24 +328,21 @@ class ZyntaxCompiler extends GenericCompiler<AST.Module, AST.Enum, AST.Expr> {
300328
compileExpressionImpl(e, topLevel);
301329

302330
case TNew(clsRef, params, args):
303-
// Object instantiation: new StringMap<Int>() -> $StringMap$new()
331+
// Object instantiation: new Counter() -> Counter$new()
304332
var cls = clsRef.get();
305333
var className = cls.name;
306334

307335
// Check if this class is extern (no Haxe implementation)
308-
// NOTE: Even non-extern classes currently need runtime support because
309-
// we don't yet compile instance methods - only static methods are compiled.
310-
// Once instance method compilation is added, we can check cls.isExtern
311-
// and only use runtime calls for actual extern classes.
312-
var isExtern = true; // For now, treat all class instantiations as extern
336+
var isExtern = cls.isExtern;
313337

338+
// Request the class to be compiled (for non-extern classes)
314339
if (!isExtern) {
315-
// Non-extern class - request compilation and use regular call
316340
addTypeForCompilation(TInst(clsRef, params));
317341
}
318342

319-
// Generate constructor call: $ClassName$new(args...)
320-
var constructorFunc = "$" + className + "$new";
343+
// Generate constructor call: ClassName$new(args...) for non-extern
344+
// or $ClassName$new(args...) for extern (runtime provided)
345+
var constructorFunc = isExtern ? ("$" + className + "$new") : (className + "$new");
321346
// Build function type: (arg_types...) -> instance_type
322347
var argTypes = args.map(a -> a.t);
323348
var constructorType = buildConstructorType(expr.t, argTypes);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Test with a custom class that has actual Haxe implementation
2+
class Counter {
3+
public var value:Int;
4+
5+
public function new() {
6+
value = 0;
7+
}
8+
9+
public function increment():Void {
10+
value = value + 1;
11+
}
12+
13+
public function getValue():Int {
14+
return value;
15+
}
16+
}
17+
18+
class CustomClassTest {
19+
public static function main():Void {
20+
// Test custom class
21+
var counter = new Counter();
22+
counter.increment();
23+
counter.increment();
24+
counter.increment();
25+
var result = counter.getValue();
26+
trace("Counter value: " + result);
27+
}
28+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-lib reflaxe.zyntax
2+
-main CustomClassTest
3+
-D zyntax-output=output
4+
--macro exclude('haxe.Log')
5+
--macro exclude('haxe.exceptions')

reflaxe.zyntax/test/build_for_loop.hxml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
-main ForLoopTest
33
-D zyntax-output=output
44
--macro exclude('haxe.Log')
5+
--macro exclude('haxe.exceptions')

0 commit comments

Comments
 (0)