Socket
Socket
Sign inDemoInstall

@hyperionbt/helios

Package Overview
Dependencies
0
Maintainers
2
Versions
160
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.13.21 to 0.13.22

2

package.json
{
"name": "@hyperionbt/helios",
"type": "module",
"version": "0.13.21",
"version": "0.13.22",
"description": "Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to build 100% client-side dApps for Cardano.",

@@ -6,0 +6,0 @@ "main": "helios.js",

@@ -7,12 +7,20 @@ //@ts-check

assert,
assertDefined
assertDefined,
reduceNull,
reduceNullPairs
} from "./utils.js";
/**
* @typedef {import("./tokens.js").Throw} Throw
*/
import {
Group,
Site,
StringLiteral,
SymbolToken,
Token,
UserError,
Word
Word,
assertToken
} from "./tokens.js";

@@ -89,2 +97,15 @@

/**
* @type {null | ((path: StringLiteral) => (string | null))}
*/
let importPathTranslator = null
/**
* Used by VSCode plugin
* @param {(path: StringLiteral) => (string | null)} fn
*/
export function setImportPathTranslator(fn) {
importPathTranslator = fn
}
/**
* @package

@@ -101,18 +122,40 @@ * @param {Token[]} ts

while (ts.length != 0) {
const t = assertDefined(ts.shift()).assertWord();
const t = ts.shift()?.assertWord();
if (!t) {
continue;
}
const kw = t.value;
/**
* @type {Statement | (Statement | null)[] | null}
*/
let s = null;
if (kw == "const") {
statements.push(buildConstStatement(t.site, ts));
s = buildConstStatement(t.site, ts);
} else if (kw == "struct") {
statements.push(buildStructStatement(t.site, ts));
s = buildStructStatement(t.site, ts);
} else if (kw == "func") {
statements.push(buildFuncStatement(t.site, ts));
s = buildFuncStatement(t.site, ts);
} else if (kw == "enum") {
statements.push(buildEnumStatement(t.site, ts));
s = buildEnumStatement(t.site, ts);
} else if (kw == "import") {
statements = statements.concat(buildImportStatements(t.site, ts));
s = buildImportStatements(t.site, ts);
} else {
throw t.syntaxError(`invalid top-level keyword '${kw}'`);
t.syntaxError(`invalid top-level keyword '${kw}'`);
}
if (s) {
if (Array.isArray(s)) {
for (let s_ of s) {
if (s_) {
statements.push(s_);
}
}
} else {
statements.push(s);
}
}
}

@@ -125,15 +168,32 @@

