Candy Logo Copyright © 2025-2026 Candy Arts

CandyRef Expressions
CandyRef is the runtime expression parser of Candy Dialogue Engine. With it, a value in a Dialogue is calculated when the line runs, instead of being fixed: a number read from your game, a name chosen from a variable, a condition tested against the player's progress... CandyRef expressions are written like GDScript:
¢globals.gold >= 100 and "Sword" in globals.inventory
A developer who knows GDScript can already write almost every expression. This page describes what CandyRef adds to GDScript, and the few places where it differs. Writing an Expression A string value is treated as an expression when it starts with the candy_ref_symbol, set in Candy_Properties.gd. By default, it is ¢.
ValueRead asResult
5Plain textThe String "5"
¢5An expressionThe integer 5
globals.goldPlain textThe String "globals.gold"
¢globals.goldAn expressionThe value of globals.gold
¢¢50Escaped textThe String "¢50"
A string is not treated as an expression if it doesn't start with the ¢ symbol, so ordinary text such as "2 + 2 = 4", "The price is ¢50" or "100%" is never mistaken for an expression. To write a string that starts with the symbol as a literal, escape the symbol by doubling it: ¢¢ is read as a single literal ¢, and the string is not evaluated. For example, "¢¢50 for this item." is read as the text "¢50 for this item.". Spaces between the symbol and the expression, and inside the expression, are ignored: "¢ globals.gold + 1" is the same as "¢globals.gold+1". Where Expressions Can Be Used Almost every Data key of every Dialogue command accepts an expression instead of a direct value. In Candy Dialogue Creator, the tooltip of a data field says when the field doesn't accept an expression. However, some fields must always be expressions:
  • The Condition of the If, Elif and While commands must start with ¢. Plain text there is an error, so a forgotten symbol is reported instead of silently counting as true.
  • The For command's Condition is written name in ¢iterable, e.g. i in ¢range(3): the part after "in" must start with ¢.
In Spoken Lines, an expression can also be displayed inside the text, between the text variable symbols (default ¤), with no ¢ needed for some special cases. A spoken text that starts with ¢ is itself an expression, whose result is displayed. See Displaying Variables. Values Expressions can contain the same literal values as GDScript:
TypeWritten as
Integers42, -7, 1_000_000, 0xFF, 0b1010
Decimals (floats)3.5, .5, 1., 2e3
Strings"text" or 'text', with escapes such as \n, \" or é
StringNames and NodePaths&"name", ^"Path/To/Node"
Booleans and nulltrue, false, null
Arrays[1, 2, "three"]
Dictionaries{"hp": 10, "mp": 5} or {hp = 10, mp = 5}
Built-in typesVector2(1, 2), Color.RED, Vector2.ZERO
ConstantsPI, TAU, INF, KEY_SPACE
Since dialogue files written in Candy Dialogue Creator only contain strings, an expression is how a field receives a real number, bool, Array or Dictionary: "¢[1, 2, 3]" is an Array, while "[1, 2, 3]" is text. Operators Operators work as in GDScript, with the same priorities. Use parentheses to change the order: ¢(globals.gold + 10) * 2.
OperatorsMeaning
+ - * / % **Addition (also joins Strings and Arrays), subtraction, multiplication, division, remainder, power.
== != < > <= >=Comparisons.
and or not xor && || !Logic. and and or stop as soon as the result is known, as in GDScript.
in not inWhether a value is in an Array, a key is in a Dictionary, or a String is part of another.
x if condition else yx when the condition is true, y otherwise. Only the chosen side is evaluated.
is is not asType checks and conversions: ¢globals.target is Node2D.
& | ^ ~ << >>Bitwise operators, for integers only.
Details:
  • Dividing two integers returns an integer, as in GDScript: ¢5 / 2 returns 2. Write one of them as a decimal for 2.5: ¢5 / 2.0.
  • % also accepts decimals (GDScript only allows it on integers): ¢7.5 % 2 returns 1.5.
  • Comparing values of different types with == is never an error: ¢1 == "1" is false.
  • In a condition, values count as true or false the same way they do in a GDScript if: 0, "", empty Arrays and Dictionaries, null and Vector2.ZERO are false.
