From 06fffb7b2b907deb2dd9a816653c95f8c177a238 Mon Sep 17 00:00:00 2001 From: Mari Date: Sun, 21 Nov 2021 19:12:34 -0500 Subject: [PATCH] Add the ability to have top-level expressions as well as battler attributes. --- src/scripting/BattlerStatement.test.ts | 103 ++++++++++++++++++++++++ src/scripting/BattlerStatement.ts | 58 +++++++++++++ src/scripting/ExampleScripts.test.ts | 36 +++++++++ src/scripting/NomScript.peggy | 56 ++++++++++--- src/scripting/NomScript.peggy.d.ts | 10 +-- src/scripting/NomScript.test.ts | 22 +---- src/scripting/ScriptExpression.test.ts | 24 ++++++ src/scripting/ScriptExpression.ts | 3 + src/scripting/ScriptFile.test.ts | 27 +++++++ src/scripting/ScriptFile.ts | 6 +- src/scripting/ScriptValue.test.ts | 69 ++++++++++++++-- src/scripting/ScriptValue.ts | 31 +++++-- src/scripting/TopLevelStatement.test.ts | 8 +- src/scripting/TopLevelStatement.ts | 14 ++-- 14 files changed, 406 insertions(+), 61 deletions(-) create mode 100644 src/scripting/BattlerStatement.test.ts create mode 100644 src/scripting/BattlerStatement.ts create mode 100644 src/scripting/ExampleScripts.test.ts create mode 100644 src/scripting/ScriptExpression.test.ts create mode 100644 src/scripting/ScriptExpression.ts create mode 100644 src/scripting/ScriptFile.test.ts diff --git a/src/scripting/BattlerStatement.test.ts b/src/scripting/BattlerStatement.test.ts new file mode 100644 index 0000000..e1dc5f8 --- /dev/null +++ b/src/scripting/BattlerStatement.test.ts @@ -0,0 +1,103 @@ +import { + battlerAttribute, + BattlerAttributeType, + BattlerDeclaration, + battlerDeclaration, + BattlerStatement, + BattlerStatementType, + isBattlerAttribute, + isBattlerDeclaration +} from "./BattlerStatement"; +import {TopLevelStatementType} from "./TopLevelStatement"; +import {ScriptFile, scriptFile} from "./ScriptFile"; +import {parse as parseInternal, SyntaxError} from "./NomScript.peggy"; +import {numberValue} from "./ScriptValue"; + +function parse(text: string): ScriptFile { + return parseInternal(text, {start: "ScriptFile", grammarSource: "testData"}) +} + +describe("BattlerDeclaration", () => { + describe("constructor", () => { + test("sets id property from input", () => { + expect(battlerDeclaration("myId", []).id).toEqual("myId") + }) + }); + describe("typecheck", () => { + test("returns true on constructor output", () => { + expect(isBattlerDeclaration(battlerDeclaration("testId", []))).toBeTruthy() + }) + test("returns true on hand-crafted instance", () => { + expect(isBattlerDeclaration({ + type: TopLevelStatementType.BATTLER, + id: "someId", + contents: [] + })).toBeTruthy() + }) + }); + describe("parsing", () => { + function success(name: string, text: string, ...result: BattlerDeclaration[]) { + test(`succeeds for ${name}`, () => { + expect(parse(text)).toEqual(scriptFile(result)) + }) + } + success("basic empty instance", "battler nomi\nend battler", battlerDeclaration("nomi", [])) + success("comment after opening", "battler nomi // of the nomi crew\nend battler", battlerDeclaration("nomi", [])) + success("comments and empty lines inside the block", "battler nomi\n\n// hungry gal...\n\nend battler", battlerDeclaration("nomi", [])) + success("nonempty instance", "battler nomi\nhealth 20\nconfidence 90\nend battler", + battlerDeclaration("nomi", [ + battlerAttribute(BattlerAttributeType.HEALTH, numberValue(20)), + battlerAttribute(BattlerAttributeType.CONFIDENCE, numberValue(90))])) + + function failure(name: string, text: string) { + test(`fails for ${name}`, () => { + expect(() => parse(text)).toThrow(SyntaxError) + }) + } + + failure("opening statement only", "battler open") + failure("ending statement only", "end battler") + failure("multi identifier", "battler cool girl\nend battler") + failure("containing top level statement", "battler genius\nscript version 3\nend battler") + failure("containing other battler declaration", "battler coolio\nbattler even_cooler\nend battler\nend battler") + }); +}); + +describe("BattlerAttribute", () => { + describe("constructor", () => { + test("forwards the elements to the fields", () => { + const attr = battlerAttribute(BattlerAttributeType.HEALTH, numberValue(20)) + + expect(attr.attribute).toEqual(BattlerAttributeType.HEALTH) + expect(attr.value).toEqual(numberValue(20)) + }) + }) + describe("typecheck", () => { + test("returns true on constructor input", () => { + expect(isBattlerAttribute(battlerAttribute(BattlerAttributeType.HEALTH, numberValue(20)))).toBeTruthy() + }) + test("returns true on hand-crafted instance", () => { + expect(isBattlerAttribute({ + type: BattlerStatementType.ATTRIBUTE, + attribute: BattlerAttributeType.STAMINA, + value: numberValue(20) + })).toBeTruthy() + }) + }) + describe("parsing", () => { + function success(name: string, text: string, ...result: readonly BattlerStatement[]) { + test(`succeeds for ${name}`, () => { + expect(parse(`battler battler\n${text}\nend battler`)).toEqual(scriptFile([battlerDeclaration("battler", result)])) + }) + } + success("basic empty instance", "health 20", battlerAttribute(BattlerAttributeType.HEALTH, numberValue(20))) + success("comment between the attribute and the number", "stamina/* 200 */99", battlerAttribute(BattlerAttributeType.STAMINA, numberValue(99))) + + function failure(name: string, text: string) { + test(`fails for ${name}`, () => { + expect(() => parse(text)).toThrow(SyntaxError) + }) + } + failure("no value after the attribute", "stamina // 99") + }) +}); \ No newline at end of file diff --git a/src/scripting/BattlerStatement.ts b/src/scripting/BattlerStatement.ts new file mode 100644 index 0000000..cbab224 --- /dev/null +++ b/src/scripting/BattlerStatement.ts @@ -0,0 +1,58 @@ +import {TopLevelStatement, TopLevelStatementType} from "./TopLevelStatement"; +import {ScriptExpression} from "./ScriptExpression"; + +export interface BattlerDeclaration { + readonly type: TopLevelStatementType.BATTLER + readonly id: string + readonly contents: readonly BattlerStatement[] +} +export function battlerDeclaration(id: string, contents: readonly BattlerStatement[]): BattlerDeclaration { + return { + type: TopLevelStatementType.BATTLER, + id, + contents, + } +} +export function isBattlerDeclaration(statement: TopLevelStatement): statement is BattlerDeclaration { + return statement.type === TopLevelStatementType.BATTLER +} + +export enum BattlerStatementType { + ATTRIBUTE = "attribute", +} + +export enum BattlerAttributeType { + HEALTH = "health", + CONFIDENCE = "confidence", + STAMINA = "stamina", +} +export function battlerAttributeType(text: string): BattlerAttributeType { + switch (text) { + case "health": + return BattlerAttributeType.HEALTH + case "confidence": + return BattlerAttributeType.CONFIDENCE + case "stamina": + return BattlerAttributeType.STAMINA + default: + throw Error(`Unrecognized attribute for battler: ${text}`) + } +} + +export interface BattlerAttribute { + readonly type: BattlerStatementType.ATTRIBUTE + readonly attribute: BattlerAttributeType + readonly value: ScriptExpression +} +export function battlerAttribute(attribute: BattlerAttributeType, value: ScriptExpression): BattlerAttribute { + return { + type: BattlerStatementType.ATTRIBUTE, + attribute, + value + } +} +export function isBattlerAttribute(statement: BattlerStatement): statement is BattlerAttribute { + return statement.type === BattlerStatementType.ATTRIBUTE +} + +export type BattlerStatement = BattlerAttribute \ No newline at end of file diff --git a/src/scripting/ExampleScripts.test.ts b/src/scripting/ExampleScripts.test.ts new file mode 100644 index 0000000..00c212b --- /dev/null +++ b/src/scripting/ExampleScripts.test.ts @@ -0,0 +1,36 @@ +import {parse} from "./NomScript.peggy"; +import {scriptFile} from "./ScriptFile"; +import {versionStatement} from "./TopLevelStatement"; +import {battlerAttribute, BattlerAttributeType, battlerDeclaration} from "./BattlerStatement"; +import {numberValue} from "./ScriptValue"; + +test("integration test", () => { + const result = parse(` + script version 1 + + battler reya + health 90 + confidence 2000 + stamina 50 + end battler + + battler kun_chan + health 500 + confidence 1000 + stamina 200 + end battler + `, {grammarSource: "testData", start: "ScriptFile"}) + expect(result).toEqual(scriptFile([ + versionStatement(1), + battlerDeclaration("reya", [ + battlerAttribute(BattlerAttributeType.HEALTH, numberValue(90)), + battlerAttribute(BattlerAttributeType.CONFIDENCE, numberValue(2000)), + battlerAttribute(BattlerAttributeType.STAMINA, numberValue(50)) + ]), + battlerDeclaration("kun_chan", [ + battlerAttribute(BattlerAttributeType.HEALTH, numberValue(500)), + battlerAttribute(BattlerAttributeType.CONFIDENCE, numberValue(1000)), + battlerAttribute(BattlerAttributeType.STAMINA, numberValue(200)) + ]), + ])) +}) \ No newline at end of file diff --git a/src/scripting/NomScript.peggy b/src/scripting/NomScript.peggy index 42642f6..18267ec 100644 --- a/src/scripting/NomScript.peggy +++ b/src/scripting/NomScript.peggy @@ -1,30 +1,60 @@ {{ // global (one-time) initializer import { scriptFile } from "./ScriptFile"; - import { numberValue } from "./ScriptValue"; import { versionStatement } from "./TopLevelStatement"; + import { battlerDeclaration, battlerAttributeType, battlerAttribute } from "./BattlerStatement"; + import { numberValue, identifier } from "./ScriptValue"; }} { // per-parse initializer } -// peggy-loader:startRule ScriptFile +// ********************************************************************************************************************* +// * Entry Points +// ********************************************************************************************************************* ScriptFile = statements:TopLevelStatementList { return scriptFile(statements); } +// peggy-loader:startRule ScriptFile +TopLevelExpression = _? @Expression FileEnd +// peggy-loader:startRule TopLevelExpression -NewLine "newline" = "\n" { return null; } -WhiteSpace "whitespace" = [ \r\v\t]+ { return null; } -LineComment "line comment" = "//" [^\n]* { return null; } -BlockCommentTail = "*/" / ( "*" !"/" [^*]* BlockCommentTail ) { return null; } -BlockComment "block comment" = "/*" [^*]* BlockCommentTail { return null; } +// ********************************************************************************************************************* +// * Top-level Statements +// ********************************************************************************************************************* +TopLevelStatementList = (__ head:TopLevelStatement tail:(StatementEnd __ @TopLevelStatement)* FileEnd { return [head, ...tail]; }) / (FileEnd { return []; }) +TopLevelStatement "top-level statement" = VersionStatement / BattlerDeclaration + +VersionStatement "version statement" = "script" _ "version" _ version:WholeNumberLiteral { return versionStatement(version.value); } -IntegerLiteral "integer literal" = digits:$[0-9]+ { return numberValue(digits); } +// ********************************************************************************************************************* +// * Battler Declaration + Statements +// ********************************************************************************************************************* +BattlerDeclaration "battler declaration" = "battler" _ id:Identifier contents:(StatementEnd __ @BattlerStatement)* StatementEnd __ "end" _ "battler" { return battlerDeclaration(id.value, contents) } +BattlerStatement = BattlerAttribute -NonToken = (LineComment / BlockComment / WhiteSpace) { return null; } +BattlerAttribute = attr:BattlerAttributeType _ expr:Expression { return battlerAttribute(attr, expr); } +BattlerAttributeType "attribute name" = attr:("health" / "confidence" / "stamina") { return battlerAttributeType(attr); } + +// ********************************************************************************************************************* +// * Expressions +// ********************************************************************************************************************* +Expression "expression" = WholeNumberLiteral / Identifier + +Identifier "identifier" = id:$([a-zA-Z_$] [a-zA-Z_$0-9]*) { return identifier(id); } +WholeNumberLiteral "whole number literal" = digits:$[0-9]+ { return numberValue(digits); } + +// ********************************************************************************************************************* +// * Whitespace and Comments +// ********************************************************************************************************************* +FileEnd = __ LineComment? +__ = StatementEnd* _? { return null; } +StatementEnd = _? LineComment? NewLine { return null; } _ = (NonToken+) { return null; } -StatementEnd = _? NewLine { return null; } +NonToken = (BlockComment / WhiteSpace) { return null; } -VersionStatement "version statement" = "script" _ "version" _ version:IntegerLiteral { return versionStatement(version.value); } +LineComment "line comment" = "//" [^\n]* { return null; } +BlockComment "block comment" = "/*" [^*]* BlockCommentTail { return null; } +BlockCommentTail = "*/" / ( "*" !"/" [^*]* BlockCommentTail ) { return null; } -TopLevelStatement "top-level statement" = VersionStatement -TopLevelStatementList = (StatementEnd* _? head:TopLevelStatement tail:(StatementEnd+ _? @TopLevelStatement)* StatementEnd* _? { return [head, ...tail]; }) / (StatementEnd* _? { return []; }) \ No newline at end of file +WhiteSpace "whitespace" = [ \r\v\t]+ { return null; } +NewLine "newline" = "\n" { return null; } \ No newline at end of file diff --git a/src/scripting/NomScript.peggy.d.ts b/src/scripting/NomScript.peggy.d.ts index fdee295..99b75c8 100644 --- a/src/scripting/NomScript.peggy.d.ts +++ b/src/scripting/NomScript.peggy.d.ts @@ -1,8 +1,9 @@ -import {ScriptFile} from "./ScriptValue.js" +import {ScriptFile} from "./ScriptValue.js"; +import {ScriptExpression} from "./ScriptExpression"; export interface Options { /** Whether this is a script or an expression. Required. */ - readonly start: "ScriptFile"|"Expression" + readonly startRule: "ScriptFile"|"TopLevelExpression" /** The file (or other source) the text is being parsed from. Required.*/ readonly grammarSource: string } @@ -19,9 +20,8 @@ export interface Location { readonly end: Position } -// For later: -//export function parse(text: string, options: Options & {readonly start: "Expression"}): Expression -export function parse(text: string, options: Options & {readonly start: "ScriptFile"}): ScriptFile +export function parse(text: string, options: Options & {readonly startRule: "TopLevelExpression"}): ScriptExpression +export function parse(text: string, options: Options & {readonly startRule: "ScriptFile"}): ScriptFile export class SyntaxError extends Error { readonly name: "SyntaxError" diff --git a/src/scripting/NomScript.test.ts b/src/scripting/NomScript.test.ts index abe6267..2d193b6 100644 --- a/src/scripting/NomScript.test.ts +++ b/src/scripting/NomScript.test.ts @@ -1,24 +1,10 @@ import { parse, SyntaxError } from "./NomScript.peggy"; describe("parse", () => { - describe("ScriptFile", () => { - test("works with an empty input", () => { - expect(() => parse("", {grammarSource: "testData", start: "ScriptFile"})).not.toThrow() - }); - test("works with empty lines", () => { - expect(() => parse("\n\n\n\n\n", {grammarSource: "testData", start: "ScriptFile"})).not.toThrow() - }); - test("works with lines filled only with whitespace", () => { - expect(() => parse("\n\n \n\t\t \t\n\n", {grammarSource: "testData", start: "ScriptFile"})).not.toThrow() - }); - test("works with lines filled only with comments", () => { - expect(() => parse("\n\n /* */ \n// test \n /* */\n", {grammarSource: "testData", start: "ScriptFile"})).not.toThrow() - }); - test("throws SyntaxError for invalid text", () => { - expect(() => parse("!! invalid !!", {grammarSource: "testData", start: "ScriptFile"})).toThrow(SyntaxError) - }); - }) -}); + test("throws SyntaxError for invalid text", () => { + expect(() => parse("!! invalid !!", {grammarSource: "testData", startRule: "ScriptFile"})).toThrow(SyntaxError) + }); +}) describe("SyntaxError", () => { describe("constructor", () => { diff --git a/src/scripting/ScriptExpression.test.ts b/src/scripting/ScriptExpression.test.ts new file mode 100644 index 0000000..076aefd --- /dev/null +++ b/src/scripting/ScriptExpression.test.ts @@ -0,0 +1,24 @@ +import {parse, SyntaxError} from "./NomScript.peggy"; + +describe("ScriptExpression", () => { + describe("parse", () => { + test("fails with an empty input", () => { + expect(() => parse("", {grammarSource: "testData", startRule: "TopLevelExpression"})).toThrow(SyntaxError) + }); + test("fails with empty lines", () => { + expect(() => parse("\n\n\n\n\n", {grammarSource: "testData", startRule: "TopLevelExpression"})).toThrow(SyntaxError) + }); + test("fails with lines filled only with whitespace", () => { + expect(() => parse("\n\n \n\t\t \t\n\n", {grammarSource: "testData", startRule: "TopLevelExpression"})).toThrow(SyntaxError) + }); + test("fails with lines filled only with comments", () => { + expect(() => parse("\n\n /* */ \n// test \n /* */\n", {grammarSource: "testData", startRule: "TopLevelExpression"})).toThrow(SyntaxError) + }); + test("works for a simple expression", () => { + expect(() => parse("73", {grammarSource: "testData", startRule: "TopLevelExpression"})).not.toThrow() + }); + test("succeeds with end of line comments", () => { + expect(() => parse("90 // you know", {grammarSource: "testData", startRule: "TopLevelExpression"})).not.toThrow() + }); + }); +}); \ No newline at end of file diff --git a/src/scripting/ScriptExpression.ts b/src/scripting/ScriptExpression.ts new file mode 100644 index 0000000..090a959 --- /dev/null +++ b/src/scripting/ScriptExpression.ts @@ -0,0 +1,3 @@ +import {ScriptValue} from "./ScriptValue"; + +export type ScriptExpression = ScriptValue \ No newline at end of file diff --git a/src/scripting/ScriptFile.test.ts b/src/scripting/ScriptFile.test.ts new file mode 100644 index 0000000..e2225e1 --- /dev/null +++ b/src/scripting/ScriptFile.test.ts @@ -0,0 +1,27 @@ +import {parse} from "./NomScript.peggy"; +import {scriptFile} from "./ScriptFile"; +import {versionStatement} from "./TopLevelStatement"; + +describe("ScriptFile", () => { + describe("constructor", () => { + test("puts the statements into the list", () => { + expect(scriptFile([versionStatement(3)])).toEqual({ + statements: [versionStatement(3)] + }) + }); + }); + describe("parse", () => { + test("works with an empty input", () => { + expect(() => parse("", {grammarSource: "testData", startRule: "ScriptFile"})).not.toThrow() + }); + test("works with empty lines", () => { + expect(() => parse("\n\n\n\n\n", {grammarSource: "testData", startRule: "ScriptFile"})).not.toThrow() + }); + test("works with lines filled only with whitespace", () => { + expect(() => parse("\n\n \n\t\t \t\n\n", {grammarSource: "testData", startRule: "ScriptFile"})).not.toThrow() + }); + test("works with lines filled only with comments", () => { + expect(() => parse("\n\n /* */ \n// test \n /* */\n", {grammarSource: "testData", startRule: "ScriptFile"})).not.toThrow() + }); + }); +}); \ No newline at end of file diff --git a/src/scripting/ScriptFile.ts b/src/scripting/ScriptFile.ts index 34f474c..a815486 100644 --- a/src/scripting/ScriptFile.ts +++ b/src/scripting/ScriptFile.ts @@ -1,9 +1,9 @@ -import {TopLevelStatement} from "./TopLevelStatement.js"; +import {TopLevelStatementList} from "./TopLevelStatement.js"; export interface ScriptFile { - readonly statements: readonly TopLevelStatement[] + readonly statements: TopLevelStatementList } -export function scriptFile(statements: readonly TopLevelStatement[]): ScriptFile { +export function scriptFile(statements: TopLevelStatementList): ScriptFile { return { statements } diff --git a/src/scripting/ScriptValue.test.ts b/src/scripting/ScriptValue.test.ts index 10d1428..505a3bf 100644 --- a/src/scripting/ScriptValue.test.ts +++ b/src/scripting/ScriptValue.test.ts @@ -1,16 +1,18 @@ -import {isNumberValue, numberValue, ScriptValueTypes} from "./ScriptValue"; +import {identifier, isIdentifier, isNumberValue, numberValue, ScriptValueType} from "./ScriptValue"; +import { parse, SyntaxError } from "./NomScript.peggy"; +import {ScriptExpression} from "./ScriptExpression"; describe("NumberValue", () => { describe("constructor", () => { test("forwards number parameter to the value property", () => { expect(numberValue(5)).toEqual({ - type: ScriptValueTypes.NUMBER, + type: ScriptValueType.NUMBER, value: 5 }) }) test("parses string parameter to the value property", () => { expect(numberValue("99")).toEqual({ - type: ScriptValueTypes.NUMBER, + type: ScriptValueType.NUMBER, value: 99 }) }) @@ -21,9 +23,66 @@ describe("NumberValue", () => { }) test("passes on a hand-constructed instance", () => { expect(isNumberValue({ - type: ScriptValueTypes.NUMBER, + type: ScriptValueType.NUMBER, value: 21 })).toBeTruthy() }) }) -}) \ No newline at end of file + describe("parsing", () => { + function success(name: string, text: string, result: ScriptExpression) { + test(`succeeds for ${name}`, () => { + expect(parse(text, {startRule: "TopLevelExpression", grammarSource: "testdata"})).toEqual(result) + }) + } + success("zero", "0", numberValue(0)) + success("leading zeroes on a zero", "00000000", numberValue(0)) + success("leading zeroes on a positive number", "0000000050", numberValue(50)) + success("simple positive number", "1", numberValue(1)) + success("long number", "99999999", numberValue(99999999)) + function failure(name: string, text: string) { + test(`fails for ${name}`, () => { + expect(() => parse(text, {startRule: "TopLevelExpression", grammarSource: "testdata"})).toThrow(SyntaxError) + }) + } + failure("number containing non-digit characters", "100a0") + failure("number with decimal point", "100.0") + failure("number with negative sign", "-100") + }) +}) + +describe("ScriptIdentifier", () => { + describe("constructor", () => { + test("transfers the value parameter to the attribute", () => { + expect(identifier("naps").value).toEqual("naps") + }) + }) + describe("typecheck", () => { + test("passes on the output of the constructor", () => { + expect(isIdentifier(identifier("baps"))).toBeTruthy() + }) + test("passes on a hand-constructed instance", () => { + expect(isIdentifier({ + type: ScriptValueType.IDENTIFIER, + value: "laps" + })).toBeTruthy() + }) + }) + describe("parsing", () => { + function success(name: string, text: string, result: ScriptExpression) { + test(`succeeds for ${name}`, () => { + expect(parse(text, {startRule: "TopLevelExpression", grammarSource: "testdata"})).toEqual(result) + }) + } + success("basic identifier", "basic", identifier("basic")) + success("identifier starting with underscore", "_boop", identifier("_boop")) + success("identifier starting with dollar sign", "$tring", identifier("$tring")) + success("single character identifier", "a", identifier("a")) + success("identifier ending in numbers", "$100", identifier("$100")) + function failure(name: string, text: string) { + test(`fails for ${name}`, () => { + expect(() => parse(text, {startRule: "TopLevelExpression", grammarSource: "testdata"})).toThrow(SyntaxError) + }) + } + failure("identifier starting in numbers", "100$") + }) +}); \ No newline at end of file diff --git a/src/scripting/ScriptValue.ts b/src/scripting/ScriptValue.ts index a6aaf3c..01b5f6c 100644 --- a/src/scripting/ScriptValue.ts +++ b/src/scripting/ScriptValue.ts @@ -1,26 +1,41 @@ -export enum ScriptValueTypes { - NUMBER = "number" +export enum ScriptValueType { + NUMBER = "number", + IDENTIFIER = "identifier", } export interface NumberValue { - readonly type: ScriptValueTypes.NUMBER, + readonly type: ScriptValueType.NUMBER, readonly value: number, } -export function numberValue(value: number|string) { +export function numberValue(value: number|string): NumberValue { if (typeof value === "string") { return { - type: ScriptValueTypes.NUMBER, + type: ScriptValueType.NUMBER, value: parseInt(value, 10), } } else { return { - type: ScriptValueTypes.NUMBER, + type: ScriptValueType.NUMBER, value, } } } export function isNumberValue(value: ScriptValue): value is NumberValue { - return value.type === ScriptValueTypes.NUMBER + return value.type === ScriptValueType.NUMBER } -export type ScriptValue = NumberValue \ No newline at end of file +export interface ScriptIdentifier { + readonly type: ScriptValueType.IDENTIFIER + readonly value: string +} +export function identifier(value: string): ScriptIdentifier { + return { + type: ScriptValueType.IDENTIFIER, + value + } +} +export function isIdentifier(value: ScriptValue): value is ScriptIdentifier { + return value.type === ScriptValueType.IDENTIFIER +} + +export type ScriptValue = NumberValue|ScriptIdentifier \ No newline at end of file diff --git a/src/scripting/TopLevelStatement.test.ts b/src/scripting/TopLevelStatement.test.ts index 19398ca..d958c48 100644 --- a/src/scripting/TopLevelStatement.test.ts +++ b/src/scripting/TopLevelStatement.test.ts @@ -1,16 +1,16 @@ import {parse as parseInternal, SyntaxError} from "./NomScript.peggy" -import {isVersionStatement, TopLevelStatementTypes, VersionStatement, versionStatement} from "./TopLevelStatement"; +import {isVersionStatement, TopLevelStatementType, VersionStatement, versionStatement} from "./TopLevelStatement"; import {scriptFile, ScriptFile} from "./ScriptFile"; function parse(text: string): ScriptFile { - return parseInternal(text, {start: "ScriptFile", grammarSource: "testData"}) + return parseInternal(text, {startRule: "ScriptFile", grammarSource: "testData"}) } describe("VersionStatement", () => { describe("constructor", () => { test("forwards parameter to the version property", () => { expect(versionStatement(5)).toEqual({ - type: TopLevelStatementTypes.VERSION, + type: TopLevelStatementType.VERSION, version: 5 }) }) @@ -21,7 +21,7 @@ describe("VersionStatement", () => { }) test("passes on a hand-constructed instance", () => { expect(isVersionStatement({ - type: TopLevelStatementTypes.VERSION, + type: TopLevelStatementType.VERSION, version: 5 })).toBeTruthy() }) diff --git a/src/scripting/TopLevelStatement.ts b/src/scripting/TopLevelStatement.ts index fcba8ef..475e042 100644 --- a/src/scripting/TopLevelStatement.ts +++ b/src/scripting/TopLevelStatement.ts @@ -1,19 +1,23 @@ -export enum TopLevelStatementTypes { +import {BattlerDeclaration} from "./BattlerStatement"; + +export enum TopLevelStatementType { VERSION = "version", + BATTLER = "battler", } export interface VersionStatement { - readonly type: TopLevelStatementTypes.VERSION, + readonly type: TopLevelStatementType.VERSION, readonly version: number, } export function versionStatement(version: number): VersionStatement { return { - type: TopLevelStatementTypes.VERSION, + type: TopLevelStatementType.VERSION, version, } } export function isVersionStatement(statement: TopLevelStatement): statement is VersionStatement { - return statement.type === TopLevelStatementTypes.VERSION + return statement.type === TopLevelStatementType.VERSION } -export type TopLevelStatement = VersionStatement \ No newline at end of file +export type TopLevelStatement = VersionStatement | BattlerDeclaration +export type TopLevelStatementList = readonly TopLevelStatement[] \ No newline at end of file