loreline 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jérémy Faivre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # Loreline
2
+
3
+ Loreline is a modern and open-source scripting language for writing interactive fictions.
4
+
5
+ https://loreline.app
6
+
7
+ ## Development
8
+
9
+ ### Prerequisites
10
+
11
+ Building Loreline from source requires [Node.js](https://nodejs.org/) (v18+).
12
+ The resulting outputs (JS library, C# library, native CLI) have no Node.js dependency.
13
+
14
+ ```sh
15
+ npm install # Install Node.js dependencies (esbuild, tsx, etc.)
16
+ ```
17
+
18
+ ### Building
19
+
20
+ ```sh
21
+ node ./setup --js # Build JavaScript library (js/loreline.js)
22
+ node ./setup --cs # Export C# source files (cs/Loreline/)
23
+ node ./setup --cs --cs-dll # Export C# + build Loreline.dll
24
+ node ./setup --cpp # Build native CLI (loreline / loreline.exe)
25
+ ```
26
+
27
+ ### Setting up samples
28
+
29
+ ```sh
30
+ node ./setup --sample # Set up all sample projects
31
+ node ./setup --sample web # Set up loreline-web only
32
+ node ./setup --sample unity # Set up loreline-unity only
33
+ node ./setup --sample cpp # Set up loreline-cpp only
34
+ ```
35
+
36
+ This copies the built runtime and story files into the sample directories.
37
+ For loreline-web, run `--js` first. For loreline-unity, run `--cs` first.
38
+ For loreline-cpp, run `--cpp` first.
39
+
40
+ ### Testing
41
+
42
+ ```sh
43
+ node run test ./test # Run Neko tests only
44
+ node ./setup --test # Run all test suites (Neko + C# + C# AOT + JS)
45
+ ```
46
+
47
+ ## License
48
+
49
+ MIT License
50
+
51
+ Copyright (c) 2025-2026 Jérémy Faivre
52
+
53
+ Permission is hereby granted, free of charge, to any person obtaining a copy
54
+ of this software and associated documentation files (the "Software"), to deal
55
+ in the Software without restriction, including without limitation the rights
56
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
57
+ copies of the Software, and to permit persons to whom the Software is
58
+ furnished to do so, subject to the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be included in all
61
+ copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
64
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
65
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
66
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
67
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
68
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
69
+ SOFTWARE.
@@ -0,0 +1,459 @@
1
+
2
+ /**
3
+ * Handler function for loading file imports
4
+ * @param path The path of the file to load
5
+ * @param callback Function to call with the loaded file content
6
+ */
7
+ export type ImportsFileHandler = (path: string, callback: (data: string) => void) => void;
8
+
9
+ /**
10
+ * Handler function for import errors
11
+ * @param error The error that occurred during import
12
+ */
13
+ export type ImportsErrorHandler = (error: Error) => void;
14
+
15
+ /**
16
+ * Tokens type
17
+ */
18
+ export type Tokens = Array<any>;
19
+
20
+ /**
21
+ * Opaque type for saved game data.
22
+ */
23
+ export type SaveData = any;
24
+
25
+ /**
26
+ * Opaque type for a translations map.
27
+ * Obtained from `Loreline.extractTranslations()` and passed to `InterpreterOptions.translations`.
28
+ */
29
+ export type Translations = any;
30
+
31
+ /**
32
+ * Represents an error in the Loreline system.
33
+ */
34
+ export interface Error {
35
+ /**
36
+ * The error message describing what went wrong.
37
+ */
38
+ message: string;
39
+
40
+ /**
41
+ * The position in the source code where the error occurred.
42
+ */
43
+ pos: Position;
44
+
45
+ /**
46
+ * The call stack of this error
47
+ */
48
+ stack: Array<any>;
49
+
50
+ /**
51
+ * Converts the error to a human-readable string.
52
+ * @return Formatted error message with position
53
+ */
54
+ toString(): string;
55
+ }
56
+
57
+ /**
58
+ * Represents a position within source code, tracking line number, column, and offset information.
59
+ * Used throughout the compiler to pinpoint locations of tokens, nodes, and error messages.
60
+ */
61
+ export interface Position {
62
+ /**
63
+ * The line number in the source code, starting from 1.
64
+ */
65
+ line: number;
66
+
67
+ /**
68
+ * The column number in the source code, starting from 1.
69
+ * Represents the character position within the current line.
70
+ */
71
+ column: number;
72
+
73
+ /**
74
+ * The absolute character offset from the start of the source code.
75
+ * Used for precise positioning and span calculations.
76
+ */
77
+ offset: number;
78
+
79
+ /**
80
+ * The length of the source text span this position represents.
81
+ * A value of 0 indicates a point position rather than a span.
82
+ */
83
+ length: number;
84
+
85
+ /**
86
+ * Converts the position to a human-readable string.
87
+ * @return Formatted position string
88
+ */
89
+ toString(): string;
90
+ }
91
+
92
+ /**
93
+ * Represents a choice option presented to the user.
94
+ */
95
+ export interface ChoiceOption {
96
+ /**
97
+ * The text of the choice option.
98
+ */
99
+ text: string;
100
+
101
+ /**
102
+ * Any tags associated with the choice text.
103
+ */
104
+ tags: Array<TextTag>;
105
+
106
+ /**
107
+ * Whether this choice option is currently enabled.
108
+ */
109
+ enabled: boolean;
110
+ }
111
+
112
+ /**
113
+ * Represents a tag in text content, which can be used for styling or other purposes.
114
+ */
115
+ export interface TextTag {
116
+ /**
117
+ * Whether this is a closing tag.
118
+ */
119
+ closing: boolean;
120
+
121
+ /**
122
+ * The value or name of the tag.
123
+ */
124
+ value: string;
125
+
126
+ /**
127
+ * The offset in the text where this tag appears.
128
+ */
129
+ offset: number;
130
+ }
131
+
132
+ /**
133
+ * Handler type for text output with callback.
134
+ * This is called when the script needs to display text to the user.
135
+ *
136
+ * @param interpreter The interpreter instance
137
+ * @param character The character speaking (null for narrator text)
138
+ * @param text The text content to display
139
+ * @param tags Any tags in the text
140
+ * @param callback Function to call when the text has been displayed
141
+ */
142
+ export type DialogueHandler = (interpreter: Interpreter, character: string | null, text: string, tags: Array<TextTag>, callback: () => void) => void;
143
+
144
+ /**
145
+ * Handler type for choice presentation with callback.
146
+ * This is called when the script needs to present choices to the user.
147
+ *
148
+ * @param interpreter The interpreter instance
149
+ * @param options The available choice options
150
+ * @param callback Function to call with the index of the selected choice
151
+ */
152
+ export type ChoiceHandler = (interpreter: Interpreter, options: Array<ChoiceOption>, callback: (index: number) => void) => void;
153
+
154
+ /**
155
+ * Handler type to be called when the execution finishes.
156
+ *
157
+ * @param interpreter The interpreter instance
158
+ */
159
+ export type FinishHandler = (interpreter: Interpreter) => void;
160
+
161
+ /**
162
+ * Options used to configure the Loreline interpreter behavior
163
+ */
164
+ export interface InterpreterOptions {
165
+ /**
166
+ * Optional map of additional functions to make available to the script
167
+ */
168
+ functions?: FunctionsMap;
169
+
170
+ /**
171
+ * Tells whether access is strict or not. If set to true,
172
+ * trying to read or write an undefined variable will throw an error.
173
+ */
174
+ strictAccess?: boolean;
175
+
176
+ /**
177
+ * A custom instantiator to create fields objects.
178
+ */
179
+ customCreateFields?: (interpreter: Interpreter, type: string, node: Node) => any;
180
+
181
+ /**
182
+ * Optional translations map for localization.
183
+ * Built from a parsed translation file using `Loreline.extractTranslations()`.
184
+ */
185
+ translations?: Translations;
186
+ }
187
+
188
+ /**
189
+ * Map of function names to their implementations
190
+ */
191
+ export type FunctionsMap = Record<string, Function>;
192
+
193
+ /**
194
+ * The main public API for Loreline runtime.
195
+ * Provides easy access to the core functionality for parsing and running Loreline scripts.
196
+ */
197
+ export class Loreline {
198
+ /**
199
+ * Parses the given text input and creates an executable `Script` instance from it.
200
+ *
201
+ * This is the first step in working with a Loreline script. The returned
202
+ * `Script` object can then be passed to methods `play()` or `resume()`.
203
+ *
204
+ * @param input The Loreline script content as a string (`.lor` format)
205
+ * @param filePath Optional file path of the input being parsed. If provided, requires `handleFile` as well.
206
+ * @param handleFile Optional file handler to read imports. If that handler is asynchronous, then `parse()` will return null and `callback` argument should be used
207
+ * @param callback If provided, will be called with the resulting script as argument. Mostly useful when reading file imports asynchronously
208
+ * @returns The parsed script as an AST `Script` instance (if loaded synchronously)
209
+ * @throws If the script contains syntax errors or other parsing issues
210
+ */
211
+ static parse(input: string, filePath?: string, handleFile?: ImportsFileHandler, callback?: (script: Script) => void): Script | null;
212
+
213
+ /**
214
+ * Starts playing a Loreline script from the beginning or a specific beat.
215
+ *
216
+ * This function takes care of initializing the interpreter and starting execution
217
+ * immediately. You'll need to provide handlers for dialogues, choices, and
218
+ * script completion.
219
+ *
220
+ * @param script The parsed script (result from `parse()`)
221
+ * @param handleDialogue Function called when dialogue text should be displayed
222
+ * @param handleChoice Function called when player needs to make a choice
223
+ * @param handleFinish Function called when script execution completes
224
+ * @param beatName Optional name of a specific beat to start from (defaults to first beat)
225
+ * @param options Additional options
226
+ * @returns The interpreter instance that is running the script
227
+ */
228
+ static play(
229
+ script: Script,
230
+ handleDialogue: DialogueHandler,
231
+ handleChoice: ChoiceHandler,
232
+ handleFinish: FinishHandler,
233
+ beatName?: string,
234
+ options?: InterpreterOptions
235
+ ): Interpreter;
236
+
237
+ /**
238
+ * Resumes a previously saved Loreline script from its saved state.
239
+ *
240
+ * This allows you to continue a story from the exact point where it was saved,
241
+ * restoring all state variables, choices, and player progress.
242
+ *
243
+ * @param script The parsed script (result from `parse()`)
244
+ * @param handleDialogue Function called when dialogue text should be displayed
245
+ * @param handleChoice Function called when player needs to make a choice
246
+ * @param handleFinish Function called when script execution completes
247
+ * @param saveData The saved game data (typically from `interpreter.save()`)
248
+ * @param beatName Optional beat name to override where to resume from
249
+ * @param options Optional options to configure interpreter behavior
250
+ * @returns The interpreter instance that is running the script
251
+ */
252
+ static resume(
253
+ script: Script,
254
+ handleDialogue: DialogueHandler,
255
+ handleChoice: ChoiceHandler,
256
+ handleFinish: FinishHandler,
257
+ saveData: SaveData,
258
+ beatName?: string,
259
+ options?: InterpreterOptions
260
+ ): Interpreter;
261
+
262
+ /**
263
+ * Extracts translations from a parsed translation script.
264
+ *
265
+ * Given a translation file parsed with `parse()`, this returns a translations map
266
+ * that can be passed as `options.translations` to `play()` or `resume()`.
267
+ *
268
+ * @param script The parsed translation script (result from `parse()` on a `.XX.lor` file)
269
+ * @returns A translations map to pass as `InterpreterOptions.translations`
270
+ */
271
+ static extractTranslations(script: Script): Translations;
272
+
273
+ /**
274
+ * Prints a parsed script back into Loreline source code.
275
+ *
276
+ * @param script The parsed script (result from `parse()`)
277
+ * @param indent The indentation string to use (defaults to two spaces)
278
+ * @param newline The newline string to use (defaults to "\n")
279
+ * @returns The printed source code as a string
280
+ */
281
+ static print(script: Script, indent?: string, newline?: string): string;
282
+ }
283
+
284
+ export class Interpreter {
285
+ /**
286
+ * Creates a new Loreline script interpreter.
287
+ *
288
+ * @param script The parsed script to execute
289
+ * @param handleDialogue Function to call when displaying dialogue text
290
+ * @param handleChoice Function to call when presenting choices
291
+ * @param handleFinish Function to call when execution finishes
292
+ * @param options Additional options
293
+ */
294
+ constructor(
295
+ script: Script,
296
+ handleDialogue: DialogueHandler,
297
+ handleChoice: ChoiceHandler,
298
+ handleFinish: FinishHandler,
299
+ options?: InterpreterOptions
300
+ );
301
+
302
+ /**
303
+ * Starts script execution from the beginning or a specific beat.
304
+ *
305
+ * @param beatName Optional name of the beat to start from. If null, execution starts from
306
+ * the first beat or a beat named "_" if it exists.
307
+ * @throws RuntimeError If the specified beat doesn't exist or if no beats are found in the script
308
+ */
309
+ start(beatName?: string): void;
310
+
311
+ /**
312
+ * Saves the current state of the interpreter.
313
+ * This includes all state variables, character states, and execution stack,
314
+ * allowing execution to be resumed later from the exact same point.
315
+ *
316
+ * @return A SaveData object containing the serialized state
317
+ */
318
+ save(): SaveData;
319
+
320
+ /**
321
+ * Restores the interpreter state from a SaveData object.
322
+ * This allows resuming execution from a previously saved state.
323
+ *
324
+ * @param saveData The SaveData object containing the serialized state
325
+ * @throws RuntimeError If the save data version is incompatible
326
+ */
327
+ restore(saveData: SaveData): void;
328
+
329
+ /**
330
+ * Resumes execution after restoring state.
331
+ * This should be called after restore() to continue execution.
332
+ */
333
+ resume(): void;
334
+
335
+ /**
336
+ * Gets a character by name.
337
+ *
338
+ * @param name The name of the character to get
339
+ * @return The character's fields or null if the character doesn't exist
340
+ */
341
+ getCharacter(name: string): any;
342
+
343
+ /**
344
+ * Gets a specific field of a character.
345
+ *
346
+ * @param character The name of the character
347
+ * @param name The name of the field to get
348
+ * @return The field value or null if the character or field doesn't exist
349
+ */
350
+ getCharacterField(character: string, name: string): any;
351
+ }
352
+
353
+ /**
354
+ * An object to store a Int64 from two `high` and `low` number values.
355
+ */
356
+ export interface Int64 {
357
+ /**
358
+ * High value of the Int64
359
+ */
360
+ high: number;
361
+
362
+ /**
363
+ * Low value of the Int64
364
+ */
365
+ low: number;
366
+ }
367
+
368
+ /**
369
+ * Represents a unique identifier for a node within the AST.
370
+ * Uses a structured ID system with section, branch, block, and node components.
371
+ */
372
+ export interface NodeId {
373
+ /**
374
+ * Converts the NodeId to a string representation.
375
+ * @returns String in the format "section.branch.block.node"
376
+ */
377
+ toString(): string;
378
+
379
+ /**
380
+ * Converts the NodeId to its Int64 representation.
381
+ * @returns The Int64 value of this NodeId
382
+ */
383
+ toInt64(): Int64;
384
+ }
385
+
386
+ /**
387
+ * Base class for all AST nodes. Contains position information and basic JSON conversion.
388
+ */
389
+ export class Node {
390
+ /**
391
+ * A unique identifier for this node within the AST, used to distinguish
392
+ * it from other nodes in the script.
393
+ */
394
+ id: NodeId;
395
+
396
+ /**
397
+ * Source code position where this node appears.
398
+ */
399
+ pos: Position;
400
+
401
+ /**
402
+ * Returns the type of this node.
403
+ * @return String representation of node type
404
+ */
405
+ type(): string;
406
+
407
+ /**
408
+ * Converts the node to a JSON representation.
409
+ * @return Object containing node type and position
410
+ */
411
+ toJson(): any;
412
+
413
+ /**
414
+ * Traverses all child nodes of this node.
415
+ * @param handleNode Function to call for each child node
416
+ */
417
+ each(handleNode: (node: Node, parent: Node) => void): void;
418
+ }
419
+
420
+ /**
421
+ * Represents the root node of a Loreline script AST.
422
+ */
423
+ export class Script extends Node {
424
+ /**
425
+ * Array of top-level declarations in the script.
426
+ */
427
+ body:Array<Node>;
428
+ }
429
+
430
+ /**
431
+ * Base interface to hold loreline values
432
+ * This interface allows to map loreline object fields to game-specific objects.
433
+ */
434
+ export interface Fields {
435
+ /**
436
+ * Called when the object has been created from an interpreter
437
+ */
438
+ lorelineCreate(interpreter: Interpreter): void;
439
+
440
+ /**
441
+ * Get the value associated to the given field key
442
+ */
443
+ lorelineGet(interpreter: Interpreter, key: string): any;
444
+
445
+ /**
446
+ * Set the value associated to the given field key
447
+ */
448
+ lorelineSet(interpreter: Interpreter, key: string, value: any): void;
449
+
450
+ /**
451
+ * Check if a value exists for the given key
452
+ */
453
+ lorelineExists(interpreter: Interpreter, key: string): boolean;
454
+
455
+ /**
456
+ * Get all the fields of this object
457
+ */
458
+ lorelineFields(interpreter: Interpreter): string[];
459
+ }