Skip to content

Commit 197e735

Browse files
committed
Add support for external function declaration
1 parent b931814 commit 197e735

File tree

7 files changed

+77
-15
lines changed

7 files changed

+77
-15
lines changed

src/loreline/AstPrinter.hx

+3
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ class AstPrinter {
292292
add(func.name);
293293
add('"');
294294
}
295+
if (func.external) {
296+
add(' external=true');
297+
}
295298
add(" args=[" + func.args.join(', ') + "]");
296299
addLineBreak();
297300
indentLevel++;

src/loreline/Interpreter.hx

+16-3
Original file line numberDiff line numberDiff line change
@@ -1736,10 +1736,10 @@ typedef InterpreterOptions = {
17361736
function initializeTopLevelFunction(func:NFunctionDecl) {
17371737

17381738
#if hscript
1739-
if (func.name != null) {
1739+
if (func.name != null && (!func.external || !topLevelFunctions.exists(func.name))) {
17401740
final codeToHscript = new CodeToHscript();
17411741
try {
1742-
final expr = codeToHscript.process(func.code);
1742+
final expr = codeToHscript.process(func.code + (func.external ? " {}" : ""));
17431743
#if loreline_debug_functions
17441744
final offsets = @:privateAccess codeToHscript.posOffsets;
17451745
trace('\n'+func.code);
@@ -3043,7 +3043,7 @@ typedef InterpreterOptions = {
30433043

30443044
// If we get here, the call target was invalid
30453045
throw new RuntimeError(
3046-
'Invalid call target: ${Type.getClassName(Type.getClass(call.target))}',
3046+
'Invalid call target: ${printLoreline(call.target) ?? Type.getClassName(Type.getClass(call.target))}',
30473047
call.pos
30483048
);
30493049

@@ -3534,6 +3534,19 @@ typedef InterpreterOptions = {
35343534

35353535
}
35363536

3537+
function printLoreline(node:Node):String {
3538+
3539+
try {
3540+
final printer = new Printer();
3541+
printer.enableComments = false;
3542+
return printer.print(node).trim();
3543+
}
3544+
catch (e:Any) {
3545+
return null;
3546+
}
3547+
3548+
}
3549+
35373550
#if loreline_debug_interpreter
35383551
public static dynamic function debug(message:String, ?pos:haxe.PosInfos) {
35393552
trace(message);

src/loreline/Lexer.hx

+19-5
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ enum TokenType {
113113
KwNew;
114114

115115
/** Function code */
116-
Function(name:Null<String>, args:Array<String>, code:String);
116+
Function(name:Null<String>, args:Array<String>, code:String, external:Bool);
117117

118118
/** String literal with optional attachments */
119119
LString(quotes:Quotes, s:String, ?attachments:Array<LStringAttachment>);
@@ -806,7 +806,7 @@ class Token {
806806
if (c == " ".code) {
807807
spaces++;
808808
} else if (c == "\t".code) {
809-
spaces += 4; // Treat each tab as 4 spaces
809+
spaces++;
810810
} else {
811811
break;
812812
}
@@ -3103,6 +3103,9 @@ class Token {
31033103
function readFunction(start:Position):Token {
31043104
skipWhitespaceAndComments();
31053105

3106+
// Evaluate min indentation
3107+
final minIndent = start.column; // Column is 1-based, so column 0 will mean min indent 1
3108+
31063109
// Read function name if present
31073110
var name:Null<String> = null;
31083111
if (isIdentifierStart(input.uCharCodeAt(pos))) {
@@ -3250,14 +3253,20 @@ class Token {
32503253
while (pos < length) {
32513254
final c = input.uCharCodeAt(pos);
32523255
if (c == " ".code) indent++;
3253-
else if (c == "\t".code) indent += 4; // Count tab as 4 spaces
3256+
else if (c == "\t".code) indent++;
32543257
else break;
32553258
advance();
32563259
}
32573260

32583261
// If this is the first line, record the indent level
32593262
if (functionIndentLevel == -1 && pos < length && input.uCharCodeAt(pos) != "\n".code && input.uCharCodeAt(pos) != "\r".code) {
32603263
functionIndentLevel = indent;
3264+
3265+
// Check this is indented enough
3266+
if (functionIndentLevel < minIndent) {
3267+
pos = indentStart;
3268+
break;
3269+
}
32613270
}
32623271
// Check if we are done (dedent or empty line at lower indentation)
32633272
else if (functionIndentLevel != -1 && indent < functionIndentLevel &&
@@ -3297,10 +3306,15 @@ class Token {
32973306

32983307
// Extract the function code from the original input
32993308
final bodyEnd = pos;
3300-
final code = input.uSubstr(start.offset, bodyEnd - start.offset).rtrim() + "\n";
3309+
var code = input.uSubstr(start.offset, bodyEnd - start.offset).rtrim();
3310+
3311+
final external = (code.uIndexOf("\n") == -1);
3312+
if (!external) {
3313+
code = code + "\n";
3314+
}
33013315

33023316
// Create token with the function code
3303-
final token = makeToken(Function(name, args, code), start);
3317+
final token = makeToken(Function(name, args, code, external), start);
33043318
token.pos.length = code.uLength();
33053319
return token;
33063320
}

src/loreline/Node.hx

+9-1
Original file line numberDiff line numberDiff line change
@@ -1605,21 +1605,28 @@ class NFunctionDecl extends NExpr {
16051605
*/
16061606
public var code:String;
16071607

1608+
/**
1609+
* Whether this is an external function (so this is only a declaration without implementation)
1610+
*/
1611+
public var external:Bool;
1612+
16081613

16091614
/**
16101615
* Creates a new function node.
16111616
* @param pos Position in source where this function appears
16121617
* @param name The function name (if any)
16131618
* @param args Argument names
16141619
* @param code The actual code of the function (including the signature)
1620+
* @param external Whether this is an external function (so this is only a declaration without implementation)
16151621
* @param leadingComments Optional comments before the function
16161622
* @param trailingComments Optional comments after the function
16171623
*/
1618-
public function new(id:NodeId, pos:Position, name:Null<String>, args:Array<String>, code:String, ?leadingComments:Array<Comment>, ?trailingComments:Array<Comment>) {
1624+
public function new(id:NodeId, pos:Position, name:Null<String>, args:Array<String>, code:String, external:Bool, ?leadingComments:Array<Comment>, ?trailingComments:Array<Comment>) {
16191625
super(id, pos, leadingComments, trailingComments);
16201626
this.name = name;
16211627
this.args = args;
16221628
this.code = code;
1629+
this.external = external;
16231630
}
16241631

16251632
override function type():String {
@@ -1637,6 +1644,7 @@ class NFunctionDecl extends NExpr {
16371644
}
16381645
json.args = [].concat(args);
16391646
json.code = code;
1647+
json.external = external;
16401648
return json;
16411649
}
16421650
}

src/loreline/Parser.hx

+3-3
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ class ParserContext {
396396
case LString(_, _, _): ensureInBeat(parseTextStatement());
397397
case Identifier(_) if (peek().type == Colon): ensureInBeat(parseDialogueStatement());
398398
case Identifier(_) | LNumber(_) | LBoolean(_) |
399-
LNull | LParen | LBracket | LBrace | OpMinus | OpNot: parseExpressionStatement();
399+
LNull | LParen | LBracket | LBrace | OpMinus | OpNot: ensureInBeat(parseExpressionStatement());
400400
case KwChoice: ensureInBeat(parseChoiceStatement());
401401
case KwIf: ensureInBeat(parseIfStatement());
402402
case Arrow: ensureInBeat(parseTransition());
@@ -1039,9 +1039,9 @@ class ParserContext {
10391039
final startPos = currentPos();
10401040

10411041
return switch (tokens[current].type) {
1042-
case Function(name, args, body):
1042+
case Function(name, args, code, external):
10431043
advance();
1044-
attachComments(new NFunctionDecl(nextNodeId(BLOCK), startPos, name, [].concat(args), body));
1044+
attachComments(new NFunctionDecl(nextNodeId(BLOCK), startPos, name, [].concat(args), code, external));
10451045

10461046
case _:
10471047
throw new ParseError("Unexpected token in expression", currentPos());

src/loreline/lsp/Server.hx

+7-3
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,11 @@ class Server {
415415

416416
// Check for errors in functions
417417
for (func in lens.getNodesOfType(NFunctionDecl, false)) {
418-
final funcHscript = lens.getFuncHscript(func);
419-
if (funcHscript.error != null) {
420-
addDiagnostic(uri, funcHscript.error.pos, funcHscript.error.message, DiagnosticSeverity.Error);
418+
if (!func.external) {
419+
final funcHscript = lens.getFuncHscript(func);
420+
if (funcHscript.error != null) {
421+
addDiagnostic(uri, funcHscript.error.pos, funcHscript.error.message, DiagnosticSeverity.Error);
422+
}
421423
}
422424
}
423425

@@ -455,6 +457,8 @@ class Server {
455457
}
456458
}
457459

460+
// TODO check references to beat calls and function calls
461+
458462
// Add semantic validation
459463
validateDocument(uri, ast);
460464
}

test/Functions-External.lor

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
function playExplosion()
3+
4+
sarah: What's this green diamond? Wait, let me touch it...
5+
james: Nooo don't touch it!
6+
7+
playExplosion()
8+
9+
james: Sarah? Sarah!!
10+
11+
/*
12+
<test>
13+
- expected: |
14+
sarah: What's this green diamond? Wait, let me touch it...
15+
16+
james: Nooo don't touch it!
17+
18+
james: Sarah? Sarah!!
19+
</test>
20+
*/

0 commit comments

Comments
 (0)