Create a new project
Edit a preoject
Delete a project
Import a project
Export a project
Interface
Settings
Chartbuild
Chartbuild is a custom scripting language made to store charts in a programatic way. That is because the features (planned for) this project would result in hard to read and large files if the data were to be stored in formats like json.
Chartbuild is an interpreted language. Before a chartbuild script can run, it goes trough multiple stages.
-
tokenization
-
parsing
-
analysis
This step is perhaps the most important. The VM does not do any checks to see if the instructions are valid. These validations are left to the analyzer which also tries to optimize the code (e.g: with constant folding).
The analyzer is also responsible for executing commands
Currently, this step is ommited becuse the previous analyzer was not good enough due to my lack of programming experience. -
byte code emission
And after all that, it can finally run in a VM (virtual machine).
- The current implementation is an insult to byte code. While AST walking is supposed to be slower and memory hogging it would still probably be more performant than the abomination that I wrote
- It is less error prone. Even now, there probably are a few bugs in the VM that could've been avoided if I dodn't try to do something facny like this.
features
- commands (there's probably a proer name for this)
- variables
- functions and closures
- control flow
-
if, else
Curently, this statement has a bug (wrong jump location. Avoid using it until it is fixed) -
loops
-
Commands
commands in chartbuild are executed by the analyzer during analysis.
Currently, the available commands are
- version
- target
- enable
- disable
- meta
Apart from enable and disable, all of the commands can only be used once.
version
Syntax
#version [version number]
The language will receive new features and bugfixes. To ensure consistent behaviour accross versions, Each patch will be in a new version. Each bug could be re-enabled with an enable command.
The result would be the ability to update scripts without changing the behaviour. and the following example could be done
#version x.x
function(true, 1) // old api
#version x.y
#enable x.x/bugs/bugn
function_updated_and_renamed(1, true) // new api
The script could be updates as easily as if it was just being formatted.
target
Syntax
#target [CompatibilityLevel]
Chartbuild has a lot of features that is incompatible with other formats and yet, the editor allows exporting to those formats as well. That is because it will ignore all incompatibilities and just carries on. By setting the target, you are setting the format you wish the script to be fully compatible with. Which results in additional errors when incompatible features are used.
Possible values are the members of the CompatibilityLevel enum.
| name | description | value |
|---|---|---|
| PCE | The compatiblity level for phigros chart engine | 0 |
| RPE | The compatibility level for Re:PhiEdit | 1 |
| PHI | The compatibility level for Phigros | 2 |
default value: PCE
Under the hood, setting target just enables and disables certain features; Just think of it as a macro.
enable & disable
Syntax
#enable [feature]
#disable [group]/[nested group]/[feature]
Enable or disable features.
Available feautres that can be enabled or disabled
| name | full path | description | default |
|---|---|---|---|
| logging | editor/log | Gives access to the log, print, info, warn, error functions inside the editor. | enabled |
| custom events | events | Custom scripted events used in chartbuild charts. | enabled |
| value interpolate events | compatibility/events | The events used in Phigros and RPE which can only interpolate a property between 2 values. | disabled |
// global scope
{
// a child scope of the global scope
#enable logging
print("it works"); // no issues
// a child scope of the child scope
{
print("it also works here");
}
}
// back in the global scope
print("this does not work");
meta
Syntax
#meta name=value
| name | description | value type | default value |
|---|---|---|---|
| charter | The creator of the chart. | str | Unknown |
| composer | The composer of the music used in the chart. | str | Unknown |
| artist | The background artist for the background used in the chart. | Unknown | str |
| preview | The song preview in the select menu. | range | 0..=length |
| title | The title of the chart (in most cases, the song). | str | Unknown |
| name | The name of the difficulty. | str | FM |
| level | The level of the difficulty. | str | ? |
Scopes
There is not much to say, just know these 4
- Scopes are where variables are stored.
- Blocks create new scopes.
- Rules applied in the parent scope are also applied in the child scope.
- a child scope can access the variables from the parent scope.
// global scope
// add the print, ...etc functions to the current scope
# enable logging
print(0);
{
// child scope
print(1); // a child scope can access the parent scope
# disable logging
// from here, print is not useable
// it won't be removed since it's not in this scope
print(2); // error, cannot use print
}
print(3);
// the disable rule only applied to the child scope,
// it can be used again
Variables
Syntax
[let | const] name (:type) (?=value);
Variable definitions start with either the let or const keyword.
let
The value of the variable can be changed.
const
The value of the variable can be set only once.
whic is why this is also valid
const a: i32;
// since const restircts the value to be settable only once
// declaring a const variable without a value is valid.
const a = 0;
a = 5; // not allowed to set a value of a constant nore than once
type annotations for variables
Type annotations are optional as long as there is a value assigned during initalization.
let a = 0; // ok, a is i32 (infered)
let b: i32; // ok, b is also known
let c; // error, unknown type
It is possible to assign a value of a different type as long as it can be coerced into it.
const a: f32 = 0; // i32 can be coerced into f32
uninitalized variables
While a variable is not assigned a value, it cannot be used.
let a;
print(a); // error, a is uninitalized
a = 0;
print(a); // ok
Functions and closures
Closures are function objects. Chartbuild handles declared function just like closures.
Closures can take in any number of arguments as parameters. If the functions receives more arugemnts than there are declared when it is called, the addition parameters will be ignored.
functions
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
add(1, 2, 3, 4); // 3 and 4 will be ignored
But if there are less parameters than declared, the analyzer will raise an error.
fn add(a: i32, b: i32) -> {
return a + b;
}
add(1); // error: Invalid arguments
Closures can declare the last parameter as a vararg parameter which are basically array parameters.
fn sum(...numbers: i32) -> i32 {
let ret = 0;
for (const n in numbers)
ret += n;
return ret;
}
// this would behave exactly as sum
fn sum2(numbers: [i32]) {
//...
}
sum(1, 2, 3); // returns 6
fn sum_mul(by: i32, ...to_sum: i32) {
return by * sum(to_sum);
}
closures
Closures behave exactly like functions but are declared differently.
The arguments are put between | chcaracters.
const print_a = || print('a');
const print_i32 = |i: i32| print(i);
differences between function and closure declarations
The main difference is that functions require the paramter and return types to be defined, unless, it returns nothing, in which case the return type doesn't need to be defined. On the other hand, when the types are known beforehand, closures can ommit such.
fn use_adder(a: i32, b: i32, adder: fn(i32, i32) -> i32) -> i32 {
return adder(a, b);
}
use_adder(1, 2, |a, b| return a + b);
If, else
What more is there to say?
if (condition) {
// ...
}
else if (other_condition) {
// ...
}
else {
// ...
}
Loops
Creating custom events
build-in functions and values
| name | description | type |
|---|---|---|
| unset | The value to express the lack of thereof. | unset |
| true | bool | |
| false | bool | |
| PLATFORM | The compatibility level of the platform that's currently executing the chartbuild script | CompatibilityLevel |
| PCE | The value of CompatibilityLevel.PCE | CompatibilityLevel |
| RPE | The value of CompatibilityLevel.RPE | CompatibilityLevel |
| PHI | The value of CompatibilityLevel.PHI | CompatibilityLevel |