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.

  1. tokenization

  2. parsing

  3. 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.
  4. byte code emission

And after all that, it can finally run in a VM (virtual machine).

There are plans to just walk down the AST instead of generating and interpreting byte code for multiple reasons.
  • 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, no commands are implemented.

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.

Because of this, chartbuild scripts are required to start by specifying a version.
Currently, there are no versions and defining this or not won't change anything.

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.

namedescriptionvalue
PCEThe compatiblity level for phigros chart engine0
RPEThe compatibility level for Re:PhiEdit1
PHIThe compatibility level for Phigros2

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

namefull pathdescriptiondefault
loggingeditor/logGives access to the log, print, info, warn, error functions inside the editor.enabled
custom eventseventsCustom scripted events used in chartbuild charts.enabled
value interpolate eventscompatibility/eventsThe events used in Phigros and RPE which can only interpolate a property between 2 values.disabled
Enable and disable only adds or removes values or applies rules to the current scope.
// 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
namedescriptionvalue typedefault value
charterThe creator of the chart.strUnknown
composerThe composer of the music used in the chart.strUnknown
artistThe background artist for the background used in the chart.Unknownstr
previewThe song preview in the select menu.range0..=length
titleThe title of the chart (in most cases, the song).strUnknown
nameThe name of the difficulty.strFM
levelThe 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

namedescriptiontype
unsetThe value to express the lack of thereof.unset
truebool
falsebool
PLATFORMThe compatibility level of the platform that's currently executing the chartbuild scriptCompatibilityLevel
PCEThe value of CompatibilityLevel.PCECompatibilityLevel
RPEThe value of CompatibilityLevel.RPECompatibilityLevel
PHIThe value of CompatibilityLevel.PHICompatibilityLevel

Transform groups

Judgeline

Notes

Event triggers