* @package
* @param {Token[]} ts
* @returns {[number, Word]} - [purpose, name] (ScriptPurpose is an integer)
* @param {Token[]} ts
* @param {null | number} expectedPurpose
* @returns {[number, Word] | null} - [purpose, name] (ScriptPurpose is an integer)
* @package
*/
export function buildScriptPurpose(ts) {
export function buildScriptPurpose(ts, expectedPurpose = null) {
// need at least 2 tokens for the script purpose
if (ts.length < 2) {
throw ts[0].syntaxError("invalid script purpose syntax");
if (ts.length == 0) {
Site.dummy().syntaxError("invalid script purpose syntax");
} else {
ts[0].syntaxError("invalid script purpose syntax");
ts.splice(0);
}
return null;
}
const purposeWord = assertDefined(ts.shift()).assertWord();
const purposeWord = ts.shift()?.assertWord();
let purpose;
if (!purposeWord) {
return null;
}
/**
* @type {number | null}
*/
let purpose = null;
if (purposeWord.isWord("spending")) {

@@ -150,9 +210,28 @@ purpose = ScriptPurpose.Spending;

} else if (purposeWord.isKeyword()) {
throw purposeWord.syntaxError(`script purpose missing`);
purposeWord.syntaxError(`script purpose missing`);
ts.unshift(purposeWord);
return null;
} else {
throw purposeWord.syntaxError(`unrecognized script purpose '${purposeWord.value}' (expected 'testing', 'spending', 'staking', 'minting' or 'module')`);
purposeWord.syntaxError(`unrecognized script purpose '${purposeWord.value}' (expected 'testing', 'spending', 'staking', 'minting' or 'module')`);
purpose = -1;
}
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
if (expectedPurpose !== null && purpose !== null) {
if (expectedPurpose != purpose) {
purposeWord.syntaxError(`expected '${getPurposeName(purpose)}' script purpose`);
}
}
const name = assertToken(ts.shift(), purposeWord.site)?.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
if (name.value === "main") {
name.syntaxError(`${purposeWord.value} script can't be named 'main'`);
}
return [purpose, name];

@@ -162,2 +241,34 @@ }

/**
* Also used by VSCode plugin
* @param {Token[]} ts
* @param {number | null} expectedPurpose
* @returns {[number | null, Word | null, Statement[], number]}
*/
export function buildScript(ts, expectedPurpose = null) {
const first = ts[0];
const purposeName = buildScriptPurpose(ts, expectedPurpose);
const statements = buildProgramStatements(ts);
let mainIdx = -1;
const [purpose, name] = purposeName !== null ? purposeName : [null, null];
if (purpose != ScriptPurpose.Module) {
mainIdx = statements.findIndex(s => s.name.value === "main");
if (mainIdx == -1) {
if (name !== null) {
first.site.merge(name.site).syntaxError("entrypoint 'main' not found");
} else {
first.site.syntaxError("entrypoint 'main' not found");
}
}
}
return [purpose, name, statements, mainIdx];
}
/**
* Parses Helios quickly to extract the script purpose header.

@@ -187,5 +298,13 @@ * Returns null if header is missing or incorrectly formed (instead of throwing an error)

let [purposeId, nameWord] = buildScriptPurpose(ts);
const purposeName = buildScriptPurpose(ts);
return [getPurposeName(purposeId), nameWord.value];
src.throwErrors();
if (purposeName !== null) {
const [purpose, name] = purposeName;
return [getPurposeName(purpose), name.value];
} else {
throw new Error("unexpected"); // should've been caught above by calling src.throwErrors()
}
} catch (e) {

@@ -204,26 +323,53 @@ if (!(e instanceof UserError)) {

* @param {Token[]} ts
* @returns {ConstStatement}
* @returns {ConstStatement | null}
*/
function buildConstStatement(site, ts) {
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
if (ts.length == 0) {
site.syntaxError("invalid syntax (expected name after 'const')");
return null;
}
const name = assertToken(ts.shift(), site)?.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
let typeExpr = null;
if (ts[0].isSymbol(":")) {
ts.shift();
if (ts.length > 0 && ts[0].isSymbol(":")) {
const colon = assertDefined(ts.shift());
const equalsPos = SymbolToken.find(ts, "=");
if (equalsPos == -1) {
throw site.syntaxError("invalid syntax");
ts.unshift(colon);
site.merge(ts[ts.length-1].site).syntaxError("invalid syntax (expected '=' after 'const')");
ts.splice(0);
return null;
} else if (equalsPos == 0) {
colon.site.merge(ts[0].site).syntaxError("expected type expression between ':' and '='");
ts.shift();
return null;
}
typeExpr = buildTypeExpr(ts.splice(0, equalsPos));
typeExpr = buildTypeExpr(colon.site, ts.splice(0, equalsPos));
}
const maybeEquals = ts.shift();
if (maybeEquals === undefined) {
throw site.syntaxError("expected '=' after 'consts'");
site.merge(name.site).syntaxError("expected '=' after 'const'");
ts.splice(0);
return null;
} else if (!maybeEquals.isSymbol("=")) {
site.merge(maybeEquals.site).syntaxError("expected '=' after 'const'");
return null;
} else {
void maybeEquals.assertSymbol("=");
const equals = maybeEquals.assertSymbol("=");
if (!equals) {
return null;
}
const nextStatementPos = Word.find(ts, ["const", "func", "struct", "enum", "import"]);

@@ -233,9 +379,16 @@

const valueExpr = buildValueExpr(tsValue);
if (tsValue.length == 0) {
equals.syntaxError("expected expression after '='");
return null;
} else {
const endSite = tsValue[tsValue.length-1].site;
if (ts.length > 0) {
site.setEndSite(ts[0].site);
const valueExpr = buildValueExpr(tsValue);
if (valueExpr === null) {
return null;
} else {
return new ConstStatement(site.merge(endSite), name, typeExpr, valueExpr);
}
}
return new ConstStatement(site, name, typeExpr, valueExpr);
}

@@ -263,3 +416,3 @@ }

* @param {Token[]} ts
* @returns {StructStatement}
* @returns {StructStatement | null}
*/

@@ -270,12 +423,35 @@ function buildStructStatement(site, ts) {

if (maybeName === undefined) {
throw site.syntaxError("expected name after 'struct'");
site.syntaxError("expected name after 'struct'");
return null;
} else {
const name = maybeName.assertWord().assertNotKeyword();
if (!maybeName.isWord()) {
maybeName.syntaxError("expected name after 'struct'");
return null;
} else if (maybeName.isKeyword()) {
maybeName.syntaxError("unexpected keyword after 'struct'");
}
const name = maybeName?.assertWord();
if (!name) {
return null;
}
const maybeBraces = ts.shift();
if (maybeBraces === undefined) {
throw name.syntaxError(`expected '{...}' after 'struct ${name.toString()}'`);
name.syntaxError(`expected '{...}' after 'struct ${name.toString()}'`);
return null;
} else {
if (!maybeBraces.isGroup("{", 1)) {
maybeBraces.syntaxError("expected non-empty '{..}' without separators");
return null;
}
const braces = maybeBraces.assertGroup("{", 1);
if (!braces) {
return null;
}
const [tsFields, tsImpl] = splitDataImpl(braces.fields[0]);

@@ -287,7 +463,7 @@

if (ts.length > 0) {
site.setEndSite(ts[0].site);
if (impl === null) {
return null;
} else {
return new StructStatement(site.merge(braces.site), name, fields, impl);
}
return new StructStatement(site, name, fields, impl);
}

@@ -311,3 +487,3 @@ }

if (fields.findIndex(f => f.name.toString() == fieldName.toString()) != -1) {
throw fieldName.typeError(`duplicate field \'${fieldName.toString()}\'`);
fieldName.typeError(`duplicate field \'${fieldName.toString()}\'`);
}

@@ -320,5 +496,7 @@ }

if (colonPos == -1) {
throw ts[0].syntaxError("expected ':' in data field");
ts[0].site.merge(ts[ts.length-1].site).syntaxError("expected ':' in data field");
return fields;
}
const colon = ts[colonPos];
const tsBef = ts.slice(0, colonPos);

@@ -328,10 +506,16 @@ const tsAft = ts.slice(colonPos+1);

if (maybeFieldName === undefined) {
throw ts[colonPos].syntaxError("expected word before ':'");
colon.syntaxError("expected word before ':'");
continue;
} else {
const fieldName = maybeFieldName.assertWord().assertNotKeyword();
const fieldName = maybeFieldName?.assertWord()?.assertNotKeyword();
if (!fieldName) {
return fields;
}
assertUnique(fieldName);
if (tsAft.length == 0) {
throw ts[colonPos].syntaxError("expected type expression after ':'");
colon.syntaxError("expected type expression after ':'");
return fields;
}

@@ -343,3 +527,4 @@

if (nextColonPos == 0) {
throw tsAft[nextColonPos].syntaxError("expected word before ':'");
tsAft[nextColonPos].syntaxError("expected word before ':'");
return fields;
}

@@ -354,4 +539,8 @@

const typeExpr = buildTypeExpr(tsAft);
const typeExpr = buildTypeExpr(colon.site, tsAft);
if (!typeExpr) {
return fields;
}
fields.push(new DataField(fieldName, typeExpr));

@@ -369,14 +558,23 @@ }

* @param {?TypeExpr} methodOf - methodOf !== null then first arg can be named 'self'
* @returns {FuncStatement}
* @returns {FuncStatement | null}
*/
function buildFuncStatement(site, ts, methodOf = null) {
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const name = assertToken(ts.shift(), site)?.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
if (ts.length == 0) {
name.site.syntaxError("invalid syntax");
return null;
}
const fnExpr = buildFuncLiteralExpr(ts, methodOf, false);
if (ts.length > 0) {
site.setEndSite(ts[0].site);
if (!fnExpr) {
return null;
}
return new FuncStatement(site, name, fnExpr);
return new FuncStatement(site.merge(fnExpr.site), name, fnExpr);
}

@@ -389,22 +587,47 @@

* @param {boolean} allowInferredRetType
* @returns {FuncLiteralExpr}
* @returns {FuncLiteralExpr | null}
*/
function buildFuncLiteralExpr(ts, methodOf = null, allowInferredRetType = false) {
const parens = assertDefined(ts.shift()).assertGroup("(");
if (!parens) {
return null;
}
const site = parens.site;
const args = buildFuncArgs(parens, methodOf);
const arrow = assertDefined(ts.shift()).assertSymbol("->");
const arrow = assertToken(ts.shift(), site)?.assertSymbol("->");
if (!arrow) {
return null;
}
const bodyPos = Group.find(ts, "{");
if (bodyPos == -1) {
throw site.syntaxError("no function body");
site.syntaxError("no function body");
return null;
} else if (bodyPos == 0 && !allowInferredRetType) {
throw site.syntaxError("no return type specified");
site.syntaxError("no return type specified");
}
const retTypeExprs = buildFuncRetTypeExprs(arrow.site, ts.splice(0, bodyPos), allowInferredRetType);
const bodyExpr = buildValueExpr(assertDefined(ts.shift()).assertGroup("{", 1).fields[0]);
if (retTypeExprs === null) {
return null;
}
const bodyGroup = assertToken(ts.shift(), site)?.assertGroup("{", 1)
if (!bodyGroup) {
return null;
}
const bodyExpr = buildValueExpr(bodyGroup.fields[0]);
if (!bodyExpr) {
return null;
}
return new FuncLiteralExpr(site, args, retTypeExprs, bodyExpr);

@@ -429,13 +652,17 @@ }

const name = assertDefined(ts.shift()).assertWord();
const name = assertToken(ts.shift(), parens.site)?.assertWord();
if (!name) {
continue;
}
if (name.toString() == "self") {
if (i != 0 || methodOf === null) {
throw name.syntaxError("'self' is reserved");
name.syntaxError("'self' is reserved");
} else {
if (ts.length > 0) {
if (ts[0].isSymbol(":")) {
throw ts[0].syntaxError("unexpected type expression after 'self'");
ts[0].syntaxError("unexpected type expression after 'self'");
} else {
throw ts[0].syntaxError("unexpected token");
ts[0].syntaxError("unexpected token");
}

@@ -449,5 +676,5 @@ } else {

if (ts[0].isSymbol(":")) {
throw ts[0].syntaxError("unexpected type expression after '_'");
ts[0].syntaxError("unexpected type expression after '_'");
} else {
throw ts[0].syntaxError("unexpected token");
ts[0].syntaxError("unexpected token");
}

@@ -458,7 +685,9 @@ } else {

} else {
name.assertNotKeyword();
if (name.isKeyword()) {
name.syntaxError("unexpected keyword");
}
for (let prev of args) {
if (prev.name.toString() == name.toString()) {
throw name.syntaxError(`duplicate argument '${name.toString()}'`);
name.syntaxError(`duplicate argument '${name.toString()}'`);
}

@@ -469,6 +698,10 @@ }

if (maybeColon === undefined) {
throw name.syntaxError(`expected ':' after '${name.toString()}'`);
name.syntaxError(`expected ':' after '${name.toString()}'`);
} else {
const colon = maybeColon.assertSymbol(":");
if (!colon) {
continue;
}
const equalsPos = SymbolToken.find(ts, "=");

@@ -483,24 +716,29 @@

if (equalsPos == ts.length-1) {
throw ts[equalsPos].syntaxError("expected expression after '='");
}
ts[equalsPos].syntaxError("expected expression after '='");
} else {
const vts = ts.splice(equalsPos);
const vts = ts.splice(equalsPos);
vts.shift()?.assertSymbol("=");
defaultValueExpr = buildValueExpr(vts);
vts.shift()?.assertSymbol("=");
defaultValueExpr = buildValueExpr(vts);
hasDefaultArgs = true;
hasDefaultArgs = true;
}
} else {
if (hasDefaultArgs) {
throw name.syntaxError("positional args must come before default args");
name.syntaxError("positional args must come before default args");
}
}
/**
* @type {TypeExpr | null}
*/
let typeExpr = null;
if (ts.length == 0) {
throw colon.syntaxError("expected type expression after ':'");
colon.syntaxError("expected type expression after ':'");
} else {
typeExpr = buildTypeExpr(colon.site, ts);
}
const typeExpr = buildTypeExpr(ts);
args.push(new FuncArg(name, typeExpr, defaultValueExpr));

@@ -518,3 +756,3 @@ }

* @param {Token[]} ts
* @returns {EnumStatement}
* @returns {EnumStatement | null}
*/

@@ -525,16 +763,27 @@ function buildEnumStatement(site, ts) {

if (maybeName === undefined) {
throw site.syntaxError("expected word after 'enum'");
site.syntaxError("expected word after 'enum'");
return null
} else {
const name = maybeName.assertWord().assertNotKeyword();
const name = maybeName.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
const maybeBraces = ts.shift();
if (maybeBraces === undefined) {
throw name.syntaxError(`expected '{...}' after 'enum ${name.toString()}'`);
name.syntaxError(`expected '{...}' after 'enum ${name.toString()}'`);
return null;
} else {
const braces = maybeBraces.assertGroup("{", 1);
if (!braces) {
return null;
}
const [tsMembers, tsImpl] = splitDataImpl(braces.fields[0]);
if (tsMembers.length == 0) {
throw braces.syntaxError("expected at least one enum member");
braces.syntaxError("expected at least one enum member");
}

@@ -546,3 +795,9 @@

while (tsMembers.length > 0) {
members.push(buildEnumMember(tsMembers));
const member = buildEnumMember(tsMembers);
if (!member) {
continue;
}
members.push(member);
}

@@ -552,7 +807,7 @@

if (ts.length > 0) {
site.setEndSite(ts[0].site);
if (!impl) {
return null;
}
return new EnumStatement(site, name, members, impl);
return new EnumStatement(site.merge(braces.site), name, members, impl);
}

@@ -566,3 +821,3 @@ }

* @param {Token[]} ts
* @returns {ImportStatement[]}
* @returns {(ImportStatement | null)[] | null}
*/

@@ -573,61 +828,94 @@ function buildImportStatements(site, ts) {

if (maybeBraces === undefined) {
throw site.syntaxError("expected '{...}' after 'import'");
site.syntaxError("expected '{...}' after 'import'");
return null;
} else {
const braces = maybeBraces.assertGroup("{");
if (!braces) {
return null;
}
const maybeFrom = ts.shift();
const maybeFrom = assertToken(ts.shift(), maybeBraces.site, "expected 'from' after 'import {...}'")?.assertWord("from");
if (!maybeFrom) {
return null;
}
if (maybeFrom === undefined) {
throw maybeBraces.syntaxError("expected 'from' after 'import {...}'");
const maybeModuleName = assertToken(ts.shift(), maybeFrom.site, "expected module name after 'import {...} from'");
if (!maybeModuleName) {
return null;
}
/**
* @type {null | undefined | Word}
*/
let moduleName = null;
if (maybeModuleName instanceof StringLiteral && importPathTranslator) {
let translated = importPathTranslator(maybeModuleName);
if (!translated) {
return null;
}
moduleName = new Word(maybeModuleName.site, translated);
} else {
const maybeModuleName = ts.shift();
moduleName = maybeModuleName.assertWord()?.assertNotKeyword();
}
if (maybeModuleName === undefined) {
throw maybeFrom.syntaxError("expected module name after 'import {...} from'");
if (!moduleName) {
return null;
}
const mName = moduleName;
if (braces.fields.length === 0) {
braces.syntaxError("expected at least 1 import field");
}
return braces.fields.map(fts => {
const ts = fts.slice();
const maybeOrigName = ts.shift();
if (maybeOrigName === undefined) {
braces.syntaxError("empty import field");
return null;
} else {
maybeFrom.assertWord("from");
const moduleName = maybeModuleName.assertWord().assertNotKeyword();
const origName = maybeOrigName.assertWord();
if (braces.fields.length === 0) {
throw braces.syntaxError("expected at least 1 import field");
}
if (!origName) {
return null;
} else if (ts.length === 0) {
return new ImportStatement(site, origName, origName, mName);
} else {
const maybeAs = ts.shift();
return braces.fields.map(fts => {
const ts = fts.slice();
const maybeOrigName = ts.shift();
if (maybeOrigName === undefined) {
throw braces.syntaxError("empty import field");
if (maybeAs === undefined) {
maybeOrigName.syntaxError(`expected 'as' or nothing after '${origName.value}'`);
return null;
} else {
const origName = maybeOrigName.assertWord();
if (ts.length === 0) {
return new ImportStatement(site, origName, origName, moduleName);
} else {
const maybeAs = ts.shift();
maybeAs.assertWord("as");
if (maybeAs === undefined) {
throw maybeOrigName.syntaxError(`expected 'as' or nothing after '${origName.value}'`);
} else {
maybeAs.assertWord("as");
const maybeNewName = ts.shift();
const maybeNewName = ts.shift();
if (maybeNewName === undefined) {
maybeAs.syntaxError("expected word after 'as'");
return null;
} else {
const newName = maybeNewName.assertWord();
if (maybeNewName === undefined) {
throw maybeAs.syntaxError("expected word after 'as'");
} else {
const newName = maybeNewName.assertWord();
if (!newName) {
return null;
}
const rem = ts.shift();
if (rem !== undefined) {
throw rem.syntaxError("unexpected");
} else {
return new ImportStatement(site, newName, origName, moduleName);
}
}
const rem = ts.shift();
if (rem !== undefined) {
rem.syntaxError("unexpected token");
return null;
} else {
return new ImportStatement(site, newName, origName, mName);
}
}
}
})
}
}
}
}).filter(f => f !== null)
}

@@ -639,15 +927,21 @@ }

* @param {Token[]} ts
* @returns {EnumMember}
* @returns {EnumMember | null}
*/
function buildEnumMember(ts) {
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const name = assertDefined(ts.shift()).assertWord()?.assertNotKeyword();
if (ts.length == 0 || ts[0].isWord()) {
if (!name) {
return null;
} else if (ts.length == 0 || ts[0].isWord()) {
return new EnumMember(name, []);
} else {
const braces = assertDefined(ts.shift()).assertGroup("{", 1);
const braces = assertToken(ts.shift(), name.site)?.assertGroup("{", 1);
const fields = buildDataFields(braces.fields[0]);
if (!braces) {
return null;
} else {
const fields = buildDataFields(braces.fields[0]);
return new EnumMember(name, fields);
return new EnumMember(name, fields);
}
}

@@ -662,11 +956,15 @@ }

* @param {?Site} endSite
* @returns {ImplDefinition}
* @returns {ImplDefinition | null}
*/
function buildImplDefinition(ts, selfTypeExpr, fieldNames, endSite) {
/**
* @param {Word} name
* @param {Word} name
* @returns {boolean}
*/
function assertNonAuto(name) {
function isNonAuto(name) {
if (AUTOMATIC_METHODS.findIndex(n => n == name.toString()) != -1) {
throw name.syntaxError(`'${name.toString()}' is a reserved member`);
name.syntaxError(`'${name.toString()}' is a reserved member`);
return false;
} else {
return true;
}

@@ -676,3 +974,5 @@ }

for (let fieldName of fieldNames) {
assertNonAuto(fieldName);
if (!isNonAuto(fieldName)) {
return null;
}
}

@@ -683,12 +983,14 @@

/**
* @param {number} i
* @param {number} i
* @returns {boolean} - ok
*/
function assertUnique(i) {
function isUnique(i) {
let s = statements[i];
assertNonAuto(s.name);
isNonAuto(s.name);
for (let fieldName of fieldNames) {
if (fieldName.toString() == s.name.toString()) {
throw s.name.syntaxError(`'${s.name.toString()}' is duplicate`);
s.name.syntaxError(`'${s.name.toString()}' is duplicate`);
return false;
}

@@ -699,5 +1001,8 @@ }

if (statements[j].name.toString() == s.name.toString()) {
throw statements[j].name.syntaxError(`'${s.name.toString()}' is duplicate`);
statements[j].name.syntaxError(`'${s.name.toString()}' is duplicate`);
return false;
}
}
return true;
}

@@ -708,3 +1013,5 @@

for (let i = 0; i < n; i++) {
assertUnique(i);
if (!isUnique(i)) {
return null;
}
}

@@ -731,5 +1038,13 @@

const t = assertDefined(ts.shift()).assertWord();
if (!t) {
continue;
}
const kw = t.value;
let s;
/**
* @type {null | ConstStatement | FuncStatement}
*/
let s = null;

@@ -741,6 +1056,8 @@ if (kw == "const") {

} else {
throw t.syntaxError("invalid impl syntax");
t.syntaxError("invalid impl syntax");
}
statements.push(s);
if (s) {
statements.push(s);
}
}

@@ -753,7 +1070,11 @@

* @package
* @param {Site} site
* @param {Token[]} ts
* @returns {TypeExpr}
* @returns {TypeExpr | null}
*/
function buildTypeExpr(ts) {
assert(ts.length > 0);
function buildTypeExpr(site, ts) {
if (ts.length == 0) {
site.syntaxError("expected token");
return null;
}

@@ -773,3 +1094,4 @@ if (ts[0].isGroup("[")) {

} else {
throw ts[0].syntaxError("invalid type syntax")
ts[0].syntaxError("invalid type syntax");
return null;
}

@@ -781,3 +1103,3 @@ }

* @param {Token[]} ts
* @returns {ListTypeExpr}
* @returns {ListTypeExpr | null}
*/

@@ -787,4 +1109,12 @@ function buildListTypeExpr(ts) {

const itemTypeExpr = buildTypeExpr(ts);
if (!brackets) {
return null
}
const itemTypeExpr = buildTypeExpr(brackets.site, ts);
if (!itemTypeExpr) {
return null;
}
return new ListTypeExpr(brackets.site, itemTypeExpr);

@@ -796,3 +1126,3 @@ }

* @param {Token[]} ts
* @returns {MapTypeExpr}
* @returns {MapTypeExpr | null}
*/

@@ -802,22 +1132,37 @@ function buildMapTypeExpr(ts) {

const maybeKeyTypeExpr = ts.shift();
if (!kw) {
return null;
}
if (maybeKeyTypeExpr === undefined) {
throw kw.syntaxError("missing Map key-type");
} else {
const keyTypeTs = maybeKeyTypeExpr.assertGroup("[", 1).fields[0];
if (keyTypeTs.length == 0) {
throw kw.syntaxError("missing Map key-type (brackets can't be empty)");
} else {
const keyTypeExpr = buildTypeExpr(keyTypeTs);
const maybeKeyTypeExpr = assertToken(ts.shift(), kw.site, "missing Map key-type");
if (ts.length == 0) {
throw kw.syntaxError("missing Map value-type");
} else {
const valueTypeExpr = buildTypeExpr(ts);
if (!maybeKeyTypeExpr) {
return null;
}
return new MapTypeExpr(kw.site, keyTypeExpr, valueTypeExpr);
}
}
const keyTypeTs = maybeKeyTypeExpr.assertGroup("[", 1)?.fields[0];
if (keyTypeTs === null || keyTypeTs === undefined) {
return null;
} else if (keyTypeTs.length == 0) {
kw.syntaxError("missing Map key-type (brackets can't be empty)");
return null;
}
const keyTypeExpr = buildTypeExpr(kw.site, keyTypeTs);
if (!keyTypeExpr) {
return null;
}
if (ts.length == 0) {
kw.syntaxError("missing Map value-type");
return null;
}
const valueTypeExpr = buildTypeExpr(kw.site, ts);
if (!valueTypeExpr) {
return null;
}
return new MapTypeExpr(kw.site, keyTypeExpr, valueTypeExpr);
}

@@ -828,3 +1173,3 @@

* @param {Token[]} ts
* @returns {TypeExpr}
* @returns {TypeExpr | null}
*/

@@ -834,4 +1179,17 @@ function buildOptionTypeExpr(ts) {

const someTypeExpr = buildTypeExpr(assertDefined(ts.shift()).assertGroup("[", 1).fields[0]);
if (!kw) {
return null;
}
const typeTs = assertToken(ts.shift(), kw.site)?.assertGroup("[", 1)?.fields[0];
if (!typeTs) {
return null;
}
const someTypeExpr = buildTypeExpr(kw.site, typeTs);
if (!someTypeExpr) {
return null;
}
const typeExpr = new OptionTypeExpr(kw.site, someTypeExpr);

@@ -841,8 +1199,16 @@ if (ts.length > 0) {

if (ts.length > 2) {
throw ts[2].syntaxError("unexpected token");
ts[2].syntaxError("unexpected token");
return null;
} else {
const memberName = ts[1].assertWord()
if (!memberName) {
return null;
}
return new TypePathExpr(ts[0].site, typeExpr, memberName);
}
return new TypePathExpr(ts[0].site, typeExpr, ts[1].assertWord());
} else {
throw ts[0].syntaxError("invalid option type syntax");
ts[0].syntaxError("invalid option type syntax");
return null;
}

@@ -857,3 +1223,3 @@ } else {

* @param {Token[]} ts
* @returns {FuncTypeExpr}
* @returns {FuncTypeExpr | null}
*/

@@ -863,15 +1229,26 @@ function buildFuncTypeExpr(ts) {

if (!parens) {
return null;
}
let hasOptArgs = false;
const argTypes = parens.fields.map(f => {
const argTypes = reduceNull(parens.fields.map(f => {
const fts = f.slice();
if (fts.length == 0) {
throw parens.syntaxError("expected func arg type");
parens.syntaxError("expected func arg type");
return null;
}
const funcArgTypeExpr = buildFuncArgTypeExpr(fts);
if (!funcArgTypeExpr) {
return null;
}
if (hasOptArgs) {
if (!funcArgTypeExpr.isOptional()) {
throw funcArgTypeExpr.syntaxError("optional arguments must come last");
funcArgTypeExpr.syntaxError("optional arguments must come last");
return null;
}

@@ -885,13 +1262,26 @@ } else {

return funcArgTypeExpr;
});
}));
if (argTypes.some(at => at.isNamed()) && argTypes.some(at => !at.isNamed())) {
throw argTypes[0].syntaxError("can't mix named and unnamed args in func type");
}
if (!argTypes) {
return null;
} else {
if (argTypes.some(at => at.isNamed()) && argTypes.some(at => !at.isNamed())) {
argTypes[0].syntaxError("can't mix named and unnamed args in func type");
return null;
}
const arrow = assertToken(ts.shift(), parens.site)?.assertSymbol("->");
const arrow = assertDefined(ts.shift()).assertSymbol("->");
if (!arrow) {
return null;
}
const retTypes = buildFuncRetTypeExprs(arrow.site, ts, false);
const retTypes = buildFuncRetTypeExprs(arrow.site, ts, false);
if (!retTypes) {
return null;
}
return new FuncTypeExpr(parens.site, argTypes, retTypes.map(t => assertDefined(t)));
return new FuncTypeExpr(parens.site, argTypes, retTypes.map(t => assertDefined(t)));
}
}

@@ -902,3 +1292,3 @@

* @param {Token[]} ts
* @returns {FuncArgTypeExpr}
* @returns {FuncArgTypeExpr | null}
*/

@@ -909,16 +1299,33 @@ function buildFuncArgTypeExpr(ts) {

if (colonPos != -1 && colonPos != 1) {
throw ts[0].syntaxError("invalid syntax");
ts[0].syntaxError("invalid syntax");
return null;
}
const name = colonPos != -1 ? assertDefined(ts.shift()).assertWord().assertNotKeyword() : null;
/**
* @type {Word | null}
*/
let name = null;
if (colonPos != -1) {
const colon = assertDefined(ts.shift());
name = assertDefined(ts.shift()).assertWord()?.assertNotKeyword() ?? null;
if (!name) {
return null;
}
const colon = assertDefined(ts.shift()).assertSymbol(":");
if (!colon) {
return null;
}
if (ts.length == 0) {
throw colon.syntaxError("expected type expression after ':'");
colon.syntaxError("expected type expression after ':'");
return null;
}
}
const hasDefault = ts[0].isSymbol("?");
const next = assertDefined(ts[0]);
const hasDefault = next.isSymbol("?");
if (hasDefault) {

@@ -928,7 +1335,11 @@ const opt = assertDefined(ts.shift());

if (ts.length == 0) {
throw opt.syntaxError("invalid type expression after '?'");
opt.syntaxError("invalid type expression after '?'");
return null;
}
}
const typeExpr = buildTypeExpr(ts);
const typeExpr = buildTypeExpr(next.site, ts);
if (!typeExpr) {
return null;
}

@@ -943,3 +1354,3 @@ return new FuncArgTypeExpr(name !== null ? name.site : typeExpr.site, name, typeExpr, hasDefault);

* @param {boolean} allowInferredRetType
* @returns {(?TypeExpr)[]}
* @returns {null | (null | TypeExpr)[]}
*/

@@ -951,12 +1362,16 @@ function buildFuncRetTypeExprs(site, ts, allowInferredRetType = false) {

} else {
throw site.syntaxError("expected type expression after '->'");
site.syntaxError("expected type expression after '->'");
return null;
}
} else {
if (ts[0].isGroup("(") && (ts.length == 1 || !ts[1].isSymbol("->"))) {
const group = assertDefined(ts.shift()).assertGroup("(");
const group = assertToken(ts.shift(), site)?.assertGroup("(");
if (group.fields.length == 0) {
if (!group) {
return null;
} else if (group.fields.length == 0) {
return [new VoidTypeExpr(group.site)];
} else if (group.fields.length == 1) {
throw group.syntaxError("expected 0 or 2 or more types in multi return type");
group.syntaxError("expected 0 or 2 or more types in multi return type");
return null;
} else {

@@ -966,7 +1381,7 @@ return group.fields.map(fts => {

return buildTypeExpr(fts);
return buildTypeExpr(group.site, fts);
});
}
} else {
return [buildTypeExpr(ts)];
return [buildTypeExpr(site, ts)];
}

@@ -979,15 +1394,28 @@ }

* @param {Token[]} ts
* @returns {TypePathExpr}
* @returns {null | TypePathExpr}
*/
function buildTypePathExpr(ts) {
const baseName = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const baseName = assertDefined(ts.shift()).assertWord()?.assertNotKeyword();
const symbol = assertDefined(ts.shift()).assertSymbol("::");
if (!baseName) {
return null;
}
const memberName = assertDefined(ts.shift()).assertWord();
const symbol = assertToken(ts.shift(), baseName.site)?.assertSymbol("::");
if (!symbol) {
return null;
}
const memberName = assertToken(ts.shift(), symbol.site)?.assertWord();
if (!memberName) {
return null;
}
if (ts.length > 0) {
throw ts[0].syntaxError("invalid type syntax");
ts[0].syntaxError("invalid type syntax");
return null;
}
return new TypePathExpr(symbol.site, new TypeRefExpr(baseName), memberName);

@@ -999,9 +1427,14 @@ }

* @param {Token[]} ts
* @returns {TypeRefExpr}
* @returns {TypeRefExpr | null}
*/
function buildTypeRefExpr(ts) {
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const name = assertDefined(ts.shift()).assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
if (ts.length > 0) {
throw ts[0].syntaxError("invalid type syntax");
ts[0].syntaxError("invalid type syntax");
return null;
}

@@ -1016,3 +1449,3 @@

* @param {number} prec
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/

@@ -1023,3 +1456,3 @@ function buildValueExpr(ts, prec = 0) {

// lower index in exprBuilders is lower precedence
/** @type {((ts: Token[], prev: number) => ValueExpr)[]} */
/** @type {((ts: Token[], prev: number) => (ValueExpr | null))[]} */
const exprBuilders = [

@@ -1060,3 +1493,3 @@ /**

* @param {number} prec
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/

@@ -1070,3 +1503,4 @@ function buildMaybeAssignOrPrintExpr(ts, prec) {

if (equalsPos != -1) {
throw ts[equalsPos].syntaxError("invalid assignment syntax, expected ';' after '...=...'");
ts[equalsPos].syntaxError("invalid assignment syntax, expected ';' after '...=...'");
return null;
} else {

@@ -1081,7 +1515,16 @@ return buildValueExpr(ts, prec + 1);

if (ts.length == 0) {
throw site.syntaxError("expected expression after ';'");
site.syntaxError("expected expression after ';'");
return null;
} else if (upstreamExpr === null) {
// error will already have been created
return null;
} else {
const downstreamExpr = buildValueExpr(ts, prec);
return new ChainExpr(site, upstreamExpr, downstreamExpr);
if (downstreamExpr === null) {
// error will already have been created
return null;
} else {
return new ChainExpr(site, upstreamExpr, downstreamExpr);
}
}

@@ -1091,8 +1534,15 @@ } else if (equalsPos != -1 && equalsPos < semicolonPos) {

if (printPos <= semicolonPos) {
throw ts[printPos].syntaxError("expected ';' after 'print(...)'");
ts[printPos].syntaxError("expected ';' after 'print(...)'");
return null;
}
}
const equalsSite = ts[equalsPos].assertSymbol("=").site;
const equals = ts[equalsPos].assertSymbol("=");
if (!equals) {
return null;
}
const equalsSite = equals.site;
const lts = ts.splice(0, equalsPos);

@@ -1109,3 +1559,4 @@

if (upstreamTs.length == 0) {
throw equalsSite.syntaxError("expected expression between '=' and ';'");
equalsSite.syntaxError("expected expression between '=' and ';'");
return null;
}

@@ -1115,6 +1566,13 @@

const semicolonSite = assertDefined(ts.shift()).assertSymbol(";").site;
const semicolon = assertToken(ts.shift(), equalsSite)?.assertSymbol(";");
if (!semicolon) {
return null;
}
const semicolonSite = semicolon.site;
if (ts.length == 0) {
throw semicolonSite.syntaxError("expected expression after ';'");
semicolonSite.syntaxError("expected expression after ';'");
return null;
}

@@ -1124,25 +1582,49 @@

return new AssignExpr(equalsSite, lhs, upstreamExpr, downstreamExpr);
if (downstreamExpr === null || upstreamExpr === null || lhs === null) {
// error will already have been thrown internally
return null;
} else {
return new AssignExpr(equalsSite, lhs, upstreamExpr, downstreamExpr);
}
} else if (printPos != -1 && printPos < semicolonPos) {
if (equalsPos != -1) {
if (equalsPos <= semicolonPos) {
throw ts[equalsPos].syntaxError("expected ';' after '...=...'");
ts[equalsPos].syntaxError("expected ';' after '...=...'");
return null;
}
}
const printSite = assertDefined(ts.shift()).assertWord("print").site;
const print = assertDefined(ts.shift()).assertWord("print");
if (!print) {
return null;
}
const printSite = print.site;
const maybeParens = ts.shift();
if (maybeParens === undefined) {
throw ts[printPos].syntaxError("expected '(...)' after 'print'");
ts[printPos].syntaxError("expected '(...)' after 'print'");
return null;
} else {
const parens = maybeParens.assertGroup("(", 1);
if (!parens) {
return null;
}
const msgExpr = buildValueExpr(parens.fields[0]);
const semicolonSite = assertDefined(ts.shift()).assertSymbol(";").site;
const semicolon = assertToken(ts.shift(), parens.site)?.assertSymbol(";")
if (!semicolon) {
return null;
}
const semicolonSite = semicolon.site;
if (ts.length == 0) {
throw semicolonSite.syntaxError("expected expression after ';'");
semicolonSite.syntaxError("expected expression after ';'");
return null;
}

@@ -1152,6 +1634,11 @@

if (!downstreamExpr || !msgExpr) {
return null;
}
return new PrintExpr(printSite, msgExpr, downstreamExpr);
}
} else {
throw new Error("unhandled");
ts[0].syntaxError("unhandled");
return null;
}

@@ -1166,17 +1653,23 @@ }

* @param {boolean} isSwitchCase
* @returns {DestructExpr}
* @returns {DestructExpr | null}
*/
function buildDestructExpr(site, ts, isSwitchCase = false) {
if (ts.length == 0) {
throw site.syntaxError("expected token inside destructuring braces");
site.syntaxError("expected token inside destructuring braces");
return null;
}
const maybeName = assertDefined(ts.shift());
let maybeName = assertToken(ts.shift(), site);
if (!maybeName) {
return null;
}
if (maybeName.isWord("_")) {
if (ts.length != 0) {
throw maybeName.syntaxError("unexpected tokens after '_'");
maybeName.syntaxError("unexpected tokens after '_'");
return null;
} else {
return new DestructExpr(new Word(maybeName.site, "_"), null);
}
return new DestructExpr(new Word(maybeName.site, "_"), null);
} else {

@@ -1186,23 +1679,56 @@ let name = new Word(maybeName.site, "_");

if (ts.length >= 1 && ts[0].isSymbol(":")) {
name = maybeName.assertWord().assertNotKeyword();
let name_ = maybeName.assertWord()?.assertNotKeyword();
const colon = assertDefined(ts.shift()).assertSymbol(":");
if (!name_) {
return null;
}
name = name_;
const colon = assertToken(ts.shift(), name.site)?.assertSymbol(":");
if (!colon) {
return null;
}
if (ts.length == 0) {
throw colon.syntaxError("expected type expression after ':'");
} else {
const destructExprs = buildDestructExprs(ts);
const typeExpr = buildTypeExpr(ts);
return new DestructExpr(name, typeExpr, destructExprs);
colon.syntaxError("expected type expression after ':'");
return null;
}
const destructExprs = buildDestructExprs(ts);
if (destructExprs === null || destructExprs === undefined) {
return null
}
const typeExpr = buildTypeExpr(colon.site, ts);
if (!typeExpr) {
return null;
}
return new DestructExpr(name, typeExpr, destructExprs);
} else if (ts.length == 0) {
if (isSwitchCase) {
const typeExpr = new TypeRefExpr(maybeName.assertWord().assertNotKeyword());
const typeName = maybeName.assertWord()?.assertNotKeyword();
if (!typeName) {
return null;
}
const typeExpr = new TypeRefExpr(typeName);
if (!typeExpr) {
return null;
}
return new DestructExpr(name, typeExpr);
} else {
name = maybeName.assertWord().assertNotKeyword();
const name = maybeName.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
return new DestructExpr(name, null);

@@ -1214,5 +1740,13 @@ }

const destructExprs = buildDestructExprs(ts);
if (destructExprs === null || destructExprs === undefined) {
return null;
}
const typeExpr = buildTypeExpr(ts);
const typeExpr = buildTypeExpr(site, ts);
if (!typeExpr) {
return null;
}
return new DestructExpr(name, typeExpr, destructExprs);

@@ -1226,3 +1760,3 @@ }

* @param {Token[]} ts
* @returns {DestructExpr[]}
* @returns {null | DestructExpr[]}
*/

@@ -1235,2 +1769,6 @@ function buildDestructExprs(ts) {

if (!group) {
return null;
}
const destructExprs = group.fields.map(fts => {

@@ -1240,7 +1778,8 @@ return buildDestructExpr(group.site, fts);

if (destructExprs.every(le => le.isIgnored() && !le.hasDestructExprs())) {
throw group.syntaxError("expected at least one used field while destructuring")
if (destructExprs.every(le => le !== null && le.isIgnored() && !le.hasDestructExprs())) {
group.syntaxError("expected at least one used field while destructuring")
return null;
}
return destructExprs;
return reduceNull(destructExprs);
} else {

@@ -1255,3 +1794,3 @@ return [];

* @param {Token[]} ts
* @returns {DestructExpr[]}
* @returns {null | DestructExpr[]}
*/

@@ -1261,3 +1800,4 @@ function buildAssignLhs(site, ts) {

if (maybeName === undefined) {
throw site.syntaxError("expected a name before '='");
site.syntaxError("expected a name before '='");
return null;
} else {

@@ -1274,4 +1814,7 @@ /**

if (lhs.isIgnored() && !lhs.hasDestructExprs()) {
throw maybeName.syntaxError(`unused assignment ${maybeName.toString()}`);
if (lhs === null) {
return null;
} else if (lhs.isIgnored() && !lhs.hasDestructExprs()) {
maybeName.syntaxError(`unused assignment ${maybeName.toString()}`);
return null;
}

@@ -1283,4 +1826,9 @@

if (!group) {
return null;
}
if (group.fields.length < 2) {
throw group.syntaxError("expected at least 2 lhs' for multi-assign");
group.syntaxError("expected at least 2 lhs' for multi-assign");
return null;
}

@@ -1291,3 +1839,4 @@

if (fts.length == 0) {
throw group.syntaxError("unexpected empty field for multi-assign");
group.syntaxError("unexpected empty field for multi-assign");
return null;
}

@@ -1299,2 +1848,6 @@

if (!lhs) {
return null;
}
if (!lhs.isIgnored() || lhs.hasDestructExprs()) {

@@ -1307,3 +1860,3 @@ someNoneUnderscore = true;

if (!lhs.isIgnored() && p.name.value === lhs.name.value) {
throw lhs.name.syntaxError(`duplicate name '${lhs.name.value}' in lhs of multi-assign`);
lhs.name.syntaxError(`duplicate name '${lhs.name.value}' in lhs of multi-assign`);
}

@@ -1316,6 +1869,8 @@ });

if (!someNoneUnderscore) {
throw group.syntaxError("expected at least one non-underscore in lhs of multi-assign");
group.syntaxError("expected at least one non-underscore in lhs of multi-assign");
return null;
}
} else {
throw maybeName.syntaxError("unexpected syntax for lhs of =");
maybeName.syntaxError("unexpected syntax for lhs of =");
return null;
}

@@ -1330,3 +1885,3 @@

* @param {string | string[]} symbol
* @returns {(ts: Token[], prec: number) => ValueExpr}
* @returns {(ts: Token[], prec: number) => (ValueExpr | null)}
*/

@@ -1340,8 +1895,14 @@ function makeBinaryExprBuilder(symbol) {

// post-unary operator, which is invalid
throw ts[iOp].syntaxError(`invalid syntax, '${ts[iOp].toString()}' can't be used as a post-unary operator`);
ts[iOp].syntaxError(`invalid syntax, '${ts[iOp].toString()}' can't be used as a post-unary operator`);
return null;
} else if (iOp > 0) { // iOp == 0 means maybe a (pre)unary op, which is handled by a higher precedence
const a = buildValueExpr(ts.slice(0, iOp), prec);
const b = buildValueExpr(ts.slice(iOp + 1), prec + 1);
const op = ts[iOp].assertSymbol();
return new BinaryExpr(ts[iOp].assertSymbol(), a, b);
if (!a || !b || !op) {
return null;
}
return new BinaryExpr(op, a, b);
} else {

@@ -1356,3 +1917,3 @@ return buildValueExpr(ts, prec + 1);

* @param {string | string[]} symbol
* @returns {(ts: Token[], prec: number) => ValueExpr}
* @returns {(ts: Token[], prec: number) => (ValueExpr | null)}
*/

@@ -1364,4 +1925,9 @@ function makeUnaryExprBuilder(symbol) {

const rhs = buildValueExpr(ts.slice(1), prec);
const op = ts[0].assertSymbol();
return new UnaryExpr(ts[0].assertSymbol(), rhs);
if (!rhs || !op) {
return null;
}
return new UnaryExpr(op, rhs);
} else {

@@ -1377,6 +1943,6 @@ return buildValueExpr(ts, prec + 1);

* @param {number} prec
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/
function buildChainedValueExpr(ts, prec) {
/** @type {ValueExpr} */
/** @type {ValueExpr | null} */
let expr = buildChainStartValueExpr(ts);

@@ -1386,20 +1952,32 @@

while (ts.length > 0) {
if (expr === null) {
return null;
}
const t = assertDefined(ts.shift());
if (t.isGroup("(")) {
expr = buildCallExpr(t.site, expr, t.assertGroup());
expr = buildCallExpr(t.site, expr, assertDefined(t.assertGroup()));
} else if (t.isGroup("[")) {
throw t.syntaxError("invalid expression '[...]'");
t.syntaxError("invalid expression '[...]'");
return null;
} else if (t.isSymbol(".") && ts.length > 0 && ts[0].isWord("switch")) {
expr = buildSwitchExpr(expr, ts);
} else if (t.isSymbol(".")) {
const name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const name = assertToken(ts.shift(), t.site)?.assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
expr = new MemberExpr(t.site, expr, name);
} else if (t.isGroup("{")) {
throw t.syntaxError("invalid syntax");
t.syntaxError("invalid syntax");
return null;
} else if (t.isSymbol("::")) {
throw t.syntaxError("invalid syntax");
t.syntaxError("invalid syntax");
return null;
} else {
throw t.syntaxError(`invalid syntax '${t.toString()}'`);
t.syntaxError(`invalid syntax '${t.toString()}'`);
return null;
}

@@ -1415,3 +1993,3 @@ }

* @param {Group} parens
* @returns {CallExpr}
* @returns {CallExpr | null}
*/

@@ -1421,3 +1999,7 @@ function buildCallExpr(site, fnExpr, parens) {

return new CallExpr(site, fnExpr, callArgs);
if (callArgs === null) {
return null;
} else {
return new CallExpr(site, fnExpr, callArgs);
}
}

@@ -1428,3 +2010,3 @@

* @param {Token[]} ts
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/

@@ -1437,3 +2019,4 @@ function buildChainStartValueExpr(ts) {

} else if (ts[0].isWord("switch")) {
throw ts[0].syntaxError("expected '... .switch' instead of 'switch'");
ts[0].syntaxError("expected '... .switch' instead of 'switch'");
return null;
} else if (ts[0].isLiteral()) {

@@ -1464,11 +2047,22 @@ return new PrimitiveLiteralExpr(assertDefined(ts.shift())); // can simply be reused

if (ts[0].isWord("const") || ts[0].isWord("struct") || ts[0].isWord("enum") || ts[0].isWord("func") || ts[0].isWord("import")) {
throw ts[0].syntaxError(`invalid use of '${ts[0].assertWord().value}', can only be used as top-level statement`);
ts[0].syntaxError(`invalid use of '${assertDefined(ts[0].assertWord()).value}', can only be used as top-level statement`);
return null;
} else {
const name = assertDefined(ts.shift()).assertWord();
const name = assertDefined(ts.shift()?.assertWord());
// only place where a word can be "self"
return new ValueRefExpr(name.value == "self" ? name : name.assertNotKeyword());
if (name.value == "self") {
return new ValueRefExpr(name);
} else {
const n = name.assertNotKeyword();
if (!n) {
return null;
}
return new ValueRefExpr(n);
}
}
} else {
throw ts[0].syntaxError("invalid syntax");
ts[0].syntaxError("invalid syntax");
return null;
}

@@ -1480,12 +2074,36 @@ }

* @param {Token[]} ts
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/
function buildParensExpr(ts) {
const group = assertDefined(ts.shift()).assertGroup("(");
if (!group) {
return null;
}
const site = group.site;
if (group.fields.length === 0) {
throw group.syntaxError("expected at least one expr in parens");
group.syntaxError("expected at least one expr in parens");
return null;
} else {
return new ParensExpr(site, group.fields.map(fts => buildValueExpr(fts)));
const fields = group.fields.map(fts => buildValueExpr(fts));
/**
* @type {ValueExpr[]}
*/
const nonNullFields = [];
fields.forEach(f => {
if (f !== null) {
nonNullFields.push(f);
}
});
if (nonNullFields.length == 0) {
// error will already have been thrown internally
return null;
} else {
return new ParensExpr(site, nonNullFields);
}
}

@@ -1497,3 +2115,3 @@ }

* @param {Group} parens
* @returns {CallArgExpr[]}
* @returns {CallArgExpr[] | null}
*/

@@ -1506,21 +2124,26 @@ function buildCallArgs(parens) {

const callArgs = parens.fields.map(fts => {
const callArgs = reduceNull(parens.fields.map(fts => {
const callArg = buildCallArgExpr(parens.site, fts);
if (callArg.isNamed()) {
if (callArg !== null && callArg.isNamed()) {
if (names.has(callArg.name)) {
throw callArg.syntaxError(`duplicate named call arg ${callArg.name}`);
} else {
names.add(callArg.name);
callArg.syntaxError(`duplicate named call arg ${callArg.name}`);
}
names.add(callArg.name);
}
return callArg;
});
}));
if (callArgs.some(ca => ca.isNamed()) && callArgs.some(ca => !ca.isNamed())) {
throw callArgs[0].syntaxError("can't mix positional and named args");
if (callArgs === null) {
return null;
} else {
if (callArgs.some(ca => ca.isNamed()) && callArgs.some(ca => !ca.isNamed())) {
callArgs[0].syntaxError("can't mix positional and named args");
return null;
}
return callArgs;
}
return callArgs;
}

@@ -1531,11 +2154,12 @@

* @param {Token[]} ts
* @returns {CallArgExpr}
* @returns {CallArgExpr | null}
*/
function buildCallArgExpr(site, ts) {
if (ts.length == 0) {
throw site.syntaxError("invalid syntax");
site.syntaxError("invalid syntax");
return null;
}
/**
* @type {null | Word}
* @type {null | undefined | Word}
*/

@@ -1545,8 +2169,13 @@ let name = null;

if (ts.length >= 2 && ts[0].isWord() && ts[1].isSymbol(":")) {
name = assertDefined(ts.shift()).assertWord().assertNotKeyword();
name = assertDefined(ts.shift()).assertWord()?.assertNotKeyword();
if (!name) {
return null;
}
const colon = assertDefined(ts.shift());
if (ts.length == 0) {
throw colon.syntaxError("expected value expressions after ':'");
colon.syntaxError("expected value expressions after ':'");
return null;
}

@@ -1557,2 +2186,6 @@ }

if (!value) {
return null;
}
return new CallArgExpr(name != null ? name.site : value.site, name, value);

@@ -1564,7 +2197,13 @@ }

* @param {Token[]} ts
* @returns {IfElseExpr}
* @returns {IfElseExpr | null}
*/
function buildIfElseExpr(ts) {
const site = assertDefined(ts.shift()).assertWord("if").site;
const ifWord = assertDefined(ts.shift()).assertWord("if");
if (!ifWord) {
return null;
}
const site = ifWord.site;
/** @type {ValueExpr[]} */

@@ -1576,18 +2215,37 @@ const conditions = [];

while (true) {
const parens = assertDefined(ts.shift()).assertGroup("(");
const braces = assertDefined(ts.shift()).assertGroup("{");
const parens = assertToken(ts.shift(), site)?.assertGroup("(");
if (!parens) {
return null;
}
const braces = assertToken(ts.shift(), site)?.assertGroup("{");
if (!braces) {
return null;
}
if (parens.fields.length != 1) {
throw parens.syntaxError("expected single if-else condition");
parens.syntaxError("expected single if-else condition");
return null;
}
if (braces.fields.length == 0) {
throw braces.syntaxError("branch body can't be empty");
braces.syntaxError("branch body can't be empty");
return null;
} else if (braces.fields.length != 1) {
throw braces.syntaxError("expected single if-else branch expession");
braces.syntaxError("expected single if-else branch expession");
return null;
}
conditions.push(buildValueExpr(parens.fields[0]));
branches.push(buildValueExpr(braces.fields[0]));
const cond = buildValueExpr(parens.fields[0]);
const branch = buildValueExpr(braces.fields[0]);
if (cond === null || branch === null) {
continue;
}
conditions.push(cond);
branches.push(branch);
const maybeElse = ts.shift();

@@ -1606,6 +2264,19 @@

const braces = next.assertGroup();
if (!braces) {
return null;
}
if (braces.fields.length != 1) {
throw braces.syntaxError("expected single expession for if-else branch");
braces.syntaxError("expected single expession for if-else branch");
return null;
}
branches.push(buildValueExpr(braces.fields[0]));
const elseBranch = buildValueExpr(braces.fields[0]);
if (elseBranch === null) {
return null;
} else {
branches.push(elseBranch);
}
break;

@@ -1615,3 +2286,4 @@ } else if (next.isWord("if")) {

} else {
throw next.syntaxError("unexpected token");
next.syntaxError("unexpected token");
return null;
}

@@ -1628,13 +2300,23 @@ }

* @param {Token[]} ts
* @returns {ValueExpr} - EnumSwitchExpr or DataSwitchExpr
* @returns {ValueExpr | null} - EnumSwitchExpr or DataSwitchExpr
*/
function buildSwitchExpr(controlExpr, ts) {
const site = assertDefined(ts.shift()).assertWord("switch").site;
const switchWord = assertDefined(ts.shift()).assertWord("switch");
const braces = assertDefined(ts.shift()).assertGroup("{");
if (!switchWord) {
return null;
}
const site = switchWord.site;
const braces = assertToken(ts.shift(), site)?.assertGroup("{");
if (!braces) {
return null;
}
/** @type {SwitchCase[]} */
const cases = [];
/** @type {?SwitchDefault} */
/** @type {null | SwitchDefault} */
let def = null;

@@ -1645,3 +2327,4 @@

if (def !== null) {
throw def.syntaxError("duplicate 'else' in switch");
def.syntaxError("duplicate 'else' in switch");
return null;
}

@@ -1652,6 +2335,13 @@

if (def !== null) {
throw def.syntaxError("switch 'else' must come last");
def.syntaxError("switch 'else' must come last");
return null;
}
cases.push(buildSwitchCase(tsInner));
const c = buildSwitchCase(tsInner);
if (c === null) {
return null;
} else {
cases.push(c);
}
}

@@ -1666,3 +2356,4 @@ }

if (set.has(t)) {
throw c.memberName.syntaxError(`duplicate switch case '${t}')`);
c.memberName.syntaxError(`duplicate switch case '${t}')`);
return null;
}

@@ -1674,3 +2365,4 @@

if (cases.length < 1) {
throw site.syntaxError("expected at least one switch case");
site.syntaxError("expected at least one switch case");
return null;
}

@@ -1680,3 +2372,4 @@

if (cases.length + (def === null ? 0 : 1) > 5) {
throw site.syntaxError(`too many cases for data switch, expected 5 or less, got ${cases.length.toString()}`);
site.syntaxError(`too many cases for data switch, expected 5 or less, got ${cases.length.toString()}`);
return null;
} else {

@@ -1687,6 +2380,8 @@ let count = 0;

if (count > 1) {
throw site.syntaxError(`expected at most 1 enum case in data switch, got ${count}`);
site.syntaxError(`expected at most 1 enum case in data switch, got ${count}`);
return null;
} else {
if (count === 1 && cases.some(c => c instanceof UnconstrDataSwitchCase)) {
throw site.syntaxError(`can't have both enum and (Int, []Data) in data switch`);
site.syntaxError(`can't have both enum and (Int, []Data) in data switch`);
return null;
} else {

@@ -1707,3 +2402,3 @@ return new DataSwitchExpr(site, controlExpr, cases, def);

* @param {boolean} isAfterColon
* @returns {Word}
* @returns {Word | null}
*/

@@ -1715,5 +2410,7 @@ function buildSwitchCaseName(site, ts, isAfterColon) {

if (isAfterColon) {
throw site.syntaxError("invalid switch case syntax, expected member name after ':'");
site.syntaxError("invalid switch case syntax, expected member name after ':'");
return null;
} else {
throw site.syntaxError("invalid switch case syntax");
site.syntaxError("invalid switch case syntax");
return null;
}

@@ -1725,12 +2422,18 @@ }

if (second === undefined) {
throw site.syntaxError("expected token after 'Map'");
if (!second) {
site.syntaxError("expected token after 'Map'");
return null;
}
const keyTs = second.assertGroup("[]", 1).fields[0];
const keyTs = second.assertGroup("[]", 1)?.fields[0];
if (keyTs === undefined || keyTs === null) {
return null;
}
const key = keyTs.shift();
if (key === undefined) {
throw second.syntaxError("expected 'Map[Data]Data'");
second.syntaxError("expected 'Map[Data]Data'");
return null;
}

@@ -1741,3 +2444,4 @@

if (keyTs.length > 0) {
throw keyTs[0].syntaxError("unexpected token after 'Data'");
keyTs[0].syntaxError("unexpected token after 'Data'");
return null;
}

@@ -1748,3 +2452,4 @@

if (third === undefined) {
throw site.syntaxError("expected token after 'Map[Data]")
site.syntaxError("expected token after 'Map[Data]");
return null;
}

@@ -1755,3 +2460,4 @@

if (ts.length > 0) {
throw ts[0].syntaxError("unexpected token after 'Map[Data]Data'");
ts[0].syntaxError("unexpected token after 'Map[Data]Data'");
return null;
}

@@ -1762,6 +2468,7 @@

if (ts.length > 0) {
throw ts[0].syntaxError("unexpected token");
ts[0].syntaxError("unexpected token");
return null;
}
return first.assertWord().assertNotKeyword();
return first?.assertWord()?.assertNotKeyword() ?? null;
} else if (first.isGroup("[")) {

@@ -1774,5 +2481,7 @@ // list

if (second === undefined) {
throw site.syntaxError("expected token after '[]'");
site.syntaxError("expected token after '[]'");
return null;
} else if (ts.length > 0) {
throw ts[0].syntaxError("unexpected token");
ts[0].syntaxError("unexpected token");
return null;
}

@@ -1784,3 +2493,4 @@

} else {
throw first.syntaxError("invalid switch case name syntax");
first.syntaxError("invalid switch case name syntax");
return null;
}

@@ -1792,3 +2502,3 @@ }

* @param {Token[]} ts
* @returns {SwitchCase}
* @returns {SwitchCase | null}
*/

@@ -1799,5 +2509,7 @@ function buildSwitchCase(ts) {

if (arrowPos == -1) {
throw ts[0].syntaxError("expected '=>' in switch case");
ts[0].syntaxError("expected '=>' in switch case");
return null;
} else if (arrowPos == 0) {
throw ts[0].syntaxError("expected '<word>' or '<word>: <word>' to the left of '=>'");
ts[0].syntaxError("expected '<word>' or '<word>: <word>' to the left of '=>'");
return null;
}

@@ -1817,3 +2529,3 @@

* @param {Token[]} ts
* @returns {[?Word, Word]} - varName is optional
* @returns {null | [?Word, Word]} - varName is optional
*/

@@ -1823,14 +2535,22 @@ function buildSwitchCaseNameType(ts) {

/** @type {?Word} */
/** @type {null | Word} */
let varName = null;
/** @type {?Word} */
/** @type {null | Word} */
let memberName = null;
if (colonPos != -1) {
varName = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const maybeVarName = assertDefined(ts.shift()).assertWord()?.assertNotKeyword();
if (!maybeVarName) {
return null;
}
varName = maybeVarName;
const maybeColon = ts.shift();
if (maybeColon === undefined) {
throw varName.syntaxError("invalid switch case syntax, expected '(<name>: <enum-member>)', got '(<name>)'");
varName.syntaxError("invalid switch case syntax, expected '(<name>: <enum-member>)', got '(<name>)'");
return null;
} else {

@@ -1846,7 +2566,9 @@ void maybeColon.assertSymbol(":");

if (ts.length !== 0) {
throw new Error("unexpected");
ts[0].syntaxError("unexpected token");
return null;
}
if (memberName === null) {
throw new Error("unexpected");
// error will already have been thrown internally
return null;
} else {

@@ -1861,3 +2583,3 @@ return [varName, memberName];

* @param {Token[]} ts
* @returns {SwitchCase}
* @returns {SwitchCase | null}
*/

@@ -1867,10 +2589,20 @@ function buildMultiArgSwitchCase(tsLeft, ts) {

const pairs = parens.fields.map(fts => buildSwitchCaseNameType(fts));
if (!parens) {
return null;
}
const pairs = reduceNull(parens.fields.map(fts => buildSwitchCaseNameType(fts)));
if (pairs === null) {
return null;
}
assert(tsLeft.length === 0);
if (pairs.length !== 2) {
throw parens.syntaxError(`expected (Int, []Data) case, got (${pairs.map(p => p[1].value).join(", ")}`);
parens.syntaxError(`expected (Int, []Data) case, got (${pairs.map(p => p[1].value).join(", ")}`);
return null;
} else if (pairs[0][1].value != "Int" || pairs[1][1].value != "[]Data") {
throw parens.syntaxError(`expected (Int, []Data) case, got (${pairs[0][1].value}, ${pairs[1][1].value})`);
parens.syntaxError(`expected (Int, []Data) case, got (${pairs[0][1].value}, ${pairs[1][1].value})`);
return null;
} else {

@@ -1880,9 +2612,18 @@ const maybeArrow = ts.shift();

if (maybeArrow === undefined) {
throw parens.syntaxError("expected '=>'");
parens.syntaxError("expected '=>'");
return null;
} else {
const arrow = maybeArrow.assertSymbol("=>");
if (!arrow) {
return null;
}
const bodyExpr = buildSwitchCaseBody(arrow.site, ts);
return new UnconstrDataSwitchCase(arrow.site, pairs[0][0], pairs[1][0], bodyExpr);
if (bodyExpr === null) {
return null;
} else {
return new UnconstrDataSwitchCase(arrow.site, pairs[0][0], pairs[1][0], bodyExpr);
}
}

@@ -1896,3 +2637,3 @@ }

* @param {Token[]} ts
* @returns {SwitchCase}
* @returns {SwitchCase | null}
*/

@@ -1904,4 +2645,7 @@ function buildSingleArgSwitchCase(tsLeft, ts) {

if (!destructExpr.hasType()) {
throw destructExpr.site.syntaxError("invalid switch case syntax");
if (destructExpr === null) {
return null;
} else if (!destructExpr.hasType()) {
destructExpr.site.syntaxError("invalid switch case syntax");
return null;
}

@@ -1912,9 +2656,18 @@

if (maybeArrow === undefined) {
throw site.syntaxError("expected '=>'");
site.syntaxError("expected '=>'");
return null;
} else {
const arrow = maybeArrow.assertSymbol("=>");
if (!arrow) {
return null;
}
const bodyExpr = buildSwitchCaseBody(arrow.site, ts);
return new SwitchCase(arrow.site, destructExpr, bodyExpr);
if (bodyExpr === null) {
return null;
} else {
return new SwitchCase(arrow.site, destructExpr, bodyExpr);
}
}

@@ -1927,3 +2680,3 @@ }

* @param {Token[]} ts
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/

@@ -1935,9 +2688,16 @@ function buildSwitchCaseBody(site, ts) {

if (ts.length == 0) {
throw site.syntaxError("expected expression after '=>'");
site.syntaxError("expected expression after '=>'");
return null;
} else if (ts[0].isGroup("{")) {
if (ts.length > 1) {
throw ts[1].syntaxError("unexpected token");
ts[1].syntaxError("unexpected token");
return null;
}
const tsBody = ts[0].assertGroup("{", 1).fields[0];
const tsBody = ts[0].assertGroup("{", 1)?.fields[0];
if (tsBody === undefined || tsBody === null) {
return null;
}
bodyExpr = buildValueExpr(tsBody);

@@ -1948,7 +2708,3 @@ } else {

if (bodyExpr === null) {
throw site.syntaxError("empty switch case body");
} else {
return bodyExpr;
}
return bodyExpr;
}

@@ -1959,22 +2715,42 @@

* @param {Token[]} ts
* @returns {SwitchDefault}
* @returns {SwitchDefault | null}
*/
function buildSwitchDefault(ts) {
const site = assertDefined(ts.shift()).assertWord("else").site;
const elseWord = assertDefined(ts.shift()).assertWord("else");
if (!elseWord) {
return null;
}
const site = elseWord.site;
const maybeArrow = ts.shift();
if (maybeArrow === undefined) {
throw site.syntaxError("expected '=>' after 'else'");
site.syntaxError("expected '=>' after 'else'");
return null;
} else {
const arrow = maybeArrow.assertSymbol("=>");
/** @type {?ValueExpr} */
if (!arrow) {
return null;
}
/** @type {null | ValueExpr} */
let bodyExpr = null;
if (ts.length == 0) {
throw arrow.syntaxError("expected expression after '=>'");
arrow.syntaxError("expected expression after '=>'");
return null;
} else if (ts[0].isGroup("{")) {
if (ts.length > 1) {
throw ts[1].syntaxError("unexpected token");
ts[1].syntaxError("unexpected token");
return null;
} else {
bodyExpr = buildValueExpr(ts[0].assertGroup("{", 1).fields[0]);
const bodyTs = ts[0].assertGroup("{", 1)?.fields[0];
if (bodyTs === undefined || bodyTs === null) {
return null;
}
bodyExpr = buildValueExpr(bodyTs);
}

@@ -1986,3 +2762,4 @@ } else {

if (bodyExpr === null) {
throw arrow.syntaxError("empty else body");
arrow.syntaxError("empty else body");
return null;
} else {

@@ -1997,19 +2774,39 @@ return new SwitchDefault(arrow.site, bodyExpr);

* @param {Token[]} ts
* @returns {ListLiteralExpr}
* @returns {ListLiteralExpr | null}
*/
function buildListLiteralExpr(ts) {
const site = assertDefined(ts.shift()).assertGroup("[", 0).site;
const group = assertDefined(ts.shift()).assertGroup("[", 0);
if (!group) {
return null;
}
const site = group.site;
const bracesPos = Group.find(ts, "{");
if (bracesPos == -1) {
throw site.syntaxError("invalid list literal expression syntax");
site.syntaxError("invalid list literal expression syntax");
return null;
}
const itemTypeExpr = buildTypeExpr(ts.splice(0, bracesPos));
const itemTypeExpr = buildTypeExpr(site, ts.splice(0, bracesPos));
const braces = assertDefined(ts.shift()).assertGroup("{");
if (!itemTypeExpr) {
return null;
}
const itemExprs = braces.fields.map(fts => buildValueExpr(fts));
const braces = assertToken(ts.shift(), site)?.assertGroup("{");
if (!braces) {
return null;
}
const itemExprs = reduceNull(braces.fields.map(fts => buildValueExpr(fts)));
if (itemExprs === null) {
// error will have already been thrown internally
return null;
}
return new ListLiteralExpr(site, itemTypeExpr, itemExprs);

@@ -2021,25 +2818,48 @@ }

* @param {Token[]} ts
* @returns {MapLiteralExpr}
* @returns {MapLiteralExpr | null}
*/
function buildMapLiteralExpr(ts) {
const site = assertDefined(ts.shift()).assertWord("Map").site;
const mapWord = assertDefined(ts.shift()).assertWord("Map");
if (!mapWord) {
return null;
}
const site = mapWord.site;
const bracket = assertDefined(ts.shift()).assertGroup("[", 1);
const keyTypeExpr = buildTypeExpr(bracket.fields[0]);
if (!bracket) {
return null;
}
const keyTypeExpr = buildTypeExpr(site, bracket.fields[0]);
if (!keyTypeExpr) {
return null;
}
const bracesPos = Group.find(ts, "{");
if (bracesPos == -1) {
throw site.syntaxError("invalid map literal expression syntax");
site.syntaxError("invalid map literal expression syntax");
return null;
}
const valueTypeExpr = buildTypeExpr(ts.splice(0, bracesPos));
const valueTypeExpr = buildTypeExpr(site, ts.splice(0, bracesPos));
if (!valueTypeExpr) {
return null;
}
const braces = assertDefined(ts.shift()).assertGroup("{");
if (!braces) {
return null;
}
/**
* @type {[ValueExpr, ValueExpr][]}
* @type {null | [ValueExpr, ValueExpr][]}
*/
const pairs = braces.fields.map(fts => {
const pairs = reduceNullPairs(braces.fields.map(fts => {
const colonPos = SymbolToken.find(fts, ":");

@@ -2049,22 +2869,28 @@

if (fts.length == 0) {
throw braces.syntaxError("unexpected empty field");
braces.syntaxError("unexpected empty field");
} else {
throw fts[0].syntaxError("expected ':' in map literal field");
fts[0].syntaxError("expected ':' in map literal field");
}
} else if (colonPos == 0) {
throw fts[colonPos].syntaxError("expected expression before ':' in map literal field");
fts[colonPos].syntaxError("expected expression before ':' in map literal field");
} else if (colonPos == fts.length - 1) {
throw fts[colonPos].syntaxError("expected expression after ':' in map literal field");
fts[colonPos].syntaxError("expected expression after ':' in map literal field");
} else {
const keyExpr = buildValueExpr(fts.slice(0, colonPos));
const valueExpr = buildValueExpr(fts.slice(colonPos+1));
/**
* @type {[ValueExpr | null, ValueExpr | null]}
*/
return [keyExpr, valueExpr];
}
const keyExpr = buildValueExpr(fts.slice(0, colonPos));
return [null, null];
}));
const valueExpr = buildValueExpr(fts.slice(colonPos+1));
if (pairs === null) {
return null;
}
/**
* @type {[ValueExpr, ValueExpr]}
*/
return [keyExpr, valueExpr];
});
return new MapLiteralExpr(site, keyTypeExpr, valueTypeExpr, pairs);

@@ -2076,3 +2902,3 @@ }

* @param {Token[]} ts
* @returns {StructLiteralExpr}
* @returns {StructLiteralExpr | null}
*/

@@ -2084,16 +2910,32 @@ function buildStructLiteralExpr(ts) {

const site = ts[bracesPos].site;
if (bracesPos == 0) {
throw ts[bracesPos].syntaxError("expected struct type before braces");
site.syntaxError("expected struct type before braces");
return null;
}
const typeExpr = buildTypeExpr(ts.splice(0, bracesPos));
const typeExpr = buildTypeExpr(site, ts.splice(0, bracesPos));
if (!typeExpr) {
return null;
}
const braces = assertDefined(ts.shift()).assertGroup("{");
const fields = braces.fields.map(fts => buildStructLiteralField(braces.site, fts));
if (!braces) {
return null;
}
const fields = reduceNull(braces.fields.map(fts => buildStructLiteralField(braces.site, fts)));
if (fields === null) {
return null;
}
if (fields.every(f => f.isNamed()) || fields.every(f => !f.isNamed())) {
return new StructLiteralExpr(typeExpr, fields);
} else {
throw braces.site.syntaxError("mangled literal struct (hint: specify all fields positionally or all with keys)");
braces.site.syntaxError("mangled literal struct (hint: specify all fields positionally or all with keys)");
return null;
}

@@ -2104,34 +2946,44 @@ }

* @package
* @param {Site} bracesSite
* @param {Site} site - site of the braces
* @param {Token[]} ts
* @returns {StructLiteralField}
* @returns {StructLiteralField | null}
*/
function buildStructLiteralField(bracesSite, ts) {
function buildStructLiteralField(site, ts) {
if (ts.length > 2 && ts[0].isWord() && ts[1].isSymbol(":")) {
const maybeName = ts.shift();
if (maybeName === undefined) {
throw bracesSite.syntaxError("empty struct literal field");
} else {
const name = maybeName.assertWord();
return buildStructLiteralNamedField(site, ts);
} else {
return buildStructLiteralUnnamedField(site, ts);
}
}
const maybeColon = ts.shift();
if (maybeColon === undefined) {
throw bracesSite.syntaxError("expected ':'");
} else {
const colon = maybeColon.assertSymbol(":");
/**
* @package
* @param {Site} site
* @param {Token[]} ts
* @returns {StructLiteralField | null}
*/
function buildStructLiteralNamedField(site, ts) {
const name = assertToken(ts.shift(), site, "empty struct literal field")?.assertWord()?.assertNotKeyword();
if (ts.length == 0) {
throw colon.syntaxError("expected expression after ':'");
} else {
const valueExpr = buildValueExpr(ts);
if (!name) {
return null;
}
return new StructLiteralField(name.assertNotKeyword(), valueExpr);
}
}
}
} else {
const valueExpr = buildValueExpr(ts);
const colon = assertToken(ts.shift(), name.site, "expected ':' after struct field name")?.assertSymbol(":");
return new StructLiteralField(null, valueExpr);
if (!colon) {
return null;
}
if (ts.length == 0) {
colon.syntaxError("expected expression after ':'");
return null;
}
const valueExpr = buildValueExpr(ts);
if (!valueExpr) {
return null;
}
return new StructLiteralField(name, valueExpr);
}

@@ -2141,4 +2993,20 @@

* @package
* @param {Site} site
* @param {Token[]} ts
* @returns {StructLiteralField | null}
*/
function buildStructLiteralUnnamedField(site, ts) {
const valueExpr = buildValueExpr(ts);
if (!valueExpr) {
return null;
}
return new StructLiteralField(null, valueExpr);
}
/**
* @package
* @param {Token[]} ts
* @returns {ValueExpr}
* @returns {ValueExpr | null}
*/

@@ -2150,9 +3018,17 @@ function buildValuePathExpr(ts) {

const typeExpr = buildTypeExpr(ts.splice(0, dcolonPos));
const typeExpr = buildTypeExpr(ts[dcolonPos].site, ts.splice(0, dcolonPos));
assertDefined(ts.shift()).assertSymbol("::");
if (!typeExpr) {
return null;
}
const memberName = assertDefined(ts.shift()).assertWord().assertNotKeyword();
const dcolon = assertDefined(ts.shift()?.assertSymbol("::"));
const memberName = assertToken(ts.shift(), dcolon.site)?.assertWord()?.assertNotKeyword();
if (!memberName) {
return null;
}
return new ValuePathExpr(typeExpr, memberName);
}

@@ -137,5 +137,5 @@ //@ts-check

if (this.site.endSite === null) {
mask.fill(0, this.site.pos);
mask.fill(0, this.site.startPos);
} else {
mask.fill(0, this.site.pos, this.site.endSite.pos);
mask.fill(0, this.site.startPos, this.site.endSite.startPos);
}

@@ -1355,5 +1355,5 @@ }

if (site.endSite === null) {
mask.fill(0, site.pos);
mask.fill(0, site.startPos);
} else {
mask.fill(0, site.pos, site.endSite.pos);
mask.fill(0, site.startPos, site.endSite.startPos);
}

@@ -1360,0 +1360,0 @@ }

@@ -71,2 +71,3 @@ //@ts-check

import {
buildScript,
buildProgramStatements,

@@ -109,21 +110,25 @@ buildScriptPurpose

static new(rawSrc, fileIndex = null) {
let src = new Source(rawSrc, fileIndex);
const src = new Source(rawSrc, fileIndex);
let ts = tokenize(src);
const ts = tokenize(src);
src.throwErrors();
if (ts === null) {
throw new Error("should've been thrown above");
}
if (ts.length == 0) {
throw UserError.syntaxError(src, 0, "empty script");
throw UserError.syntaxError(src, 0, 1, "empty script");
}
let [purpose, name] = buildScriptPurpose(ts);
const [purpose, name, statements, mainIdx] = buildScript(ts, ScriptPurpose.Module);
if (purpose != ScriptPurpose.Module) {
throw name.syntaxError("expected 'module' script purpose");
} else if (name.value == "main") {
throw name.syntaxError("name of 'module' can't be 'main'");
src.throwErrors();
if (name !== null) {
return new Module(name, statements);
} else {
throw new Error("unexpected"); // should've been caught by calling src.throwErrors() above
}
let statements = buildProgramStatements(ts);
return new Module(name, statements);
}

@@ -326,34 +331,34 @@

static parseMain(rawSrc) {
let src = new Source(rawSrc, 0);
const src = new Source(rawSrc, 0);
let ts = tokenize(src);
const ts = tokenize(src);
if (ts.length == 0) {
throw UserError.syntaxError(src, 0, "empty script");
src.throwErrors();
if (ts === null) {
throw new Error("should've been thrown above");
}
let [purpose, name] = buildScriptPurpose(ts);
if (name.value === "main") {
throw name.site.syntaxError("script can't be named 'main'");
if (ts.length == 0) {
throw UserError.syntaxError(src, 0, 1, "empty script");
}
let statements = buildProgramStatements(ts);
const [purpose, name, statements, mainIdx] = buildScript(ts);
let mainIdx = statements.findIndex(s => s.name.value === "main");
src.throwErrors();
if (mainIdx == -1) {
throw name.site.syntaxError("'main' not found");
}
if (purpose !== null && name !== null) {
/**
* @type {Module[]}
*/
const modules = [new MainModule(name, statements.slice(0, mainIdx+1))];
/**
* @type {Module[]}
*/
let modules = [new MainModule(name, statements.slice(0, mainIdx+1))];
if (mainIdx < statements.length - 1) {
modules.push(new Module(name, statements.slice(mainIdx+1)));
}
if (mainIdx < statements.length - 1) {
modules.push(new Module(name, statements.slice(mainIdx+1)));
return [purpose, modules];
} else {
throw new Error("unexpected"); // should've been caught by calling src.throwErrors() above
}
return [purpose, modules];
}

@@ -360,0 +365,0 @@

@@ -96,3 +96,3 @@ //@ts-check

} else {
data[prevGroup.site.pos] = SyntaxCategory.Error;
data[prevGroup.site.startPos] = SyntaxCategory.Error;
data[j++] = SyntaxCategory.Error;

@@ -330,3 +330,3 @@ }

for (let s of groupStack) {
data[s.site.pos] = SyntaxCategory.Error;
data[s.site.startPos] = SyntaxCategory.Error;
}

@@ -333,0 +333,0 @@

@@ -8,3 +8,4 @@ //@ts-check

assertDefined,
hexToBytes
hexToBytes,
reduceNull
} from "./utils.js";

@@ -25,3 +26,4 @@

Token,
Word
Word,
assertToken
} from "./tokens.js";

@@ -77,3 +79,3 @@

if (pair[0] == t.site.pos) {
if (pair[0] == t.site.startPos) {
t.site.setCodeMapSite(pair[1]);

@@ -132,3 +134,3 @@ this.#codeMapPos += 1;

} else if (!(c == ' ' || c == '\n' || c == '\t' || c == '\r')) {
throw site.syntaxError(`invalid source character '${c}' (utf-8 not yet supported outside string literals)`);
site.syntaxError(`invalid source character '${c}' (utf-8 not yet supported outside string literals)`);
}

@@ -140,3 +142,3 @@ }

* Nests groups before returning a list of tokens
* @returns {Token[]}
* @returns {Token[] | null}
*/

@@ -157,3 +159,3 @@ tokenize() {

return Tokenizer.nestGroups(this.#ts);
return this.nestGroups(this.#ts);
}

@@ -213,5 +215,15 @@

if (value == "true" || value == "false") {
this.pushToken(new BoolLiteral(site, value == "true"));
this.pushToken(
new BoolLiteral(
new Site(site.src, site.startPos, this.currentSite.startPos),
value == "true"
)
);
} else {
this.pushToken(new Word(site, value));
this.pushToken(
new Word(
new Site(site.src, site.startPos, this.currentSite.startPos),
value
)
);
}

@@ -267,3 +279,5 @@ }

} else if (c == '\0') {
throw site.syntaxError("unterminated multiline comment");
const errorSite = new Site(site.src, site.startPos, this.currentSite.startPos);
errorSite.syntaxError("unterminated multiline comment");
return;
}

@@ -289,3 +303,3 @@ }

} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
throw site.syntaxError(`bad literal integer type 0${c}`);
site.syntaxError(`bad literal integer type 0${c}`);
} else if (c >= '0' && c <= '9') {

@@ -332,5 +346,9 @@ this.readDecimalInteger(site, c);

chars.push(c);
} else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
throw site.syntaxError("invalid syntax for decimal integer literal");
} else {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
const errorSite = new Site(site.src, site.startPos, this.currentSite.startPos);
errorSite.syntaxError("invalid syntax for decimal integer literal");
}
this.unreadChar();

@@ -343,3 +361,8 @@ break;

this.pushToken(new IntLiteral(site, BigInt(chars.join(''))));
this.pushToken(
new IntLiteral(
new Site(site.src, site.startPos, this.currentSite.startPos),
BigInt(chars.join(''))
)
);
}

@@ -358,3 +381,8 @@

if (!(valid(c))) {
throw site.syntaxError(`expected at least one char for ${prefix} integer literal`);
const errorSite = new Site(site.src, site.startPos, this.currentSite.startPos);
errorSite.syntaxError(`expected at least one char for ${prefix} integer literal`);
this.unreadChar();
return;
}

@@ -365,5 +393,9 @@

chars.push(c);
} else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
throw site.syntaxError(`invalid syntax for ${prefix} integer literal`);
} else {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
const errorSite = new Site(site.src, site.startPos, this.currentSite.startPos);
errorSite.syntaxError(`invalid syntax for ${prefix} integer literal`);
}
this.unreadChar();

@@ -376,3 +408,8 @@ break;

this.pushToken(new IntLiteral(site, BigInt(prefix + chars.join(''))));
this.pushToken(
new IntLiteral(
new Site(site.src, site.startPos, this.currentSite.startPos),
BigInt(prefix + chars.join(''))
)
);
}

@@ -402,3 +439,8 @@

this.pushToken(new ByteArrayLiteral(site, bytes));
this.pushToken(
new ByteArrayLiteral(
new Site(site.src, site.startPos, this.currentSite.startPos),
bytes
)
);
}

@@ -422,3 +464,4 @@

if (c == '\0') {
throw site.syntaxError("unmatched '\"'");
site.syntaxError("unmatched '\"'");
break;
}

@@ -436,3 +479,5 @@

} else if (escapeSite !== null) {
throw escapeSite.syntaxError(`invalid escape sequence ${c}`)
const errorSite = new Site(escapeSite.src, escapeSite.startPos, this.currentSite.startPos);
errorSite.syntaxError(`invalid escape sequence ${c}`);
} else {

@@ -443,2 +488,3 @@ throw new Error("escape site should be non-null");

escaping = false;
escapeSite = null;
} else {

@@ -456,3 +502,8 @@ if (c == '\\') {

this.pushToken(new StringLiteral(site, chars.join('')));
this.pushToken(
new StringLiteral(
new Site(site.src, site.startPos, this.currentSite.startPos),
chars.join('')
)
);
}

@@ -497,3 +548,8 @@

this.pushToken(new SymbolToken(site, chars.join('')));
this.pushToken(
new SymbolToken(
new Site(site.src, site.startPos, site.endPos),
chars.join('')
)
);
}

@@ -504,80 +560,96 @@

* @param {Token[]} ts
* @returns {Group}
* @returns {Group | null}
*/
static buildGroup(ts) {
let tOpen = ts.shift();
if (tOpen === undefined) {
throw new Error("unexpected");
} else {
let open = tOpen.assertSymbol();
buildGroup(ts) {
const open = assertDefined(ts.shift()).assertSymbol();
let stack = [open]; // stack of symbols
let curField = [];
let fields = [];
if (!open) {
return null;
}
/** @type {?SymbolToken} */
let firstComma = null;
const stack = [open]; // stack of symbols
let curField = [];
let fields = [];
/** @type {?SymbolToken} */
let lastComma = null;
/** @type {?SymbolToken} */
let firstComma = null;
/** @type {?Site} */
let endSite = null;
/** @type {?SymbolToken} */
let lastComma = null;
while (stack.length > 0 && ts.length > 0) {
let t = assertDefined(ts.shift());
let prev = stack.pop();
endSite = t.site;
/** @type {?Site} */
let endSite = null;
if (t != undefined && prev != undefined) {
if (!t.isSymbol(Group.matchSymbol(prev))) {
stack.push(prev);
while (stack.length > 0 && ts.length > 0) {
const t = assertToken(ts.shift(), open.site);
if (Group.isCloseSymbol(t)) {
throw t.syntaxError(`unmatched '${t.assertSymbol().value}'`);
} else if (Group.isOpenSymbol(t)) {
stack.push(t.assertSymbol());
curField.push(t);
} else if (t.isSymbol(",") && stack.length == 1) {
if (firstComma === null) {
firstComma = t.assertSymbol();
}
if (!t) {
return null;
}
lastComma = t.assertSymbol();
if (curField.length == 0) {
throw t.syntaxError("empty field");
} else {
fields.push(curField);
curField = [];
}
const prev = stack.pop();
endSite = t.site;
if (t != undefined && prev != undefined) {
if (!t.isSymbol(Group.matchSymbol(prev))) {
stack.push(prev);
if (Group.isCloseSymbol(t)) {
t.site.syntaxError(`unmatched '${assertDefined(t.assertSymbol()).value}'`);
return null;
} else if (Group.isOpenSymbol(t)) {
stack.push(assertDefined(t.assertSymbol()));
curField.push(t);
} else if (t.isSymbol(",") && stack.length == 1) {
if (firstComma === null) {
firstComma = t.assertSymbol();
}
lastComma = t.assertSymbol();
if (curField.length == 0) {
t.site.syntaxError("empty field");
return null;
} else {
curField.push(t);
fields.push(curField);
curField = [];
}
} else if (stack.length > 0) {
} else {
curField.push(t);
}
} else {
throw new Error("unexpected");
} else if (stack.length > 0) {
curField.push(t);
}
} else {
throw new Error("unexpected");
}
}
let last = stack.pop();
if (last != undefined) {
throw last.syntaxError(`EOF while matching '${last.value}'`);
}
let last = stack.pop();
if (last != undefined) {
last.syntaxError(`EOF while matching '${last.value}'`);
return null;
}
if (curField.length > 0) {
// add removing field
fields.push(curField);
} else if (lastComma !== null) {
throw lastComma.syntaxError(`trailing comma`);
}
if (curField.length > 0) {
// add removing field
fields.push(curField);
} else if (lastComma !== null) {
lastComma.syntaxError(`trailing comma`);
return null;
}
fields = fields.map(f => Tokenizer.nestGroups(f));
const groupedFields = reduceNull(fields.map(f => this.nestGroups(f)));
let site = tOpen.site;
site.setEndSite(endSite);
if (!groupedFields) {
return null;
}
return new Group(site, open.value, fields, firstComma);
let site = open.site;
if (endSite) {
site = site.merge(endSite);
}
return new Group(site, open.value, groupedFields, firstComma);
}

@@ -589,5 +661,8 @@

* @param {Token[]} ts
* @returns {Token[]}
* @returns {Token[] | null}
*/
static nestGroups(ts) {
nestGroups(ts) {
/**
* @type {(Token | null)[]}
*/
let res = [];

@@ -600,5 +675,5 @@

res.push(Tokenizer.buildGroup(ts));
res.push(this.buildGroup(ts));
} else if (Group.isCloseSymbol(t)) {
throw t.syntaxError(`unmatched '${t.assertSymbol().value}'`);
t.syntaxError(`unmatched '${assertDefined(t.assertSymbol()).value}'`);
} else {

@@ -611,3 +686,3 @@ res.push(t);

return res;
return reduceNull(res);
}

@@ -618,5 +693,5 @@ }

* Tokenizes a string (wrapped in Source)
* @package
* Also used by VSCode plugin
* @param {Source} src
* @returns {Token[]}
* @returns {Token[] | null}
*/

@@ -642,3 +717,11 @@ export function tokenize(src) {

return tokenizer.tokenize();
}
const ts = tokenizer.tokenize();
if (src.errors.length > 0) {
throw src.errors[0];
} else if (ts === null) {
throw new Error("should've been thrown above");
}
return ts;
}

@@ -22,5 +22,6 @@ //@ts-check

#src;
#pos;
#startPos;
#endPos;
/** @type {?Site} - end of token, exclusive */
/** @type {?Site} - end of token, exclusive, TODO: replace with endPos */
#endSite;

@@ -33,7 +34,9 @@

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
*/
constructor(src, pos) {
constructor(src, startPos, endPos = startPos + 1) {
this.#src = src;
this.#pos = pos;
this.#startPos = startPos;
this.#endPos = endPos;
this.#endSite = null;

@@ -51,8 +54,8 @@ this.#codeMapSite = null;

get pos() {
return this.#pos;
get startPos() {
return this.#startPos;
}
get line() {
return this.#src.posToLine(this.#pos);
get endPos() {
return this.#endPos;
}

@@ -65,17 +68,14 @@

/**
* @param {?Site} site
* @param {Site} other
* @returns {Site}
*/
setEndSite(site) {
this.#endSite = site;
merge(other) {
return new Site(this.#src, this.#startPos, other.#endPos);
}
/**
* @type {string}
* @param {?Site} site
*/
get part() {
if (this.#endSite === null) {
return this.#src.raw.slice(this.#pos);
} else {
return this.#src.raw.slice(this.#pos, this.#endSite.pos);
}
setEndSite(site) {
this.#endSite = site;
}

@@ -103,3 +103,3 @@

syntaxError(info = "") {
return UserError.syntaxError(this.#src, this.#pos, info);
return UserError.syntaxError(this.#src, this.#startPos, this.#endPos, info);
}

@@ -113,3 +113,3 @@

typeError(info = "") {
return UserError.typeError(this.#src, this.#pos, info);
return UserError.typeError(this.#src, this.#startPos, this.#endPos, info);
}

@@ -123,3 +123,3 @@

referenceError(info = "") {
return UserError.referenceError(this.#src, this.#pos, info);
return UserError.referenceError(this.#src, this.#startPos, this.#endPos, info);
}

@@ -135,5 +135,5 @@

let site = this.#codeMapSite;
return RuntimeError.newRuntimeError(site.#src, site.#pos, false, info);
return RuntimeError.newRuntimeError(site.#src, site.#startPos, false, info);
} else {
return RuntimeError.newRuntimeError(this.#src, this.#pos, true, info);
return RuntimeError.newRuntimeError(this.#src, this.#startPos, true, info);
}

@@ -144,6 +144,10 @@ }

* Calculates the column,line position in 'this.#src'
* @returns {[number, number]}
* @returns {[number, number, number, number]} - [startLine, startCol, endLine, endCol]
*/
getFilePos() {
return this.#src.posToColAndLine(this.#pos);
const [startLine, startCol] = this.#src.posToLineAndCol(this.#startPos);
const [endLine, endCol] = this.#src.posToLineAndCol(this.#endPos);
return [startLine, startCol, endLine, endCol];
}

@@ -158,4 +162,5 @@ }

export class UserError extends Error {
#pos;
#src;
#startPos;
#endPos;

@@ -165,8 +170,10 @@ /**

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
*/
constructor(msg, src, pos) {
constructor(msg, src, startPos, endPos = startPos + 1) {
super(msg);
this.#pos = pos;
this.#src = src;
this.#startPos = startPos;
this.#endPos = endPos;
}

@@ -177,7 +184,8 @@

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
* @param {string} info
*/
static new(type, src, pos, info = "") {
let line = src.posToLine(pos);
static new(type, src, startPos, endPos, info = "") {
let line = src.posToLine(startPos);

@@ -189,3 +197,3 @@ let msg = `${type} on line ${line + 1}`;

return new UserError(msg, src, pos);
return new UserError(msg, src, startPos, endPos);
}

@@ -203,8 +211,13 @@

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
* @param {string} info
* @returns {UserError}
*/
static syntaxError(src, pos, info = "") {
return UserError.new("SyntaxError", src, pos, info);
static syntaxError(src, startPos, endPos, info = "") {
const error = UserError.new("SyntaxError", src, startPos, endPos, info);
src.errors.push(error);
return error;
}

@@ -215,8 +228,13 @@

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
* @param {string} info
* @returns {UserError}
*/
static typeError(src, pos, info = "") {
return UserError.new("TypeError", src, pos, info);
static typeError(src, startPos, endPos, info = "") {
const error = UserError.new("TypeError", src, startPos, endPos, info);
src.errors.push(error);
return error;
}

@@ -235,8 +253,13 @@

* @param {Source} src
* @param {number} pos
* @param {number} startPos
* @param {number} endPos
* @param {string} info
* @returns {UserError}
*/
static referenceError(src, pos, info = "") {
return UserError.new("ReferenceError", src, pos, info);
static referenceError(src, startPos, endPos, info = "") {
const error = UserError.new("ReferenceError", src, startPos, endPos, info);
src.errors.push(error);
return error;
}

@@ -259,4 +282,4 @@

*/
get pos() {
return this.#pos;
get startPos() {
return this.#startPos;
}

@@ -266,6 +289,9 @@

* Calculates column/line position in 'this.src'.
* @returns {[number, number]}
* @returns {[number, number, number, number]} - [startLine, startCol, endLine, endCol]
*/
getFilePos() {
return this.#src.posToColAndLine(this.#pos);
const [startLine, startCol] = this.#src.posToLineAndCol(this.#startPos);
const [endLine, endCol] = this.#src.posToLineAndCol(this.#endPos);
return [startLine, startCol, endLine, endCol];
}

@@ -316,2 +342,6 @@

/**
* @typedef {(error: UserError) => void} Throw
*/
/**
* @package

@@ -382,3 +412,3 @@ */

return new RuntimeError(msg, this.src, this.pos, isIR);
return new RuntimeError(msg, this.src, this.startPos, isIR);
}

@@ -393,5 +423,5 @@

if (site.codeMapSite === null) {
return this.addTrace(site.src, site.pos, true, info);
return this.addTrace(site.src, site.startPos, true, info);
} else {
return this.addTrace(site.codeMapSite.src, site.codeMapSite.pos, false, info);
return this.addTrace(site.codeMapSite.src, site.codeMapSite.startPos, false, info);
}

@@ -443,2 +473,9 @@ }

/**
* @returns {boolean}
*/
isKeyword() {
return false;
}
/**
* Returns 'true' if 'this' is a Symbol token (eg. '+', '(' etc.)

@@ -455,5 +492,6 @@ * @param {?(string | string[])} value

* @param {?string} value
* @param {number | null} nFields
* @returns {boolean}
*/
isGroup(value) {
isGroup(value, nFields = null) {
return false;

@@ -492,10 +530,12 @@ }

* @param {?(string | string[])} value
* @returns {Word}
* @returns {Word | null}
*/
assertWord(value = null) {
if (value !== null) {
throw this.syntaxError(`expected \'${value}\', got \'${this.toString()}\'`);
this.syntaxError(`expected \'${value}\', got \'${this.toString()}\'`);
} else {
throw this.syntaxError(`expected word, got ${this.toString()}`);
this.syntaxError(`expected word, got ${this.toString()}`);
}
return null;
}

@@ -506,10 +546,12 @@

* @param {?(string | string[])} value
* @returns {SymbolToken}
* @returns {SymbolToken | null}
*/
assertSymbol(value = null) {
if (value !== null) {
throw this.syntaxError(`expected '${value}', got '${this.toString()}'`);
this.syntaxError(`expected '${value}', got '${this.toString()}'`);
} else {
throw this.syntaxError(`expected symbol, got '${this.toString()}'`);
this.syntaxError(`expected symbol, got '${this.toString()}'`);
}
return null;
}

@@ -521,10 +563,12 @@

* @param {?number} nFields
* @returns {Group}
* @returns {Group | null}
*/
assertGroup(type = null, nFields = null) {
if (type !== null) {
throw this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}'`)
this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}'`);
} else {
throw this.syntaxError(`invalid syntax: expected group`);
this.syntaxError(`invalid syntax: expected group`);
}
return null;
}

@@ -534,2 +578,18 @@ }

/**
* @package
* @param {undefined | null | Token} t
* @param {Site} site
* @param {string} msg
* @returns {null | Token}
*/
export function assertToken(t, site, msg = "expected token") {
if (!t) {
site.syntaxError(msg);
return null;
} else {
return t;
}
}
/**
* A Word token represents a token that matches /[A-Za-z_][A-Za-z_0-9]/

@@ -626,3 +686,3 @@ * @package

/**
* @returns {Word}
* @returns {Word | null}
*/

@@ -633,3 +693,4 @@ assertNotKeyword() {

if (this.isKeyword()) {
throw this.syntaxError(`'${this.#value}' is a reserved word`);
this.syntaxError(`'${this.#value}' is a reserved word`);
return null;
}

@@ -773,9 +834,12 @@

* @param {?string} type
* @param {number | null} nFields
* @returns {boolean}
*/
isGroup(type = null) {
isGroup(type = null, nFields = null) {
const nFieldsOk = (nFields === null) || (nFields == this.#fields.length);
if (type !== null) {
return this.#type == type;
return this.#type == type && nFieldsOk;
} else {
return true;
return nFieldsOk;
}

@@ -787,16 +851,20 @@ }

* @param {?number} nFields
* @returns {Group}
* @returns {Group | null}
*/
assertGroup(type = null, nFields = null) {
if (type !== null && this.#type != type) {
throw this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}', got '${this.#type}...${Group.matchSymbol(this.#type)}'`);
this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}', got '${this.#type}...${Group.matchSymbol(this.#type)}'`);
return null;
} else if (type !== null && nFields !== null && nFields != this.#fields.length) {
if (this.#fields.length > 1 && nFields <= 1 && this.#firstComma !== null) {
throw this.#firstComma.syntaxError(`invalid syntax, unexpected ','`);
this.#firstComma.syntaxError(`invalid syntax, unexpected ','`);
} else {
throw this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}' with ${nFields} field(s), got '${type}...${Group.matchSymbol(type)}' with ${this.#fields.length} fields`);
this.syntaxError(`invalid syntax: expected '${type}...${Group.matchSymbol(type)}' with ${nFields} field(s), got '${type}...${Group.matchSymbol(type)}' with ${this.#fields.length} fields`);
}
return null;
} else {
return this;
}
return this;
}

@@ -1169,2 +1237,2 @@

}
}
}

@@ -871,3 +871,3 @@ //@ts-check

if (!("cborHex" in obj)) {
throw UserError.syntaxError(new Source(jsonString), 0, "cborHex field not in json")
throw UserError.syntaxError(new Source(jsonString), 0, 1, "cborHex field not in json")
}

@@ -881,5 +881,6 @@

if (cborHexMatch === null) {
throw UserError.syntaxError(src, 0, "'cborHex' key not found");
throw UserError.syntaxError(src, 0, 1, "'cborHex' key not found");
} else {
throw UserError.syntaxError(src, jsonString.search(re), "cborHex not a string");
const pos = jsonString.search(re)
throw UserError.syntaxError(src, pos, pos+1, "cborHex not a string");
}

@@ -886,0 +887,0 @@ }

@@ -6,2 +6,3 @@ //@ts-check

/**

@@ -67,2 +68,60 @@ * Throws an error if 'cond' is false.

/**
* @package
* @template T
* @param {(T | null)[]} lst
* @returns {null | (T[])}
*/
export function reduceNull(lst) {
/**
* @type {T[]}
*/
const nonNullLst = [];
let someNull = false;
lst.forEach(item => {
if (item !== null && !someNull) {
nonNullLst.push(item);
} else {
someNull = true;
}
});
if (someNull) {
return null;
} else {
return nonNullLst;
}
}
/**
* @template Ta
* @template Tb
* @param {[Ta | null, Tb | null][]} pairs
* @returns {null | [Ta, Tb][]}
*/
export function reduceNullPairs(pairs) {
/**
* @type {[Ta, Tb][]}
*/
const nonNullPairs = [];
let someNull = false;
pairs.forEach(([a, b]) => {
if (a === null || b === null) {
someNull = true;
} else if (!someNull) {
nonNullPairs.push([a, b]);
}
});
if (someNull) {
return null;
} else {
return nonNullPairs;
}
}
/**
* Compares two objects (deep recursive comparison)

@@ -576,3 +635,3 @@ * @package

* A Source instance wraps a string so we can use it cheaply as a reference inside a Site.
* @package
* Also used by VSCode plugin
*/

@@ -582,2 +641,3 @@ export class Source {

#fileIndex;
#errors; // errors are collected into this object

@@ -591,2 +651,3 @@ /**

this.#fileIndex = fileIndex;
this.#errors = [];
}

@@ -611,2 +672,15 @@

/**
* @type {Error[]}
*/
get errors() {
return this.#errors;
}
throwErrors() {
if (this.#errors.length > 0) {
throw this.#errors[0];
}
}
/**
* Get char from the underlying string.

@@ -690,3 +764,3 @@ * Should work fine utf-8 runes.

// returns [col, line]
posToColAndLine(pos) {
posToLineAndCol(pos) {
let col = 0;

@@ -703,3 +777,3 @@ let line = 0;

return [col, line];
return [line, col];
}

@@ -765,2 +839,2 @@

console.warn(msg);
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc