1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-11-30 18:53:35 +00:00
Funkin/source/funkin/data/DataParse.hx
2023-12-19 01:23:42 -05:00

255 lines
8 KiB
Haxe

package funkin.data;
import funkin.data.song.importer.FNFLegacyData.LegacyNote;
import funkin.data.song.importer.FNFLegacyData.LegacyNoteData;
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds;
import haxe.ds.Either;
import hxjsonast.Json;
import hxjsonast.Json.JObjectField;
import hxjsonast.Tools;
import thx.semver.Version;
import thx.semver.VersionRule;
/**
* `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values.
*
* It also allows for validation, since throwing an error in this function will cause the issue to be properly caught.
* Parsing will fail and `parser.errors` will contain the thrown exception.
*
* Functions must be of the signature `(hxjsonast.Json, String) -> T`, where the String is the property name and `T` is the type of the property.
*/
class DataParse
{
/**
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
* @param json Contains the `pos` and `value` of the property.
* @param name The name of the property.
* @throws Error If the property is not a string or is empty.
* @return The string value.
*/
public static function stringNotEmpty(json:Json, name:String):String
{
switch (json.value)
{
case JString(s):
if (s == "") throw 'Expected property $name to be non-empty.';
return s;
default:
throw 'Expected property $name to be a string, but it was ${json.value}.';
}
}
/**
* `@:jcustomparse(funkin.data.DataParse.semverVersion)`
* @param json Contains the `pos` and `value` of the property.
* @param name The name of the property.
* @return The value of the property as a `thx.semver.Version`.
*/
public static function semverVersion(json:Json, name:String):Version
{
switch (json.value)
{
case JString(s):
if (s == "") throw 'Expected version property $name to be non-empty.';
return s;
default:
throw 'Expected version property $name to be a string, but it was ${json.value}.';
}
}
/**
* `@:jcustomparse(funkin.data.DataParse.semverVersionRule)`
* @param json Contains the `pos` and `value` of the property.
* @param name The name of the property.
* @return The value of the property as a `thx.semver.VersionRule`.
*/
public static function semverVersionRule(json:Json, name:String):VersionRule
{
switch (json.value)
{
case JString(s):
if (s == "") throw 'Expected version rule property $name to be non-empty.';
return s;
default:
throw 'Expected version rule property $name to be a string, but it was ${json.value}.';
}
}
/**
* Parser which outputs a Dynamic value, either a object or something else.
* @param json
* @param name
* @return The value of the property.
*/
public static function dynamicValue(json:Json, name:String):Dynamic
{
return Tools.getValue(json);
}
/**
* Parser which outputs a `Either<Array<LegacyNoteSection>, LegacyNoteData>`.
* Used by the FNF legacy JSON importer.
*/
public static function eitherLegacyNoteData(json:Json, name:String):Either<Array<LegacyNoteSection>, LegacyNoteData>
{
switch (json.value)
{
case JArray(values):
return Either.Left(legacyNoteSectionArray(json, name));
case JObject(fields):
return Either.Right(cast Tools.getValue(json));
default:
throw 'Expected property $name to be note data, but it was ${json.value}.';
}
}
/**
* Parser which outputs a `Either<Float, Array<Float>>`.
*/
public static function eitherFloatOrFloats(json:Json, name:String):Null<Either<Float, Array<Float>>>
{
switch (json.value)
{
case JNumber(f):
return Either.Left(Std.parseFloat(f));
case JArray(fields):
return Either.Right(fields.map((field) -> cast Tools.getValue(field)));
default:
throw 'Expected property $name to be one or multiple floats, but it was ${json.value}.';
}
}
/**
* Parser which outputs a `Either<Float, LegacyScrollSpeeds>`.
* Used by the FNF legacy JSON importer.
*/
public static function eitherLegacyScrollSpeeds(json:Json, name:String):Either<Float, LegacyScrollSpeeds>
{
switch (json.value)
{
case JNumber(f):
return Either.Left(Std.parseFloat(f));
case JObject(fields):
return Either.Right(cast Tools.getValue(json));
default:
throw 'Expected property $name to be scroll speeds, but it was ${json.value}.';
}
}
/**
* Array of JSON fields `[{key, value}, {key, value}]` to a Dynamic object `{key:value, key:value}`.
* @param fields
* @return Dynamic
*/
static function jsonFieldsToDynamicObject(fields:Array<JObjectField>):Dynamic
{
var result:Dynamic = {};
for (field in fields)
{
Reflect.setField(result, field.name, Tools.getValue(field.value));
}
return result;
}
/**
* Array of JSON elements `[Json, Json, Json]` to a Dynamic array `[String, Object, Int, Array]`
* @param jsons
* @return Array<Dynamic>
*/
static function jsonArrayToDynamicArray(jsons:Array<Json>):Array<Null<Dynamic>>
{
return [for (json in jsons) Tools.getValue(json)];
}
static function legacyNoteSectionArray(json:Json, name:String):Array<LegacyNoteSection>
{
switch (json.value)
{
case JArray(values):
return [for (value in values) legacyNoteSection(value, name)];
default:
throw 'Expected property to be an array, but it was ${json.value}.';
}
}
static function legacyNoteSection(json:Json, name:String):LegacyNoteSection
{
switch (json.value)
{
case JObject(fields):
var result:LegacyNoteSection =
{
mustHitSection: false,
sectionNotes: [],
};
for (field in fields)
{
switch (field.name)
{
case 'sectionNotes':
result.sectionNotes = legacyNotes(field.value, field.name);
case 'mustHitSection':
result.mustHitSection = Tools.getValue(field.value);
case 'typeOfSection':
result.typeOfSection = Tools.getValue(field.value);
case 'lengthInSteps':
result.lengthInSteps = Tools.getValue(field.value);
case 'changeBPM':
result.changeBPM = Tools.getValue(field.value);
case 'bpm':
result.bpm = Tools.getValue(field.value);
}
}
return result;
default:
throw 'Expected property $name to be an object, but it was ${json.value}.';
}
}
public static function legacyNoteData(json:Json, name:String):LegacyNoteData
{
switch (json.value)
{
case JObject(fields):
var result = {};
for (field in fields)
{
Reflect.setField(result, field.name, legacyNoteSectionArray(field.value, field.name));
}
return result;
default:
throw 'Expected property $name to be an object, but it was ${json.value}.';
}
}
public static function legacyNotes(json:Json, name:String):Array<LegacyNote>
{
switch (json.value)
{
case JArray(values):
return [for (value in values) legacyNote(value, name)];
default:
throw 'Expected property $name to be an array of notes, but it was ${json.value}.';
}
}
public static function legacyNote(json:Json, name:String):LegacyNote
{
switch (json.value)
{
case JArray(values):
var time:Null<Float> = values[0] == null ? null : Tools.getValue(values[0]);
var data:Null<Int> = values[1] == null ? null : Tools.getValue(values[1]);
var length:Null<Float> = values[2] == null ? null : Tools.getValue(values[2]);
var alt:Null<Bool> = values[3] == null ? null : Tools.getValue(values[3]);
return new LegacyNote(time, data, length, alt);
// return null;
default:
throw 'Expected property $name to be a note, but it was ${json.value}.';
}
}
}