Introduction
XMLang is an esolang, that is also valid XML. It is a fully-functional, turing-complete programming language, that features the basic things you would expect from a programming language, such as standard input/output, variables, functions, and control flow.
Interpreter
The interpreter is written in Rust, allowing it to be relatively fast and very portable. You can run it locally on your machine, or use the WebAssembly-powered online playground to try it out easily.
License
This project is open-source on GitHub and is licensed under the GNU GPL 3.0 license.
Local installation
The XMLang interpreter is written in Rust, and can be run locally on your machine.
GitHub Actions
The interpreter is built automatically on every commit using GitHub Actions.
You can get the latest built executables for your platform from this link.
Most likely you'll want to download these files:
- Windows:
- MacOS x86_64
- Linux:
Building from source
-
Clone the repository:
git clone https://github.com/GGORG0/xmlang.git
-
Change to the project directory:
cd xmlang
-
Build the project:
cargo build --release
-
The built executable will be located in the
target/release
directory. You can run it with:./target/release/xmlang examples/hello.xml
Online playground
The online playground is available at https://xmlang.ggorg.xyz.
It automatically saves your code in the browser's local storage, so you don't lose it.
Loading code from GitHub
Note: This will replace the code you saved in the browser's local storage.
Gist
The playground supports loading code from GitHub Gist.
The Gist has to contain a single XML file with your code. If it contains multiple files, the first one will be used.
To load a Gist, get its ID (the part after your username in the URL) and append it to the playground URL like this:
https://xmlang.ggorg.xyz/?gist=8da581666cbfd662d6d5fbbe8fce3ca7
The above URL will load the code from this Gist.
Repository
The playground also supports loading code from a file in a GitHub repository.
To load a file, append the repository owner, name, branch and the path to the file to the playground URL like this:
https://xmlang.ggorg.xyz/?owner=GGORG0&repo=xmlang&branch=master&file=examples/hello.xml
The above URL will load the code from this file.
Stack
- WebAssembly (via Wasmer.js) - allows running the XMLang interpreter in the browser inside a full WASI environment with a virtual filesystem.
- CodeMirror - code editor
- Xterm.js - terminal emulator
- Vite - build tool
Building
-
Install the following dependencies:
-
Clone the repository:
git clone https://github.com/GGORG0/xmlang.git
-
Change to the project directory:
cd xmlang
-
Build the WebAssembly module (optional - this will be done automatically by Vite during the build):
cargo build --release --target wasm32-wasip1
-
Build the documentation (optional):
mdbook build
The documentation will be built in the
book
directory. -
Change to the
playground
directory:cd playground
-
Install the dependencies:
pnpm install
-
Build the project:
pnpm build
Or start the development server:
pnpm dev
Language introduction
All XMLang code is valid XML, and can be parsed by any XML parser.
Comments are standard XML comments, and are ignored by the parser:
<!-- This is a comment -->
<element attribute1="value1" attribute2="value2">
<child1>Child 1 content</child1>
<child2>Child 2 content</child2>
<!-- This is another comment -->
</element>
Note: Support for CDATA sections and entity references (for example <
) is not implemented yet.
Elements
Everything in XMLang is an XML element.
An element is a tag in the XML document, which can have attributes, children, and text content.
<element attribute1="value1" attribute2="value2">
<child1>Child 1 content</child1>
<child2>Child 2 content</child2>
</element>
Attributes
Attributes are defined in the opening tag of an element, and are used to provide constant, static values to the element. They can't be dynamically generated or changed at runtime.
Attribute values are always strings by default, but certain built-in elements parse them as other types.
Children
Children are elements that are nested inside another element.
Some elements accept 0, 1, a predefined number, or an arbitrary number of children.
Every child element of an expression is considered an argument to that expression, and the order of the children matters. The children are evaluated in the order they appear in the XML document, and the result of each child is passed to the parent element as an argument.
An element can also be self-closing, as defined by the XML standard:
<element attribute1="value1" attribute2="value2" />
That is equivalent to:
<element attribute1="value1" attribute2="value2"></element>
Text content
Text content is the text that appears between the opening and closing tags of an element.
It is always considered a string, and has to be converted to other types if needed.
You can mix text content with other child elements, but due to parser limitations, the whitespace between the text and the child elements is not preserved, and will be ignored.
<element attribute1="value1" attribute2="value2">
Text content
<child1>Child 1 content</child1>
<child2>Child 2 content</child2>
Other text content
</element>
If you want to preserve whitespace, you can use tricks like the <space />
element or the <join>
element.
<program>
<set var="myVar">wonderful</set>
<print>Hello <space /> <get var="myVar" /> <space /> world!</print>
</program>
When mixing text content with child elements, each text node and child element is considered a separate child of the parent element, and the order of the children matters. If you want to concatenate text content with child elements, keeping them as 1 string child, you can use the <string>
element to wrap the text content and child elements together.
<element attribute1="value1" attribute2="value2">
<string>Text content <child1>Child 1 content</child1> Other text content</string>
</element>
Internally each text content node is represented as an unnamed element (<></>
), with the text as its _text
attribute.
Expressions and statements
There are 2 main types of elements in XMLang: expressions and statements. Almost everything is an expression.
The 2 are distinguished by the fact that statements contain children that aren't valid elements anywhere else in the language, for example, the <if>
statement contains <condition>
, <then>
, and optionally <else>
elements, which are not valid outside an <if>
statement.
Every element returns a value, which can be used by its parent element.
If an element does not return a value, it is considered to return null
.
Data types
XMLang has several built-in data types.
All text content in XMLang is considered a string, and can be converted to other types as needed. For example, these are different:
<set var="n"><int>42</int></set>
<print>
<add>
<get var="n" />
1
</add>
</print> <!-- prints 421 -->
<print>
<add>
<get var="n" />
<int>1</int>
</add>
</print> <!-- prints 43 -->
XMLang supports the following data types:
- null: Represents the absence of a value.
- int: Represents an integer value.
- float: Represents a floating-point number.
- bool: Represents a boolean value, either
true
orfalse
. - string: Represents a UTF-8 encoded string of text.
There are no lists, arrays, dictionaries, objects, classes, or other complex data types in XMLang.
<type>
The <type>
element is used to get the type name of a value as a string.
If multiple children are present, their types are concatenated with a space.
<type>42</type> <!-- int -->
<type>3.14</type> <!-- float -->
<type>true</type> <!-- bool -->
<type>hello</type> <!-- string -->
<type>
<int>42</int>
<float>3.14</float>
<true />
hello
</type> <!-- int float bool string -->
Null
Type name: null
Rust type: ()
This is the most basic data type, representing the absence of a value.
<null />
Conversion to other types
When converting null
to other types, it converts to the following values:
<unwrap>
The <unwrap>
element is used to throw an error if its child is null
. If the child is not null
, it returns the child value.
It optionally receives the message
attribute, which is used as the error message if the child is null
.
<unwrap><null /></unwrap> <!-- Error: "Unwrapped value is null" -->
<unwrap message="Custom error message"><null /></unwrap> <!-- Error: "Custom error message" -->
<unwrap><int>42</int></unwrap> <!-- 42 -->
Integer
Type name: int
Rust type: i64
An integer is a whole number, which can be positive, negative, or zero. Under the hood, integers are represented as 64-bit signed integers.
<int>42</int>
<int>-7</int>
<int>0</int>
Conversion to other types
When converting an int
to other types, it behaves exactly as you would expect:
- float: A float with the same value and a fractional part of
.0
. - bool:
true
if the value is non-zero,false
if it is zero. - string: The string representation of the integer, e.g., the string
42
.
Float
Type name: float
Rust type: f64
A float is a number that can have a fractional part, represented in decimal notation. Under the hood, floats are represented as 64-bit double-precision floating-point numbers.
<float>1.0</float>
<float>3.14</float>
<float>-2.71828</float>
<float>0.0</float>
Conversion to other types
When converting a float
to other types, it behaves exactly as you would expect:
- int: An integer with the same value, discarding the fractional part (always rounding down).
- bool:
true
if the value is non-zero,false
if it is zero (0.0
). - string: The string representation of the float, e.g., the string
42.5
.
Boolean
Type name: bool
Rust type: bool
A boolean is a data type that can have one of two values: true
or false
.
<bool>true</bool>
<bool>yes</bool>
<bool>anything</bool>
<true />
<bool>false</bool>
<bool>no</bool>
<false />
Conversion to other types
When converting a bool
to other types, it converts to the following values:
- int:
1
fortrue
,0
forfalse
. - float:
1.0
fortrue
,0.0
forfalse
. - string: The string representation, which is always
true
orfalse
.
String (and string operations)
Type name: string
Rust type: String
A string is a UTF-8 encoded sequence of characters.
All text content in XMLang is considered a string, and wrapping it in a <string>
element is optional, although sometimes it's necessary for, for example, concatenation - it ensures that the value is treated as a single string, rather than multiple children.
All children of a <string>
element are evaluated, converted to strings and then concatenated into a single string (without any separators). Null values are ignored.
<string>Hello, world!</string>
<string>42</string>
<string>3.14</string>
<string>true</string>
<string>false</string>
<string />
Conversion to other types
When converting a string
to other types, it behaves as follows:
- int: Parses the string as an integer. If the string is not a valid integer, it will throw an error.
- float: Parses the string as a float. If the string is not a valid float, it will throw an error.
- bool: Converts the string to a boolean value, where all values except for the following are considered
true
:false
0
off
no
- empty string
<space />
The <space />
element returns a string with a single space character.
Attributes
count
(int, optional): Specifies the number of space characters to return. Defaults to1
.
Example
<space /> <!-- " " -->
<space count="3" /> <!-- " " -->
<print>
<string>Hello,</string>
<space />
<string>world!</string>
</print> <!-- prints "Hello, world!" -->
<join>
The <join>
element concatenates multiple strings into a single string, with a separator.
Attributes
separator
(string, optional): A string to insert between each child string (default is a single space).start
(string, optional): A string to prefix the result with (default is an empty string).end
(string, optional): A string to suffix the result with (default is an empty string).
Children
All its children get evaluated, and then converted to strings before concatenation.
Example
<join end="!">
<int>1</int>
<string>Hello</string>
<string>world</string>
</join> <!-- "1 Hello world!" -->
<trim>
The <trim>
element removes leading and trailing whitespace from a string.
Attributes
start
(bool, optional): Whether to trim leading whitespace. Defaults totrue
.end
(bool, optional): Whether to trim trailing whitespace. Defaults totrue
.
Children
It accepts exactly 1 child, which is the string to trim. The child is evaluated and converted to a string before trimming.
Example
<trim> Hello, world! </trim> <!-- "Hello, world!" -->
<trim start="true"> Hello, world! </trim> <!-- "Hello, world! " -->
<trim end="true"> Hello, world! </trim> <!-- " Hello, world!" -->
<trim start="true" end="true"> Hello, world! </trim> <!-- "Hello, world!" (default) -->
<trim start="false" end="false"> Hello, world! </trim> <!-- " Hello, world! " (why?) -->
<starts-with>
The <starts-with>
element checks if a string starts with a given prefix, and returns a boolean value.
Children
It accepts exactly 2 children - the first is the string to check, and the second is the prefix to check against.
Example
<starts-with>
<string>Hello, world!</string>
<string>Hello</string>
</starts-with> <!-- true -->
<starts-with>
<string>Hello, world!</string>
<string>world</string>
</starts-with> <!-- false -->
<starts-with>
<string>Hello, world!</string>
<string>hello</string>
</starts-with> <!-- false (case-sensitive) -->
<ends-with>
The <ends-with>
element checks if a string ends with a given suffix, and returns a boolean value.
It is similar to <starts-with>
, but checks the end of the string instead.
Children
It also accepts exactly 2 children - the first is the string to check, and the second is the suffix to check against.
Example
<ends-with>
<string>Hello, world!</string>
<string>world!</string>
</ends-with> <!-- true -->
<ends-with>
<string>Hello, world!</string>
<string>Hello</string>
</ends-with> <!-- false -->
<ends-with>
<string>Hello, world!</string>
<string>WORLD!</string>
</ends-with> <!-- false (case-sensitive) -->
<contains>
The <contains>
element checks if a string contains a given substring, and returns a boolean value.
It is similar to <starts-with>
and <ends-with>
, but checks if the string contains the substring anywhere within it.
Children
It accepts exactly 2 children - the first is the string to check, and the second is the substring to check against.
Example
<contains>
<string>Hello, world!</string>
<string>world</string>
</contains> <!-- true -->
<contains>
<string>Hello, world!</string>
<string>hello</string>
</contains> <!-- false (case-sensitive) -->
<contains>
<string>Hello, world!</string>
<string>!</string>
</contains> <!-- true -->
<program>
Every XMLang program has to be a valid XML document.
The root element of the document is <program>
, which contains the program's code.
This is the simplest XMLang program, which prints Hello, world!
to the standard output:
<program>
<print>Hello, world!</print>
</program>
The <program>
element can contain any number of elements, which are executed in the order they appear in the document.
It is a block - <return>
can be used to stop its execution. The returned value of the <program>
element is discarded.
The <program>
element is only valid as the root element of the document.
It cannot be used anywhere else in the document.
Input/output
<print>
Outputs text to the standard output.
Attributes
newline
(bool, optional): Whether to print a newline after the output. Defaults totrue
. Useful for reading input from the user or printing something in chunks. If set tofalse
, the next output will continue on the same line.
Children
All its children are evaluated, converted to strings and concatenated together (without any separators), in the order they appear in the XML document.
Example
<program>
<print>Hello, world!</print>
<print>Sum of 2 and 3 is: <space /> <add><int>2</int><int>3</int></add></print>
<print newline="false">This is printed without a newline. <space/></print>
<print>And this is printed on the same line.</print>
</program>
This will output:
Hello, world!
Sum of 2 and 3 is: 5
This is printed without a newline. And this is printed on the same line.
<readline />
Reads a line of input from the user (until the 0xA
newline character is reached) and returns it as a string.
The returned value is trimmed of trailing CR (carriage return) and LF (line feed) characters, so it can be used directly without further processing. Further trimming has to be done manually with <trim>
if needed.
It doesn't accept any attributes or children.
Variables
XMLang supports variables, which are used to store data that can be referenced and manipulated throughout the program.
Except for functions, there is 1 global scope in the entire program. Each function execution has its own local scope, which can't access or modify variables in the global scope.
Variables can have any string name, with no restrictions on characters (even spaces are allowed!) or length. However, it's highly recommended to use a proper casing style, with only letters and numbers.
Variables can store values of any data type. The value and type of a variable can be changed at any time, and the new value will be used in subsequent operations.
Example
<program>
<set var="x"><int>5</int></set>
<set var="y"><float>3.14</float></set>
<set var="message">Hello, world!</set>
<print>Value of x: <space /> <get var="x" /></print>
<print>Value of y: <space /> <get>y</get></print>
<print>Message: <space /> <get var="message" /></print>
<print>This variable does not exist: <space /> <get var="non_existent" /></print>
<print>And neither does this one: <space /> <get var="also_non_existent"><int>42</int></get></print>
</program>
<set>
The <set>
element is used to assign a value to a variable.
Attributes
var
(string): The name of the variable to set. If the variable does not exist, it will be created.
Children
It accepts a single child, which is evaluated and the resulting value is assigned to the variable.
<get>
The <get>
element is used to retrieve the value of a variable.
Attributes
var
(string, optional): The name of the variable to retrieve.
Children
If the var
attribute has been provided, <get>
optionally accepts a single child, which is used if the variable does not exist. If the variable exists, this child is ignored. If the variable does not exist and no child is provided, null is returned.
If the var
attribute has not been provided, <get>
must have a single child, which is evaluated and converted to a string. This child is used as the variable name to retrieve. If the variable does not exist, null is returned.
Blocks
Blocks are elements, that execute their children in the order they appear, and return the result of the last child as their own result.
Blocks do not have their own scope, meaning that variables set inside a block are accessible outside of it, and variables set outside of a block are accessible inside it.
Many elements in XMLang are blocks, such as <program>
, <block>
, <loop>
, ;<if>
's children, <try>
's children and functions.
<block>
The <block>
element is used to create a block of code that can be executed.
This is the simplest way to create a block, and it can be useful when you want to calculate a value inside another element's children, while returning a single value.
If no children are provided, the result of the block is null.
Example
<program>
<print>
The result of the block is:
<space />
<block>
<set var="x"><int><readline /></int></set>
<set var="x"><add><get var="x" /><int>1</int></add></set>
<get var="x" /> <!-- Only the result of this child will be returned -->
</block>
</print>
</program>
<return>
The <return>
element is used to return a value from a block or a function.
It stops the execution of the nearest block or function and returns a value to the caller.
No further elements after <return>
are executed.
Children
It accepts at most a single child, which is evaluated and returned as the result of the block or function.
If no child is provided, the result of the block or function is null.
Example
<program>
<print>
The result of the block is:
<space />
<block>
<set var="x"><int><readline /></int></set>
<set var="x"><add><get var="x" /><int>1</int></add></set>
<return><get var="x" /></return>
<print>This will never be printed.</print>
</block>
</print>
</program>
Specials
Specials are constants set by a containing element.
Examples of specials include the error caught by a <try>
element, or the attributes and children passed to a function.
<special>
The <special>
element is used to retrieve the value of a special.
If the special of the requested name, the Special `{name}` not found
error is thrown.
Attributes
name
(string, optional): The name of the special to retrieve.
Children
If the name
attribute has been provided, <special>
does not accept any children. The value of the special with the given name is returned.
If the name
attribute has not been provided, <special>
must have a single child, which is evaluated and converted to a string. This child is used as the name of the special to retrieve.
Error handling
Errors in XMLang work similarly to errors in many other programming languages.
They can be thrown (either manually or by a built-in operation) and caught using the <try>
and <catch>
elements.
When an error is thrown, the program execution stops and control is transferred to the nearest <catch>
element that can handle the error.
If no <catch>
element is found, the program execution stops and the error is printed to the standard output.
<throw>
The <throw>
element is used to throw an error.
Attributes
message
(string, optional): The error message to throw.
Children
If the message
attribute has been provided, <throw>
does not accept any children. The provided message is used as the error message.
If the message
attribute has not been provided, <throw>
can optionally have children, which are evaluated, converted to strings and concatenated, just like in the <print>
element.
- If at least 1 child are provided, the resulting string is used as the error message.
- If no children are provided or the constructed error message is empty, the error message is set to
An error occurred, but no message was provided.
.
<try>
The <try>
element is a statement that allows you to execute a block of code that may throw an error.
If an error is thrown, control is transferred to the nearest <try>
statement's <catch>
element.
Children
This is a statement.
The only elements that can be direct children of <try>
are the <do>
and <catch>
elements, which aren't valid elements anywhere else.
<do>
The <do>
element is used to define a block of fallible code that will be executed when the <try>
statement is reached.
It is a block.
If an error is thrown during the execution of the <do>
block, control is transferred to the nearest <catch>
element.
<catch>
The <catch>
element is used to handle errors thrown by the <do>
block.
It is a block.
Specials
The <catch>
element can access the error that was thrown by the <do>
block using the <special>
element with the name
attribute set to error
. This will return the error as a string.
Example
<program>
<try>
<do>
<print>
The meaning of life is
<space />
<div>
<int>42</int>
<int>0</int> <!-- Oh no! Division by zero! -->
</div>
</print>
</do>
<catch>
<print>
An error occurred:
<space />
<special name="error" />
</print> <!-- Prints: "An error occurred: Division by zero is not allowed" -->
</catch>
</try>
</program>
Mathematical operations
The compatibility tables below represent the actions taken by the interpreter when the operation is applied to two values of the given types. anything means that the operation can be applied to any data type that wasn't explicitly listed above in the list (the list is evaluated from top to bottom, so the first matching type is used).
Folding operations
Addidion, subtraction, multiplication, division, and modulo in XMLang accept any number of children.
They are evaluated and the result is calculated by using the popular fold
iterator method.
The first child's value is used as the initial value and stored in an accumulator. Then, each subsequent child's value is used to update the accumulator by applying the operation. The final value of the accumulator is returned as the result.
If no children are provided, the result is null.
If the two data types of an operation are incompatible, an error (Can't {operation} incompatible types: {type1} and {type2}
) is thrown.
Example
<program>
<print>
<add>
<int>1</int>
<float>2.1</float> <!-- 3.1 -->
<int>3</int> <!-- 6.1 -->
<string>a</string> <!-- "6.1a" -->
</add>
</print> <!-- prints 6.1a -->
</program>
<add>
The order of the children does not matter, as addition is commutative.
Compatible types:
- null + anything = the other value
- int + int = the sum of the two integers (int)
- float + float = the sum of the two floats (float)
- int + float = the sum of the two numbers (float)
- bool + bool = the logical OR of the two booleans (bool)
- bool + anything = the boolean converted to the other type + the other value
- string + string = the concatenation of the two strings (string)
- string + int/float = the concatenation of the string and the numer (string)
<sub>
The order of the children matters, as subtraction is not commutative.
Incompatible types:
Compatible types:
- anything - null = the other value
- null - anything = the negation of the other value
- int - int = the difference of the two integers (int)
- float - float = the difference of the two floats (float)
- int - float = the difference of the two numbers (float)
- float - int = the difference of the two numbers (float)
- bool - bool = the difference of the two booleans converted to int (int)
- bool - anything = the boolean converted to the other type - the other value
- anything - bool = the other value - the boolean converted to the other type
<mul>
The order of the children does not matter, as multiplication is commutative.
Incompabile types:
Compatible types:
- null * anything = null
- int * int = the product of the two integers (int)
- float * float = the product of the two floats (float)
- int * float = the product of the two numbers (float)
- bool * bool = the logical AND of the two booleans (bool)
- bool * anything = if the boolean is
true
, the other value is returned, otherwise null is returned - string * int (positive) = the string repeated int times (string)
- string * int (negative) = the string reversed, and then repeated int times (string)
- string * float (positive) = the string repeated [float converted to int] times (string)
- string * float (negative) = the string reversed, and then repeated [float converted to int] times (string)
<div>
The order of the children matters, as division is not commutative.
Division by zero will throw the error Division by zero is not allowed
.
Incompatible types:
Compatible types:
- null / null = null
- int / int = the quotient of the two integers (int)
- float / float = the quotient of the two floats (float)
- int / float = the quotient of the two numbers (float)
- float / int = the quotient of the two numbers (float)
- bool / bool = the quotient of the two booleans converted to int (int)
- bool / anything = the boolean converted to the other type / the other value
- anything / bool = the other value / the boolean converted to the other type
<mod>
The order of the children matters, as modulo is not commutative.
Modulo by zero will throw the error Division by zero is not allowed
.
Incompatible types:
Compatible types:
- null % null = null
- int % int = the remainder of the two integers (int)
- float % float = the remainder of the two floats (float)
- int % float = the remainder of the two numbers (float)
- float % int = the remainder of the two numbers (float)
- bool % bool = the remainder of the two booleans converted to int (int)
- bool % anything = the boolean converted to the other type % the other value
- anything % bool = the other value % the boolean converted to the other type
Unary operations
Arithmetic negation and absolute value in XMLang accept a single child.
If the data type of an operation is incompatible, an error (Can't {operation} incompatible type: {type}
) is thrown.
<neg>
The <neg>
element is used to compute the additive inverse of a value.
Incompatible types:
Compatible types:
<abs>
The <abs>
element is used to calculate the absolute value of a number.
Incompatible types:
Compatible types:
- null = null
- int = the absolute value of the integer (int)
- float = the absolute value of the float (float)
Logical operations
<not>
The <not>
element is used to negate a boolean value.
Children
It only accepts a single child, which is evaluated and converted to a boolean.
After evaluation, the boolean value is negated (i.e. true
becomes false
, and false
becomes true
).
Example
<program>
<print>
<join>
<not><bool>true</bool></not> <!-- false -->
<not><int>1</int></not> <!-- false -->
<not><float>0.0</float></not> <!-- true -->
<not>hello</not> <!-- false -->
<not><null /></not> <!-- true -->
</join>
</print>
</program>
<and>
The <and>
element is used to perform a logical AND operation on at least two boolean values.
Children
It accepts at least two children, which are evaluated and converted to booleans.
The result is true
if all children evaluate to true
, and false
otherwise.
Example
<program>
<print>
<join>
<and>
<bool>true</bool>
<bool>false</bool>
</and> <!-- false -->
<and>
<int>1</int>
<float>1.0</float>
</and> <!-- true -->
<and>
<bool>true</bool>
<not><null /></not>
<string>hello</string>
</and> <!-- true -->
</join>
</print>
</program>
<or>
The <or>
element is used to perform a logical OR operation on at least two boolean values.
Children
It accepts at least two children, which are evaluated and converted to booleans.
The result is true
if at least one child evaluates to true
, and false
otherwise.
Example
<program>
<print>
<join>
<or>
<bool>true</bool>
<bool>false</bool>
</or> <!-- true -->
<or>
<int>0</int>
<float>0.0</float>
</or> <!-- false -->
</join>
</print>
</program>
<eq>
The <eq>
element is used to check if all of at least two values are equal.
Two values are considered equal if they have the same type and value.
Children
It accepts at least two children.
The result is true
if all children evaluate to the same value, and false
otherwise.
Example
<program>
<print>
<join>
<eq>
<int>42</int>
<int>42</int>
</eq> <!-- true -->
<eq>
<int>3</int>
<float>3.0</float>
</eq> <!-- false -->
<eq>
<bool>true</bool>
<not><null /></not>
<bool><string>hello</string></bool>
</eq> <!-- true -->
<eq>
<null />
<null />
</eq> <!-- true -->
</join>
</print>
</program>
<ne>
The <ne>
element is used to check if none of at least two values are equal.
Children
It accepts at least two children.
The result is true
if all children evaluate to different values, and false
otherwise.
Example
<program>
<print>
<join>
<ne>
<int>42</int>
<int>42</int>
</ne> <!-- false -->
<ne>
<int>3</int>
<float>3.0</float>
</ne> <!-- true -->
<ne>
<bool>true</bool>
<not><null /></not>
<bool><string>hello</string></bool>
</ne> <!-- false -->
<ne>
<bool>true</bool>
<string>true</string>
<not><null /></not>
</ne> <!-- true -->
</join>
</print>
</program>
<gt>
(>), <ge>
(≥), <lt>
(<), <le>
(≤)
The <gt>
, <ge>
, <lt>
, and <le>
elements are used to compare at least two values.
Children
These elements accept at least two children.
The result is true
if all overlapping pairs of children evaluate to values that meet the specified comparison, and false
otherwise.
For example, when using <gt>
with 4 children (a, b, c, d), it returns true
if (a > b AND b > c AND c > d).
The order of the children matters, as the comparisons are made sequentially.
Uncomparable data types
Comparisons are done using the Rust PartialOrd
trait, which allows for uncomparable values.
This means that if you try to compare two incompatible types (e.g., an integer and a string), the result will be false
, no matter the comparison operator used.
This can be counterintuitive, see the example (the second child of the comparison elements is a string, which is not comparable to an integer):
<program>
<print>
<join>
<ge>
<int>42</int>
42
</ge> <!-- false -->
<eq>
<int>42</int>
42
</eq> <!-- false -->
<le>
<int>42</int>
42
</le> <!-- false -->
</join>
</print>
</program>
Data type compatibility
This compatibility list is similar to the one used for mathematical operations.
- null and null are always considered equal.
- null and anything are always considered different.
- int and int are always comparable.
- float and float are always comparable.
- int and float are always comparable (the int is converted to a float).
- bool and bool are always comparable.
- bool and int/float are always comparable (the bool is converted to an int/float).
- string and string are always comparable (lexicographically).
- string and anything are always considered different.
<if>
The <if>
element is a conditional statement that allows you to execute a block of code based on whether a condition is true or false.
Children
This is a statement.
The only elements that can be direct children of <if>
are the <condition>
, <then>
, <elif>
, and <else>
elements, which aren't valid elements anywhere else.
<condition>
The <condition>
element is used to define the condition that will be evaluated to determine whether the <then>
block should be executed.
There must be exactly 1 <condition>
element as a child of <if>
/<elif>
.
It must have a single child, which is evaluated and converted to a boolean. If the result is true
, the <then>
block will be executed.
<then>
The <then>
element is used to define a block of code that will be executed if the <condition>
evaluates to true
.
It is a block.
There must be exactly 1 <then>
element as a child of <if>
/<elif>
.
Specials
The <then>
element can access the result of the <condition>
evaluation using the <special>
element with the name
attribute set to condition
. This will return the result as the type of the value returned by the child of <condition>
(but before being converted to a boolean).
<elif>
The <elif>
element is used to define an additional condition that will be evaluated if the <condition>
evaluates to false
and all previous <elif>
conditions also evaluated to false
.
It is a statement.
There can be any number of <elif>
elements as children of <if>
.
Each <elif>
must have exactly 1 <condition>
and 1 <then>
element as children.
<else>
The <else>
element is used to define a block of code that will be executed if the <condition>
evaluates to false
and all <elif>
conditions also evaluated to false
.
It is a block.
There can be at most 1 <else>
element as a child of <if>
.
If present, it must be the last child of <if>
.
Example
<program>
<set var="secret"><int>42</int></set>
<set var="guess"><int><readline /></int></set>
<if>
<condition>
<eq>
<get var="guess" />
<get var="secret" />
</eq>
</condition>
<then>
<print>Congratulations! You guessed the secret number!</print>
</then>
<elif>
<condition>
<lt>
<get var="guess" />
<get var="secret" />
</lt>
</condition>
<then>
<print>Your guess is too low. Try again!</print>
</then>
</elif>
<else>
<print>Your guess is too high. Try again!</print>
</else>
</if>
</program>
<loop>
The <loop>
element is used to create loops in the program. It allows you to repeat a block of code multiple times.
It's similar to the for
loop in many other programming languages.
Attributes
start
(int, optional): The starting value of the loop counter. If not provided, it defaults to0
.end
(int, optional): The ending value of the loop counter. If not provided, the loop will run indefinitely until a<return>
or<exit />
element is encountered, or an unhandled error is thrown.
The loop will run from start
to end - 1
, incrementing the loop counter by 1
on each iteration.
If the loop terminates due to the counter reaching end - 1
, the result of the loop is null.
Children
This is a block.
Its children will be executed on each iteration of the loop.
Specials
The <loop>
element can access the current loop counter using the <special>
element with the name
attribute set to iteration
. This will return the current value of the loop counter as an int.
<continue />
The <continue />
element is used to skip the rest of the current iteration and move to the next iteration of the loop.
It is only valid inside a <loop>
block.
When used anywhere else, it will throw an error (Tried to continue outside of a loop
).
<return>
Similarly to other blocks, the <return>
element is used to exit the loop and return a value to the caller.
Example
<program>
<set var="sum"><int>0</int></set>
<loop start="1" end="6">
<set var="sum">
<add>
<get var="sum" />
<special name="iteration" />
</add>
</set>
<print>Current iteration: <space /> <special name="iteration" /></print>
</loop>
<print>Total sum: <space /> <get var="sum" /></print> <!-- Prints: "Total sum: 15" -->
</program>
Functions
XMLang supports functions, which are reusable blocks of code.
Functions can take attributes and children, which are used as parameters. They can return a value, which can be used in the calling code. They can be called from anywhere in the program, including inside other functions, provided they are defined before the call.
Unlike anywhere else in the program, functions have their own local scope. They can't access or modify variables in the global scope, and values of the local variables defined in the function do not persist between function calls.
<function>
The <function>
element is used to define a function.
Attributes
name
(string): The name of the function.
Children
This is a block. Its children are saved and will be executed when the function is called.
<call>
The <call>
element is used to call a function.
Attributes
name
(string): The name of the function to call. If the function does not exist, an error will be thrown (Function `{name}` not found
).- Any other attributes: see specials below.
Children
It accepts any number of children, which are evaluated and passed as parameters to the function. See specials below.
Specials
The body of the function (children of <function>
) can access the attributes and children passed to the function using the <special>
element with the name
attribute set to:
- The name of the attribute passed to the
<call>
element to retrieve the value of that attribute as a string. child_count
to retrieve the number of children passed to the<call>
element as an int.child:{index}
to retrieve the value of the child at the specifiedindex
(int). Theindex
is zero-based, so the first child ischild:0
, the second child ischild:1
, and so on.
Example
<program>
<function name="greet">
<set var="person">
<if>
<condition>
<eq>
<special name="child_count" />
<int>0</int>
</eq>
</condition>
<then>
<special name="person" />
</then>
<else>
<special name="child:0" />
</else>
</if>
</set>
<print>Hello, <space /> <get var="person" />!</print>
</function>
<call name="greet" person="Alice" />
<call name="greet">Bob</call>
</program>
<exit />
The <exit />
element is used to terminate the program immediately.
Attributes
code
(int, optional): The exit code of the program. If not provided, the default exit code is0
.
Example
<program>
<print>Exiting the program...</print>
<exit code="1" />
<print>This line will not be executed.</print>
</program>
<delay>
The <delay>
element is used to pause the execution of the program for a specified duration.
Attributes
duration
(int, optional): The duration in milliseconds for which the program should be paused.
Children
If the duration
attribute has been provided, it does not accept any children. The program will pause for the specified duration.
If the duration
attribute has not been provided, it must have a single child, which is evaluated and converted to an int. This value is used as the duration for which the program should be paused.
Example
<program>
<print>Waiting for 1 second...</print>
<delay duration="1000" />
<print>Waiting for 2 seconds...</print>
<delay>
<mul>
<int>2</int>
<int>1000</int>
</mul>
</delay>
<print>Hello, world!</print>
</program>
<rand />
The <rand />
element is used to generate a random integer within a specified range.
Attributes
min
(int, optional): The minimum value of the range (inclusive). Defaults to0
.max
(int, optional): The maximum value of the range (inclusive). Defaults to Rust'si64::MAX
.
Example
<program>
<print>
Random number between 1 and 10: <space /> <rand min="1" max="10" />
</print>
</program>