Names A name in an expression (globals, candy_de, language...) is searched for in this order:
  • 1. Values registered by add-ons. Candy Dialogue Engine registers file_var (see 'File Variables' below).
  • 2. The properties, functions and signals of the Engine running the Dialogue.
  • 3. Your project's autoloads, such as candy_de or your own globals.
  • 4. Godot's own singletons, such as Input, Time or OS.
  • 5. Godot's constants and type names (PI, Vector2, Node), and your own class_name scripts.
self and caller always mean the Engine running the Dialogue. C# classes registered with [GlobalClass] work as type names too: new(), is, as, static methods, and an instance's exported properties and methods. Their constants, enums and static fields and properties can't be read, because Godot doesn't expose them: add a static method that returns the value to the class. For example, ¢dialogue_mode reads the running Engine's dialogue_mode, and ¢globals.gold reads the gold variable of an autoload named globals. Candy Dialogue Engine's Data Every table in Candy Dialogue Engine's User_Data folder is a property of the candy_de autoload, and so is every setting in Candy_Properties.gd:
¢candy_de.actors["Alice"]["Age"] ¢candy_de.variables["player"] ¢candy_de.language
The variables dictionary and the Roles have symbols of their own, which are shorter to write (see 'Symbols' below). Reading Properties and Keys Properties, Dictionary keys and Array items are read as in GDScript, and can be chained:
¢globals.player.stats["strength"] ¢globals.inventory[0] ¢globals.inventory[-1]
A negative index counts from the end: [-1] is the last item. Unlike GDScript, Dictionary keys can also be read with a dot, when they are plain names. These two are the same:
¢candy_de.actors.Alice.Age ¢candy_de.actors["Alice"]["Age"]
Brackets are still needed for keys that aren't plain names, such as keys with spaces (["Favorite Food"]), or keys that come from another value ([globals.current_npc]). A missing key or property is an error, not null. When a key may not exist, use the Dictionary's get() method with a default value:
¢globals.quests.get("dragon", "not started")
Calling Functions Functions are called as in GDScript, on any value:
¢globals.player_name.to_upper() ¢globals.inventory.size() ¢globals.shop.get_price("Sword") * 2
GDScript's global functions work too: max(), min(), clamp(), str(), int(), randi_range(), range(), len(), load(), and so on. So do the methods of built-in types and classes: Vector2(1, 2).length(), Color.from_hsv(0.5, 1, 1), Node.new(). preload() can't be used: use load() instead. A function called with the wrong number of arguments fails the expression, with an error naming the function. Nothing is off limits: an expression can call any function your game can, Godot's OS singleton included. If your game runs Dialogues it didn't ship with, such as mods, see Security Risks. Symbols Inside an expression, symbols are shortcuts to data you read often:
SymbolExampleSame as
€ Variables¢€goldcandy_de.variables["gold"]
° Roles¢°companioncandy_de.roles["companion"], the Actor in that Role
° Role + property¢°companion.Agecandy_de.actors[candy_de.roles["companion"]]["Age"]
$ Nodes¢$UI/Box/Label.textget_node_or_null("UI/Box/Label").text, from the Engine
% Unique nodes¢%HealthBar.valueget_node_or_null("%HealthBar").value, from the Engine
€ and ° are set in Candy_Properties.gd (vardict_symbol and role_symbol), and can be modified there. $ and % are GDScript's own syntax, and can't be changed. Symbols combine with everything else: ¢€inventory[0], ¢€gold + 10, ¢°companion.DisplayName.to_upper(). Reading an Actor's name property, such as ¢°companion.DisplayName, returns the Default name, untranslated. To display it in the player's language, see 'Reading Names Directly' in Translating Names. Details:
  • Keys that aren't plain names are written in quotes after the symbol: ¢€"high score".
  • Node paths stop at a space, so ¢$Node / 2 divides. For node names with spaces, quote the path: ¢$"My Node/Label".
  • A missing node returns null, not an error.
  • A symbol is only read inside an expression: "€gold" without ¢ is plain text.
