AiScript Syntax
Statements and Expressions
In AiScript, syntax elements are categorized into "statements" and "expressions," excluding comments.
Statements can only be written at the beginning of a line or within syntax elements that accept expressions (such as if
or function literals). They are not expected to return a value and always return null.
Expressions, on the other hand, can be written in almost all syntax elements that require some value and often return a meaningful value.
Comments
Lines starting with //
or sections enclosed by /*
*/
are comments and do not affect the program's behavior.
// Single-line comment
/*
Multi-line comment
*/
Version Notation
By writing the following notation on the first line of the program, you can specify the intended version of AiScript.
This version may be read by the host program.
/// @ 0.16.0
Statements
Variable and Function Declarations
Use let
for immutable variables (constants), var
for mutable variables, and @
for functions.
About Reserved Words
There are some keywords that cannot be used as names when declaring variables or functions.
For details, please refer to keywords.md.
Variables
// Immutable (cannot be reassigned)
let answer = 42
// Mutable (can be reassigned)
var answer2 = 57
// Omission of initial value is not allowed
var answer // Syntax Error
// Reserved words like match cannot be used as variable names
let match = 12 // Syntax Error
// Redeclaration of variables with the same name is prohibited
var a = 1
var a = 2 // Runtime Error
let a = 3 // Runtime Error
Functions
Function declarations work the same as initializing an immutable variable with a function.
// The last expression is implicitly returned
@add(x, y) {
x + y
}
<: add(1, 2) // 3
// Initializing a constant with a literal function works the same
let add2 = @(x, y) {
x + y
}
// You can also explicitly write return
@add3(x, y) {
return x + y
}
// Arguments can be written on multiple lines
@add4(
x,
y
) {
x + y
}
// Optional arguments
@func1(a, b?) {
<: a
<: b // If omitted, it becomes null
}
func1('hoge') // 'hoge' null
// Arguments with default values (can be used in combination with optional arguments)
@func2(a, b?, c = 'piyo', d?) {
<: a
<: b
<: c
<: d
}
func2('hoge', 'fuga') // 'hoge' 'fuga' 'piyo' null
// Variables can be used for default values (the value is fixed at the time of declaration)
var v = 'hoge'
@func3(a = v) {
<: a
}
v = 'fuga'
func3() // 'hoge'
// One-liner
@func4(a,b?,c=1){<:a;<:b;<:c}
// Reserved words like match cannot be used as function names
@match(x, y) { // Syntax Error
x == y
}
// A colon cannot be added after the last argument
@add(x, y,) { // Syntax Error
x + y
}
// Redeclaration is not allowed, same as variables
var func = null
@func() { // Runtime Error
'hoge'
}
// Optional argument syntax and default value syntax cannot be used together
@func(a? = 1) {} // Syntax Error
Assignment
Change the value of a declared variable.
var a = 0
a = 1
<: a // 1
// Variables declared with let cannot be assigned
let a = 0
a = 1 // Runtime Error
Destructuring Assignment
// Array destructuring assignment
var a = ''
var b = ''
[a, b] = ['hoge', 'fuga']
<: a // 'hoge'
<: b // 'fuga'
// Object destructuring assignment
{ name: a, nature: b } = { name: 'Ai-chan', nature: 'kawaii' }
<: a // 'Ai-chan'
<: b // 'kawaii'
// Combination
let ai_kun = {
name: 'Ai-kun',
nature: ['kakkoii', 'kawaii', 'ponkotsu'],
}
{ name: a, nature: [b] } = ai_kun
<: a // 'Ai-kun'
<: b // 'kakkoii'
// Destructuring assignment can also be used in declarations
let [hoge, fuga] = ['hoge', 'fuga']
each let { value: a }, [{ value: 1 }, { value: 2 }] {
<: a // 1, 2
}
// Can also be used in arguments
@func([a, b] = [0, 0]) {
[a, b]
}
func([1, 2]) // [1, 2]
func([1]) // [1, null], not [1, 0]
func() // [0, 0]
// Declarations that include redeclaration are not allowed
var a = 0
let [a, b] = [1, 'piyo'] // Runtime Error
// Cannot be used in namespace declarations
:: Ai {
// Runtime Error
let [chan, kun] = ['kawaii', 'kakkoii']
}
// Error if the assigned value is not a type that can be destructured
[a, b] = 1 // Runtime Error
{ zero: a, one: b } = ['hoge', 'fuga'] // Runtime Error
for
Loops a given number of times.
let repeat = 5
for repeat print('Wan') // WanWanWanWanWan
// Multiple statements can be written using {}
for 2 + 3 {
<: 'Nyan'
} // NyanNyanNyanNyanNyan
// Can also be enclosed in ()
for ({ a: 3 }.a) {
<: 'Piyo'
} // PiyoPiyoPiyo
// Space required just before {
for 5{ // Syntax Error
<: 'Mogu'
}
for-let
Declare an iterator variable and reference it within the loop.
for let i, 5 {
<: i
} // 0 1 2 3 4
// Initial value can also be set
for let i = 3, 5 {
<: i
} // 3 4 5 6 7
// Iterator variable must be declared with let
for var i, 5 {
<: i
} // Syntax Error
each
Loops through each element of an array.
let arr = ['foo', 'bar', 'baz']
each let v, arr {
<: v
} // foo bar baz
// Space required just before {
each let v, arr{ // Syntax Error
<: v
}
while
Loops as long as the condition is true.
If the condition is false from the beginning, the loop is not executed.
var count = 0
while count < 42 {
count += 1
}
<: count // 42
// If the condition is false from the beginning
while false {
<: 'hoge'
} // no output
do-while
Loops as long as the condition is true.
Even if the condition is false from the beginning, the loop is executed once.
var count = 0
do {
count += 1
} while count < 42
<: count // 42
// If the condition is false from the beginning
do {
<: 'hoge'
} while false // hoge
loop
Loops indefinitely until break
is called.
var i = 5
loop {
<: i
i -= 1
if i == 0 break
} // 5 4 3 2 1
Global-only Statements
Special statements that are not allowed to be written inside other syntax elements.
These syntax elements are hoisted at the start of execution, so they are read first regardless of where they are written in the program.
Metadata Syntax
A feature that allows embedding metadata in an AiScript file using a notation similar to object literals.
Metadata may be read by the host program.
Only pure literals excluding functions are allowed as elements, and including any other expressions will result in a syntax error.
### {
name: "example"
version: 42
keywords: ["foo", "bar", "baz"]
}
Namespace
A feature that allows adding a common prefix to multiple constants and functions.
Mutable variables are not allowed.
This is an underdeveloped feature and may undergo significant changes in the future.
:: Ai {
let chan = 'kawaii'
@kun() {
<: chan
}
}
<: Ai:chan // kawaii
Ai:kun() // kawaii
Expressions
Literals
Syntax that allows writing values directly in the script.
For details, see → literals.md
Operators
Represent major operations.
Unary Operators
Used as a prefix to an expression. There are three types: logical negation (!
), positive sign (+
), and negative sign (-
).
Binary Operators
Used between two expressions. There are arithmetic operations and their derivatives (+
,-
,*
,^
,/
,%
), comparison operations (==
,!=
,<
,<=
,>
,>=
), and logical operations (&&
,||
).
Operator Precedence
For example, in 1 + 2 * 3
, 2 * 3
is calculated first, followed by 1 +
. This is because *
has a higher precedence than +
. See the table below for a list of precedences.
This precedence can be changed by enclosing in (
)
, like (1 + 2) * 3
.
Syntactic Sugar of Binary Operators
Binary operators are replaced with corresponding built-in functions during parsing.
(The unary operator !
also has a corresponding function Core:not
, but it is not replaced.)
See the table below for which functions they are replaced with.
List of Operators
The higher the precedence, the higher it is in the table. (Some have the same precedence)
Operator | Corresponding Function | Meaning |
---|---|---|
^ | Core:pow | Exponentiation |
+ (unary) | None | Positive |
- (unary) | None | Negative |
! (unary) | None | Negation |
* | Core:mul | Multiplication |
/ | Core:div | Division |
% | Core:mod | Modulus |
+ | Core:add | Addition |
- | Core:sub | Subtraction |
> | Core:gt | Greater than |
>= | Core:gteq | Greater than or equal to |
< | Core:lt | Less than |
<= | Core:lteq | Less than or equal to |
== | Core:eq | Equal to |
!= | Core:neq | Not equal to |
&& | Core:and | And |
|| | Core:or | Or |
if
Performs conditional branching based on whether the expression following the keyword if
evaluates to true or false.
It can be treated as an expression and returns the value of the last statement. There must be one or more spaces or tabs immediately after if
. (Even if there is a newline)
If the conditional expression evaluates to a value that is not of type bool
, an error occurs.
// Single-line
if answer == 42 print("correct answer")
// Multiple lines
if answer == 42 {
<: "correct answer"
}
// The conditional expression can also be enclosed in ()
if ({ a: true }.a) print('ok')
// Can be used as an expression
<: `{if answer == 42 "collect answer"}`
// else, elif, else if can also be used
let result = if answer == "bebeyo" {
"correct answer"
} elif answer == "ai" {
"kawaii"
} else if answer == "hoge" {
"fuga"
} else {
"wrong answer"
}
// If there is no else, null is returned if the conditional expression is false
<: if false 1 // null
// Spaces before and after the conditional expression are required (even if enclosed in parentheses)
if(true) return 1 // Syntax Error
if (true)return 1 // Syntax Error
// Spaces just before elif, else are required
if (false) {
}elif (true) { // Syntax Error
}else {} // Syntax Error
eval
Also known as block expression. Evaluates the statements within { }
in sequence and returns the value of the last statement.
let foo = eval {
let x = 1
let y = 2
x + y
}
<: foo // 3
match
let x = 1
let y = match x {
case 1 => "yes"
case 0 => "no"
default => "other"
}
<: y // "yes"
// One-liner
<:match x{case 1=>"yes",case 0=>"no",default=>"other"} // "yes"
exists
Returns true if the given variable or function name exists, otherwise returns false.
// Variable bar does not exist, so false
var foo = exists bar
// Variable foo exists, so true
var bar = exists foo