Your Own Symbols Your own symbols are declared in User_Data/CandyRef_Shortcuts.gd. It's a 'safe' file: updates never overwrite it. Shortcut symbols read a key from a value, like €:
const SHORTCUT_SYMBOLS := { "¥": "globals.inventory", }
¢¥sword then means globals.inventory["sword"]. Lookup symbols work in two steps, like °: the name alone reads a key from the "keys" value, and followed by [ or a dot, the result is looked up in the "entries" value:
const LOOKUP_SYMBOLS := { "¶": {"keys": "globals.equipped", "entries": "globals.items"}, }
¢¶hand then returns globals.equipped["hand"] (e.g. "sword"), and ¢¶hand.damage returns globals.items[globals.equipped["hand"]]["damage"]. To use the first meaning before a dot, enclose it in parentheses: ¢(¶hand).length() is the length of "sword". Rules for symbols:
  • A symbol can be made of several characters, but don't let it match or contain an operator or punctuation mark used in expressions: + - * / % < > = ! & | ^ ~ ( ) [ ] { } , . : or quotes.
  • Don't use $ or %: they are GDScript's node syntax.
  • Don't use a symbol another add-on registers: every add-on using CandyRef shares the same symbols.
  • Don't use letters, digits or underscores: the characters of a symbol can no longer be used in names.
File Variables file_var() reads a data file and returns its content, read like any other value:
¢file_var("user://save.json").stats.hp ¢file_var("res://Data/prices.cfg")
The file can be a json, txt, cfg, ini, bin, b64, base64, tres, res or csv file. A file that doesn't exist returns an empty Dictionary, so a Set command writing to it creates it. The file is read again every time the expression runs, so it always returns its current content. Writing to Variables Expressions only read values. To change one, use the Set command, whose Variable field names the variable to write to. It is written like an expression, but without ¢:
globals.gold €gold °companion.Mood $UI/Box/Label.text file_var("user://save.json").stats.hp
Writing into a file_var() saves the file. Expressions Inside Values A value read during an expression is evaluated in turn when it starts with ¢. For example, if the variables dictionary contains:
"price": "¢€base_price * 2", "base_price": 50,
then ¢€price + 1 returns 101, not an error about adding a number to a String. Formulas can therefore be stored in your data, and their result follows the values they read. To store text that starts with a literal ¢, escape it: "¢¢". An expression that leads back to itself ("a" reading "b", which reads "a") stops after 32 steps, and prints an error. Errors When an expression can't be read or calculated, the Engine prints an error starting with "CandyRef failed:", followed by the expression and, when the error is inside a stored expression, every expression that led to it. Syntax errors point at the faulty character:
CandyRef failed: Parse error: Expected a value, but found ')' globals.gold + ) ^
The value of a failed expression is null. Commands that need a reliable answer treat a failure as an error rather than as null: a broken If condition, for example, doesn't run the If, the Elifs nor the Else. Common causes:
  • A name that doesn't exist in any of the places listed under 'Names', often a typo or a missing autoload.
  • A Dictionary key that doesn't exist (use get() with a default value).
  • An Array index out of range.
  • Text written without quotes: ¢globals.name == Alice looks for something named Alice. Write ¢globals.name == "Alice".
  • A missing ¢: "globals.gold > 5" is plain text.
Differences from GDScript
  • Dictionary keys can be read with a dot (actors.Alice).
  • % works on decimals.
  • Comparing different types with == returns false instead of an error.
  • Symbols (€, ° and your own) are available.
  • Statements can't be written: no assignments (=, +=), no var, no loops, no func. An expression only calculates one value. Use commands such as Set, If and For for the rest.
  • preload() isn't available: use load().
Advanced Settings Every Engine shares one CandyRef, CandyRef.get_shared(). Its settings can be modified from your own code, in an autoload's _ready() for example; the defaults suit most projects.
  • name_lookup_order: where names are looked for, in order (see 'Names' above). The default is ["constants", "caller", "source", "autoload", "engine_singleton"]: "constants" contains the values add-ons register, "caller" and "source" are the Engine running the Dialogue. A misspelled entry is skipped, with a warning. Without "autoload", candy_de, your own autoloads and the € and ° symbols, which read candy_de, no longer work. Without "engine_singleton", Input, Engine and the other singletons only offer their constants and static methods.
  • max_ast_cache: how many parsed expressions CandyRef retains (512 by default). When the limit is reached, all of them are discarded and parsed again as they're used. -1 means no limit, useful only when your code builds many different expressions; 0 parses every expression anew each time, several times slower.
  • max_resolve_depth: how many expressions inside values can lead into one another before CandyRef stops (32 by default, see 'Expressions Inside Values').
CandyRef.get_shared().max_ast_cache = 1024