derw
Version:
An Elm-inspired language that transpiles to TypeScript
1,393 lines • 115 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseWithContext = exports.addTypeErrors = exports.parse = exports.stripComments = exports.parseBlock = exports.parseExpression = void 0;
const maybe_1 = require("@eeue56/ts-core/build/main/lib/maybe");
const result_1 = require("@eeue56/ts-core/build/main/lib/result");
const Blocks_1 = require("./Blocks");
const Collisions_1 = require("./Collisions");
const Tokens_1 = require("./Tokens");
const builtins_1 = require("./builtins");
const type_checking_1 = require("./type_checking");
const types_1 = require("./types");
function afterArrow(tokens) {
let index = 0;
while (index < tokens.length) {
if (tokens[index].kind !== "ArrowToken")
break;
index++;
}
return tokens.slice(index);
}
function splitOnArrowTokens(tokens) {
const results = [];
let lastIndex = 0;
let index = 0;
while (index < tokens.length) {
if (tokens[index].kind === "ArrowToken") {
results.push(tokens.slice(lastIndex, index));
lastIndex = index + 1;
}
index++;
}
if (index > lastIndex) {
results.push(tokens.slice(lastIndex, index));
}
return results;
}
function parseTypeToken(token) {
switch (token.kind) {
case "ArrowToken": {
return (0, result_1.Err)("Unexpected arrow in type");
}
case "BaseTypeToken": {
const rootType = token.body[0];
if (rootType.kind === "IdentifierToken") {
const parsedTypes = afterArrow(token.body.slice(1)).map(parseTypeToken);
if (parsedTypes.length === 0) {
if ((0, builtins_1.isBuiltinType)(rootType.body) &&
rootType.body !== "any") {
return (0, result_1.Ok)((0, types_1.FixedType)(rootType.body, []));
}
else if (rootType.body.toLowerCase() === rootType.body) {
return (0, result_1.Ok)((0, types_1.GenericType)(rootType.body));
}
}
const errors = [];
const correct = [];
for (const parsed of parsedTypes) {
if (parsed.kind === "Ok") {
correct.push(parsed.value);
}
else {
errors.push(parsed.error);
}
}
if (errors.length > 0) {
return (0, result_1.Err)(errors.join("\n"));
}
return (0, result_1.Ok)((0, types_1.FixedType)(rootType.body, correct));
}
return (0, result_1.Err)(`Invalid root type ${rootType.kind}`);
}
case "CloseBracketToken": {
return (0, result_1.Err)("Unexpected close bracket in type");
}
case "FunctionTypeToken": {
const parsedTypes = token.body.map((x) => {
return parseTypeToken(x);
});
const errors = [];
const correct = [];
for (const parsed of parsedTypes) {
if (parsed.kind === "Ok") {
correct.push(parsed.value);
}
else {
errors.push(parsed.error);
}
}
if (errors.length > 0) {
return (0, result_1.Err)(errors.join("\n"));
}
return (0, result_1.Ok)((0, types_1.FunctionType)(correct));
}
case "IdentifierToken": {
if ((0, builtins_1.isBuiltinType)(token.body) && token.body !== "any") {
return (0, result_1.Ok)((0, types_1.FixedType)(token.body, []));
}
else if (token.body.toLowerCase() === token.body) {
return (0, result_1.Ok)((0, types_1.GenericType)(token.body));
}
return (0, result_1.Ok)((0, types_1.FixedType)(token.body, []));
}
case "OpenBracketToken": {
return (0, result_1.Err)("Unexected open bracket in type");
}
case "StringToken": {
return (0, result_1.Err)("Unexpected string token");
}
}
}
function parseRootTypeTokens(token) {
switch (token.kind) {
case "BaseTypeToken": {
return parseTypeToken(token);
}
case "FunctionTypeToken": {
return parseTypeToken(token);
}
}
}
function parseType(tokens) {
const tokenizedTypes = (0, Tokens_1.tokenizeType)(tokens);
if (tokenizedTypes.kind === "Err")
return tokenizedTypes;
if (tokenizedTypes.value.length < 1) {
return (0, result_1.Err)("Expected a type but couldn't find one");
}
return parseRootTypeTokens(tokenizedTypes.value[0]);
}
function parseUnionUntaggedType(tokens) {
if (tokens[0].kind === "KeywordToken") {
if (tokens[0].body !== "type") {
return (0, result_1.Err)("Expected `type` but got " + tokens[0].body);
}
}
const assignIndex = tokens.findIndex((t) => t.kind === "AssignToken");
const parsedType = parseType(tokens.slice(1, assignIndex - 1));
if (parsedType.kind === "Err") {
return (0, result_1.Err)("Failed to parse untagged union type name " + parsedType.error);
}
if (parsedType.value.kind === "GenericType") {
return (0, result_1.Err)("Expected a fixed type but got a generic type for a union type. Maybe you missed a captal letter?");
}
if (parsedType.value.kind === "FunctionType") {
return (0, result_1.Err)("Expected a fixed type but got a function type for a union type. Maybe you missed a captal letter?");
}
if (parsedType.value.kind === "ObjectLiteralType") {
return (0, result_1.Err)("Expected a fixed type but got a object lieral type for a union type. Maybe you missed a captal letter?");
}
const branches = [];
for (const token of tokens.slice(assignIndex + 1)) {
switch (token.kind) {
case "StringToken": {
const value = parseStringValue([token]);
if (value.kind === "Err")
return (0, result_1.Err)("Failed to parse string in untagged union type definiton: " +
value.error);
branches.push(value.value);
break;
}
case "WhitespaceToken": {
continue;
}
case "PipeToken": {
continue;
}
default: {
return (0, result_1.Err)(`Expected string, whitespace, or pipe but got ${token.kind} in untagged union type definition.`);
}
}
}
return (0, result_1.Ok)((0, types_1.UnionUntaggedType)(parsedType.value, branches));
}
function parseUnionType(tokens) {
if (tokens[0].kind === "KeywordToken") {
if (tokens[0].body !== "type") {
return (0, result_1.Err)("Expected `type` but got " + tokens[0].body);
}
}
let typeLine = [];
let isInBranches = false;
let branches = [];
let currentBranch = [];
let inBrackets = false;
for (var i = 1; i < tokens.length; i++) {
const token = tokens[i];
switch (token.kind) {
case "IdentifierToken": {
if (isInBranches) {
currentBranch.push(token.body);
}
else {
typeLine.push(token);
}
break;
}
case "PipeToken": {
branches.push(currentBranch.join(" "));
currentBranch = [];
break;
}
case "WhitespaceToken": {
typeLine.push(token);
break;
}
case "OpenBracketToken":
case "CloseBracketToken": {
if (isInBranches) {
if (currentBranch[currentBranch.length - 1] === ":" &&
!inBrackets) {
}
else {
currentBranch.push(token.kind === "OpenBracketToken" ? "(" : ")");
inBrackets = token.kind === "OpenBracketToken";
}
}
continue;
}
case "CommaToken": {
currentBranch.push(",");
break;
}
case "ColonToken": {
currentBranch.push(":");
break;
}
case "OpenCurlyBracesToken": {
if (isInBranches) {
currentBranch.push("{");
break;
}
}
case "CloseCurlyBracesToken": {
if (isInBranches) {
currentBranch.push(" }");
break;
}
}
case "AssignToken": {
isInBranches = true;
break;
}
case "ArrowToken": {
currentBranch.push("->");
break;
}
default: {
return (0, result_1.Err)("Unexpected token parsing a union type. Got " + token.kind);
}
}
}
if (currentBranch) {
branches.push(currentBranch.join(" "));
}
const parsedType = parseType(typeLine);
if (parsedType.kind === "Err")
return parsedType;
const tags = branches.map((tag) => {
if (tag.startsWith("|")) {
tag = tag.slice(1);
}
tag = tag.trim();
const tagName = tag.split(" ")[0];
if (tagName.length === 0) {
return (0, result_1.Err)(`Missing expected tag name for union type \`${(0, Tokens_1.tokensToString)(typeLine)}\``);
}
if ((0, builtins_1.isReservedName)(tagName)) {
return (0, result_1.Err)(`Redefining ${tagName.trim()} will cause problems. Try renaming it to ${tagName.trim()}Value`);
}
let argsAsJson = tag.split(" ").slice(1).join(" ");
const args = argsAsJson
.split(" ")
// remove brackets
.filter((j) => j !== "{" && j !== "}")
.join(" ")
// split args by commmas
.split(",")
.filter((arg) => arg.trim().length > 0)
.map((arg) => {
const property = parseProperty((0, Tokens_1.tokenize)(arg));
if (property.kind === "Err")
return property;
return (0, result_1.Ok)((0, types_1.TagArg)(property.value.name, property.value.type));
});
if (args.filter((maybeTag) => maybeTag.kind === "Ok").length ===
args.length) {
return (0, result_1.Ok)((0, types_1.Tag)(tagName, args.map((arg) => arg.value)));
}
return (0, result_1.Err)("Error parsing args due to:\n" +
args
.filter((arg) => arg.kind === "Err")
.map((err) => err.error));
});
if (tags.length === 0) {
return (0, result_1.Err)("Not enough tags given.");
}
for (var i = 0; i < tags.length; i++) {
const tag = tags[i];
if (tag.kind === "Err") {
return tag;
}
}
if (parsedType.value.kind === "GenericType") {
return (0, result_1.Err)("Expected a fixed type but got a generic type for a union type. Maybe you missed a captal letter?");
}
if (parsedType.value.kind === "FunctionType") {
return (0, result_1.Err)("Expected a fixed type but got a function type for a union type. Maybe you missed a captal letter?");
}
if (parsedType.value.kind === "ObjectLiteralType") {
return (0, result_1.Err)("Expected a fixed type but got a object literal type for a union type. Maybe you missed a captal letter?");
}
return (0, result_1.Ok)((0, types_1.UnionType)(parsedType.value, tags
.filter((tag) => tag.kind === "Ok")
.map((tag) => tag.value)));
}
function parseProperty(tokens) {
let index = 0;
let name = null;
while (index < tokens.length) {
const token = tokens[index];
if (token.kind === "WhitespaceToken") {
}
else if (token.kind === "IdentifierToken") {
if (name) {
return (0, result_1.Err)("Got too many identifiers for property name");
}
name = token.body;
}
else if (token.kind === "ColonToken") {
break;
}
else if (token.kind === "KeywordToken" && token.body === "type") {
if (name) {
return (0, result_1.Err)("Got too many identifiers for property name");
}
name = token.body;
}
else {
return (0, result_1.Err)(`Expected identifier in property name but got ${token.kind}`);
}
index++;
}
index++;
if (name === null) {
return (0, result_1.Err)("Expected identifier for property name but found nothing");
}
let bitsAfterName = tokens.slice(index);
if (tokens.find((token) => token.kind === "ArrowToken")) {
bitsAfterName = [
(0, Tokens_1.OpenBracketToken)({}),
...bitsAfterName,
(0, Tokens_1.CloseBracketToken)({}),
];
}
const tokenizedTypes = (0, Tokens_1.tokenizeType)(bitsAfterName);
if (tokenizedTypes.kind === "Err")
return tokenizedTypes;
const types = tokenizedTypes.value;
if (types.length > 1) {
return (0, result_1.Err)("Too many types found in property");
}
if (types.length < 1)
return (0, result_1.Err)("Failed to find type");
const type = parseRootTypeTokens(types[0]);
if (type.kind === "Err")
return type;
return (0, result_1.Ok)((0, types_1.Property)(name, type.value));
}
function isRootProperty(line) {
if (line.match(/ .+/)) {
return true;
}
return false;
}
function parseTypeAlias(tokens) {
let index = 0;
let hasSeenType = false;
while (index < tokens.length) {
const token = tokens[index];
if (token.kind === "KeywordToken") {
if (hasSeenType) {
if (token.body === "alias") {
break;
}
}
else if (token.body === "type") {
hasSeenType = true;
}
else {
return (0, result_1.Err)("Expected `type alias` but got " + token);
}
}
index++;
}
index += 2;
let typeLine = [];
let isInDefinition = false;
let currentDefinition = [];
let braceDepth = 0;
for (var i = index; i < tokens.length; i++) {
const token = tokens[i];
switch (token.kind) {
case "IdentifierToken": {
if (isInDefinition) {
currentDefinition.push(token.body);
}
else {
typeLine.push(token);
}
break;
}
case "WhitespaceToken": {
continue;
}
case "OpenBracketToken": {
currentDefinition.push("(");
break;
}
case "CloseBracketToken": {
currentDefinition.push(")");
break;
}
case "CommaToken": {
currentDefinition.push(",");
break;
}
case "ColonToken": {
currentDefinition.push(":");
break;
}
case "OpenCurlyBracesToken": {
if (isInDefinition) {
braceDepth += 1;
currentDefinition.push("{");
break;
}
break;
}
case "CloseCurlyBracesToken": {
if (isInDefinition) {
if (braceDepth > 0) {
currentDefinition.push(" }");
}
break;
}
braceDepth -= 1;
break;
}
case "AssignToken": {
isInDefinition = true;
break;
}
case "ArrowToken": {
currentDefinition.push("->");
break;
}
default: {
return (0, result_1.Err)("Unexpected token parsing a type alias. Got " + token.kind);
}
}
}
const parsedAliasName = parseType(typeLine);
if (parsedAliasName.kind === "Err") {
return parsedAliasName;
}
const aliasName = parsedAliasName.value;
const recordDefinition = currentDefinition.join(" ");
let lines = [];
recordDefinition.split("\n").forEach((line) => {
const hasComma = line.indexOf(",") > -1;
if (hasComma) {
const hasTextAfterComma = line.split(",")[1].trim().length > 0;
if (hasTextAfterComma) {
lines = lines.concat(...line.split(",").map((piece) => " " + piece));
return;
}
}
lines.push(line);
});
let currentBuffer = [];
const properties = [];
lines.forEach((line, i) => {
const isOpeningBrace = line.trim() === "{" && i === 0;
const isClosingBrace = line.trim() === "}" && i === lines.length - 1;
const otherThanWhitespace = line
.split("")
.filter((x) => x.trim().length > 0)
.join("");
const isEmptyBody = otherThanWhitespace === "{}";
if (isOpeningBrace || isClosingBrace || isEmptyBody) {
return;
}
const hasInlineOpeningBrace = line.trim().startsWith("{") && i === 0;
if (hasInlineOpeningBrace) {
line = line.trim().slice(1);
}
const hasInlineClosingBrace = line.trim().endsWith("}") && i === lines.length - 1;
if (hasInlineClosingBrace) {
line = line.slice(0, -1);
}
if (isRootProperty(line)) {
if (currentBuffer.length > 0) {
properties.push(currentBuffer.join("\n"));
currentBuffer = [line];
}
else {
currentBuffer.push(line);
}
}
else {
currentBuffer.push(line);
}
});
if (currentBuffer.length > 0) {
properties.push(currentBuffer.join("\n"));
}
const parsedProperties = properties.map((x) => parseProperty((0, Tokens_1.tokenize)(x)));
const errors = parsedProperties.filter((property) => property.kind === "Err");
if (errors.length > 0) {
return (0, result_1.Err)(errors
.map((err) => err.error)
.join("\n"));
}
if (aliasName.kind === "GenericType") {
return (0, result_1.Err)("Expected a fixed type but got a generic type for a type alias. Maybe you missed a captal letter?");
}
if (aliasName.kind === "FunctionType") {
return (0, result_1.Err)("Expected a fixed type but got a function type for a type alias. Maybe you missed a captal letter?");
}
if (aliasName.kind === "ObjectLiteralType") {
return (0, result_1.Err)("Expected a fixed type but got an object literal type for a type alias. Maybe you missed a captal letter?");
}
return (0, result_1.Ok)((0, types_1.TypeAlias)(aliasName, parsedProperties.map((property) => property.value)));
}
function splitOnNewlines(tokens) {
const lines = [];
let currentLine = [];
for (const token of tokens) {
if (token.kind === "WhitespaceToken" && token.body.indexOf("\n") > -1) {
lines.push(currentLine);
currentLine = [];
}
else {
currentLine.push(token);
}
}
if (currentLine.length > 0) {
lines.push(currentLine);
}
return lines;
}
function splitOn(tokens, splitToken) {
const pieces = [];
let currentPiece = [];
for (const token of tokens) {
if (equalTokens(token, splitToken)) {
pieces.push(currentPiece);
currentPiece = [];
}
else {
currentPiece.push(token);
}
}
if (currentPiece.length > 0) {
pieces.push(currentPiece);
}
return pieces;
}
function parseTypeclassFunction(tokens) {
const functionNameToken = tokens[0];
if (functionNameToken.kind !== "IdentifierToken") {
return (0, result_1.Err)(`Expected identifier token for function name but got ${functionNameToken.kind}`);
}
const functionName = functionNameToken.body;
const typeSignatureIndex = goToTheEndOfPattern(tokens, [(0, Tokens_1.ColonToken)({})]);
if (typeSignatureIndex === -1) {
return (0, result_1.Err)("Unable to find colon in type signature");
}
const typeSignature = tokens.slice(typeSignatureIndex + 1, tokens.length);
const typePieces = splitOn(typeSignature, (0, Tokens_1.ArrowToken)({}));
const types = typePieces.map(parseType);
const parsedTypes = [];
const errors = [];
for (const type of types) {
if (type.kind === "Err") {
errors.push(type.error);
}
else {
parsedTypes.push(type.value);
}
}
if (errors.length > 0) {
return (0, result_1.Err)(errors.join("\n"));
}
return (0, result_1.Ok)((0, types_1.TypeclassFunction)(functionName, parsedTypes[parsedTypes.length - 1], parsedTypes.slice(0, parsedTypes.length - 1)));
}
function parseTypeclass(tokens) {
const typeclassNameIndex = goToTheEndOfPattern(tokens, [
(0, Tokens_1.KeywordToken)({ body: "typeclass" }),
(0, Tokens_1.WhitespaceToken)({ body: " " }),
]);
if (typeclassNameIndex === -1)
return (0, result_1.Err)("Failed to find typeclass name");
const typeclassNameToken = tokens[typeclassNameIndex + 1];
if (typeclassNameToken.kind !== "IdentifierToken") {
return (0, result_1.Err)(`Expected an identifier for the typeclass name but got ${typeclassNameToken.kind}`);
}
const typeclassName = typeclassNameToken.body;
const variableIndex = goToTheEndOfPattern(tokens, [
(0, Tokens_1.KeywordToken)({ body: "typeclass" }),
(0, Tokens_1.WhitespaceToken)({ body: " " }),
typeclassNameToken,
(0, Tokens_1.WhitespaceToken)({ body: " " }),
]);
if (variableIndex === -1)
return (0, result_1.Err)("Failed to find typeclass variable");
const variableToken = tokens[variableIndex + 1];
if (variableToken.kind !== "IdentifierToken") {
return (0, result_1.Err)(`Expected an identifier for the typeclass variable name but got ${variableToken.kind}`);
}
const variable = (0, types_1.GenericType)(variableToken.body);
const functionsStartIndex = goToTheEndOfPattern(tokens, [
(0, Tokens_1.KeywordToken)({ body: "typeclass" }),
(0, Tokens_1.WhitespaceToken)({ body: " " }),
typeclassNameToken,
(0, Tokens_1.WhitespaceToken)({ body: " " }),
variableToken,
(0, Tokens_1.WhitespaceToken)({ body: "\n " }),
]);
const functionsAsTokens = splitOnNewlines(tokens.slice(functionsStartIndex + 1, tokens.length));
const functions = functionsAsTokens.map(parseTypeclassFunction);
const parsedFunctions = [];
const errors = [];
for (const type of functions) {
if (type.kind === "Err") {
errors.push(type.error);
}
else {
parsedFunctions.push(type.value);
}
}
if (errors.length > 0) {
return (0, result_1.Err)(errors.join("\n"));
}
return (0, result_1.Ok)((0, types_1.Typeclass)(typeclassName, [variable], parsedFunctions));
}
function goUpToNewline(tokens) {
for (var i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.kind === "WhitespaceToken" && token.body.indexOf("\n") > -1) {
return i - 1;
}
}
return i;
}
function parseImpl(tokens) {
const implIndex = goToTheEndOfPattern(tokens, [
(0, Tokens_1.KeywordToken)({ body: "impl" }),
(0, Tokens_1.WhitespaceToken)({ body: " " }),
]);
if (implIndex === -1)
return (0, result_1.Err)("Failed to find impl name");
const implNameToken = tokens[implIndex + 1];
if (implNameToken.kind !== "IdentifierToken") {
return (0, result_1.Err)(`Expected an identifier for the impl name but got ${implNameToken.kind}`);
}
const implName = implNameToken.body;
const typesEndIndex = goUpToNewline(tokens);
const types = parseType(tokens.slice(implIndex + 2, typesEndIndex + 1));
if (types.kind === "Err") {
return types;
}
const parsedTypes = types.value;
const functionBlocks = (0, Blocks_1.intoBlocks)((0, Tokens_1.tokensToString)(deIndentTokens(tokens.slice(typesEndIndex + 2, tokens.length), 4)));
const parsedBlocks = functionBlocks.map(parseBlock);
const errors = [];
const blocks = [];
for (const block of parsedBlocks) {
if (block.kind === "Err") {
errors.push(block.error);
}
else if (block.value.kind !== "Function") {
errors.push(`Expected a function but got ${block.value.kind}`);
}
else {
blocks.push(block.value);
}
}
return (0, result_1.Ok)((0, types_1.Impl)(implName, parsedTypes, blocks));
}
function parseObjectLiteral(tokens) {
const fields = [];
let currentName = "";
let currentValue = null;
let objectDepth = 0;
let innermostBuffer = "";
let base = null;
let previousWasBase = false;
let isInName = false;
let index = 0;
while (index < tokens.length) {
const token = tokens[index];
if (token.kind === "WhitespaceToken") {
index++;
continue;
}
break;
}
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "OpenCurlyBracesToken": {
objectDepth++;
if (objectDepth === 1) {
isInName = true;
}
else {
innermostBuffer += "{";
}
break;
}
case "CloseCurlyBracesToken": {
objectDepth--;
if (objectDepth === 0) {
if (innermostBuffer.trim().length === 0)
continue;
const innerLiteral = parseExpression(innermostBuffer);
if (innerLiteral.kind === "Err")
return innerLiteral;
innermostBuffer = "";
currentValue = innerLiteral.value;
fields.push((0, types_1.Field)(currentName.trim(), currentValue));
currentName = "";
currentValue = null;
}
else {
innermostBuffer += "}";
}
break;
}
case "ColonToken": {
if (objectDepth === 1) {
isInName = false;
}
else {
innermostBuffer += ":";
}
break;
}
case "CommaToken": {
if (previousWasBase) {
previousWasBase = false;
break;
}
if (objectDepth > 1) {
innermostBuffer += ",";
}
else {
const innerLiteral = parseExpression(innermostBuffer);
if (innerLiteral.kind === "Err")
return innerLiteral;
fields.push((0, types_1.Field)(currentName.trim(), innerLiteral.value));
innermostBuffer = "";
currentName = "";
isInName = true;
}
break;
}
case "FormatStringToken":
case "StringToken":
case "LiteralToken":
case "IdentifierToken": {
if (isInName) {
if (token.kind === "IdentifierToken") {
if (token.body.startsWith("...")) {
base = (0, types_1.Value)(token.body);
previousWasBase = true;
break;
}
}
currentName += token.body;
}
else {
innermostBuffer += token.body;
}
break;
}
case "OperatorToken": {
if (!isInName) {
innermostBuffer += token.body;
}
break;
}
case "WhitespaceToken": {
if (!isInName) {
innermostBuffer += token.body;
}
break;
}
case "OpenBracketToken": {
innermostBuffer += "(";
break;
}
case "CloseBracketToken": {
innermostBuffer += ")";
break;
}
case "ArrowToken": {
innermostBuffer += "->";
}
}
index++;
}
return (0, result_1.Ok)((0, types_1.ObjectLiteral)(base, fields));
}
function parseValue(tokens) {
const body = [];
let index = 0;
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
break;
}
case "LiteralToken":
case "IdentifierToken": {
body.push(token.body);
break;
}
case "OpenBracketToken": {
body.push("(");
break;
}
case "CloseBracketToken": {
body.push(")");
break;
}
default: {
return (0, result_1.Err)(`Expected value but got ${token.kind}`);
}
}
index++;
}
const firstChar = body.join("").slice(0, 1);
if (firstChar !== "-" &&
firstChar.toUpperCase() === firstChar &&
isNaN(parseFloat(firstChar))) {
return parseConstructor(tokens);
}
return (0, result_1.Ok)((0, types_1.Value)(body.join("")));
}
function parseStringValue(tokens) {
if (tokens[0].kind === "StringToken")
return (0, result_1.Ok)((0, types_1.StringValue)(tokens[0].body.slice(1, -1)));
return (0, result_1.Err)(`Expected string literal, got ${tokens[0].kind}`);
}
function listRangeDotsNotWithinString(tokens) {
let i = 0;
for (const token of tokens) {
if (token.kind !== "WhitespaceToken")
break;
i++;
}
if (i === tokens.length)
return true;
const firstToken = tokens[i];
if (firstToken.kind === "LiteralToken" && firstToken.body.startsWith("[")) {
const newTokens = (0, Tokens_1.tokenize)(firstToken.body.slice(1));
for (const token of newTokens) {
if ((token.kind === "LiteralToken" ||
token.kind === "IdentifierToken") &&
token.body.indexOf("..") > -1) {
return true;
}
}
}
return false;
}
function parseListRange(tokens) {
let index = 0;
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
break;
}
case "LiteralToken": {
const trimmed = token.body.trim().slice(1).slice(0, -1);
const pieces = trimmed.split("..");
const start = parseValue((0, Tokens_1.tokenize)(pieces[0]));
const end = parseValue((0, Tokens_1.tokenize)(pieces[1]));
if (start.kind === "Err")
return start;
if (end.kind === "Err")
return end;
if (start.kind === "Ok" && start.value.kind === "Constructor")
return (0, result_1.Err)("Expected variable but got constructor");
if (end.kind === "Ok" && end.value.kind === "Constructor")
return (0, result_1.Err)("Expected variable but got constructor");
return (0, result_1.Ok)((0, types_1.ListRange)(start.value, end.value));
}
default: {
return (0, result_1.Err)(`Unxpected ${token.kind}, expected whitespace or literal`);
}
}
index++;
}
return (0, result_1.Err)("Failed to find list range. They should look like [1..2]");
}
function parseListValue(tokens) {
let index = 0;
let isFound = false;
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "LiteralToken": {
isFound = true;
}
}
if (isFound)
break;
index++;
}
const parsedValues = [];
// drop leading and trailing []
const body = (0, Tokens_1.tokensToString)(tokens.slice(index));
const trimmed = body.trim();
const innerBody = trimmed.slice(1, trimmed.length - 1).trim();
if (innerBody.trim().length === 0)
return (0, result_1.Ok)((0, types_1.ListValue)([]));
const innerTokens = (0, Tokens_1.tokenize)(innerBody);
let innerIndex = 0;
let currentBuffer = [];
let depth = 0;
while (innerIndex < innerTokens.length) {
const token = innerTokens[innerIndex];
switch (token.kind) {
case "OpenCurlyBracesToken": {
currentBuffer.push((0, Tokens_1.OpenCurlyBracesToken)({}));
depth++;
break;
}
case "CloseCurlyBracesToken": {
currentBuffer.push((0, Tokens_1.CloseCurlyBracesToken)({}));
depth--;
break;
}
case "CommaToken": {
if (depth === 0) {
parsedValues.push(parseExpression((0, Tokens_1.tokensToString)(currentBuffer)));
currentBuffer = [];
break;
}
}
default: {
currentBuffer.push(token);
}
}
innerIndex++;
}
if (currentBuffer.length > 0) {
parsedValues.push(parseExpression((0, Tokens_1.tokensToString)(currentBuffer)));
}
const errors = parsedValues.filter((part) => part.kind === "Err");
const passedValues = parsedValues.filter((part) => part.kind === "Ok");
if (errors.length > 0)
return (0, result_1.Err)(`Invalid array: ${errors
.map((error) => error.error)
.join(",")}`);
if (passedValues.length === 0) {
return (0, result_1.Ok)((0, types_1.ListValue)([]));
}
else {
return (0, result_1.Ok)((0, types_1.ListValue)(passedValues.map((value) => value.value)));
}
}
function replaceNewlinesInFormatString(str, depth) {
const lines = [];
const split = str.split("\n");
for (const line of split.slice(1, split.length - 1)) {
lines.push(deIndent(line, depth + 4));
}
return lines.join("\n");
}
function parseFormatStringValue(tokens) {
if (tokens[0].kind === "FormatStringToken") {
if (tokens[0].body.indexOf("\n") === -1) {
return (0, result_1.Ok)((0, types_1.FormatStringValue)(tokens[0].body.slice(1, -1)));
}
else {
return (0, result_1.Ok)((0, types_1.FormatStringValue)(replaceNewlinesInFormatString(tokens[0].body.slice(1, -1), tokens[0].indentLevel)));
}
}
return (0, result_1.Err)(`Expected format string literal, got ${tokens[0].kind}`);
}
function parseDestructure(tokens) {
let index = 0;
let constructor = null;
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
break;
}
case "IdentifierToken": {
constructor = token.body;
break;
}
default: {
return (0, result_1.Err)(`Expected identifier or whitespace but got ${token.kind} while parsing a destructure.`);
}
}
if (constructor)
break;
index++;
}
index++;
if (constructor === null)
return (0, result_1.Err)("Expected identifer for a destructor but got nothing.");
let patternParts = [];
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
if (patternParts.length === 0)
break;
}
default: {
patternParts.push(token);
break;
}
}
index++;
}
index++;
const pattern = (0, Tokens_1.tokensToString)(patternParts).trim();
return (0, result_1.Ok)((0, types_1.Destructure)(constructor, pattern));
}
function parseConstructor(tokens) {
let index = 0;
let constructor = null;
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
break;
}
case "IdentifierToken": {
constructor = token.body;
break;
}
default: {
return (0, result_1.Err)(`Expected identifier or whitespace but got ${token.kind} while parsing constructor.`);
}
}
if (constructor)
break;
index++;
}
index++;
if (constructor === null)
return (0, result_1.Err)("Expected identifer for a constructor but got nothing.");
let patternParts = [];
while (index < tokens.length) {
const token = tokens[index];
switch (token.kind) {
case "WhitespaceToken": {
if (patternParts.length === 0)
break;
}
default: {
patternParts.push(token);
break;
}
}
index++;
}
index++;
const pattern = parseObjectLiteral(patternParts);
if (pattern.kind === "Err")
return pattern;
return (0, result_1.Ok)((0, types_1.Constructor)(constructor, pattern.value));
}
function parseIfPredicate(tokens) {
const inbetweenTokens = [];
let state = "WaitingForIf";
for (const token of tokens) {
switch (token.kind) {
case "KeywordToken": {
if (token.body === "if") {
state = "BetweenIfAndThen";
break;
}
else if (token.body === "then") {
state = "PastThen";
break;
}
}
default: {
if (state === "BetweenIfAndThen") {
inbetweenTokens.push(token);
}
}
}
if (state === "PastThen")
break;
}
return parseExpression((0, Tokens_1.tokensToString)(inbetweenTokens));
}
function parseIfBody(tokens) {
const inbetweenTokens = [];
let state = "WaitingForThen";
for (const token of tokens) {
switch (token.kind) {
case "KeywordToken": {
if (token.body === "then") {
state = "BetweenThenAndElse";
break;
}
else if (token.body === "else") {
state = "PastElse";
break;
}
}
default: {
if (state === "BetweenThenAndElse") {
inbetweenTokens.push(token);
}
}
}
if (state === "PastElse")
break;
}
return parseExpression((0, Tokens_1.tokensToString)(inbetweenTokens));
}
function parseElseBody(tokens) {
const inbetweenTokens = [];
let state = "WaitingForElse";
for (const token of tokens) {
switch (token.kind) {
case "KeywordToken": {
if (token.body === "else") {
state = "BetweenElseAndEnd";
break;
}
}
default: {
if (state === "BetweenElseAndEnd") {
inbetweenTokens.push(token);
}
}
}
}
return parseExpression((0, Tokens_1.tokensToString)(inbetweenTokens));
}
function parseIfStatementSingleLine(body) {
const parsedPredicate = parseIfPredicate((0, Tokens_1.tokenize)(body));
const parsedIfBody = parseIfBody((0, Tokens_1.tokenize)(body));
const parsedElseBody = parseElseBody((0, Tokens_1.tokenize)(body));
const errors = [];
if (parsedPredicate.kind === "Err")
errors.push(parsedPredicate.error);
if (parsedIfBody.kind === "Err")
errors.push(parsedIfBody.error);
if (parsedElseBody.kind === "Err")
errors.push(parsedElseBody.error);
if (errors.length > 0) {
return (0, result_1.Err)(errors.join("\n"));
}
return (0, result_1.Ok)((0, types_1.IfStatement)(parsedPredicate.value, parsedIfBody.value, [], [], parsedElseBody.value, []));
}
function getIndentLevel(line) {
return line.split("").reduce((previous, current) => {
if (previous.seenText)
return previous;
if (current === " ") {
return {
seenText: previous.seenText,
indentLevel: previous.indentLevel + 1,
};
}
return {
seenText: true,
indentLevel: previous.indentLevel,
};
}, { seenText: false, indentLevel: 0 }).indentLevel;
}
function equalTokens(first, second) {
if (first.kind !== second.kind)
return false;
switch (first.kind) {
case "ArrowToken":
case "AssignToken":
case "CloseBracketToken":
case "ColonToken":
case "CloseCurlyBracesToken":
case "CommaToken":
case "CommentToken":
case "OpenBracketToken":
case "OpenCurlyBracesToken":
case "PipeToken": {
return true;
}
case "FormatStringToken": {
return first.body === second.body;
}
case "IdentifierToken": {
return first.body === second.body;
}
case "KeywordToken": {
return first.body === second.body;
}
case "LiteralToken": {
return first.body === second.body;
}
case "MultilineCommentToken": {
return first.body === second.body;
}
case "OperatorToken": {
return first.body === second.body;
}
case "StringToken": {
return first.body === second.body;
}
case "WhitespaceToken": {
return first.body === second.body;
}
}
}
function goToTheStartOfPattern(tokens, patternToFind) {
let patternTokenIndex = 0;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
const patternToken = patternToFind[patternTokenIndex];
if (equalTokens(token, patternToken)) {
if (patternTokenIndex === patternToFind.length - 1) {
return i - patternToFind.length;
}
else {
patternTokenIndex++;
}
}
else {
patternTokenIndex = 0;
}
}
return -1;
}
function goToTheEndOfPattern(tokens, patternToFind) {
let patternTokenIndex = 0;
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
const patternToken = patternToFind[patternTokenIndex];
if (equalTokens(token, patternToken)) {
if (patternTokenIndex === patternToFind.length - 1) {
return i;
}
else {
patternTokenIndex++;
}
}
else {
patternTokenIndex = 0;
}
}
return -1;
}
function deIndentTokens(tokens, level) {
return tokens.map((token) => {
if (token.kind === "WhitespaceToken") {
const lines = token.body.split("\n").map((line) => {
if (token.body.indexOf("\n") === -1) {
return line;
}
return line.slice(level, line.length);
});
return (0, Tokens_1.WhitespaceToken)({ body: lines.join("\n") });
}
return token;
});
}
function parseElseIfStatement(body) {
const tokens = (0, Tokens_1.tokenize)(body);
let startIndex = goToTheEndOfPattern(tokens, [
(0, Tokens_1.KeywordToken)({ body: "else" }),
(0, Tokens_1.WhitespaceToken)({ body: " " }),
(0, Tokens_1.KeywordToken)({ body: "if" }),
]);
if (startIndex === -1) {
return (0, result_1.Err)("Was expecting to find an else if but failed to find one.");
}
startIndex++;
let firstThenIndex = goToTheStartOfPattern(tokens.slice(startIndex, tokens.length), [(0, Tokens_1.KeywordToken)({ body: "then" })]);
if (firstThenIndex === -1) {
return (0, result_1.Err)("Was expecting to find a then after an else if but failed to find one.");
}
firstThenIndex = startIndex + firstThenIndex + 1;
const predicate = parseExpression((0, Tokens_1.tokensToString)(tokens.slice(startIndex, firstThenIndex)));
if (predicate.kind === "Err") {
return (0, result_1.Err)(`Failed to parse else if predicate due to ${predicate.error}`);
}
let letIndex = goToTheStartOfPattern(tokens.slice(firstThenIndex, tokens.length), [(0, Tokens_1.KeywordToken)({ body: "let" })]);
let letEnd = firstThenIndex + 1;
let blocks = [];
if (letIndex > -1) {
letIndex += firstThenIndex;
const whitespaceToken = tokens[letIndex];
if (whitespaceToken.kind !== "WhitespaceToken") {
return (0, result_1.Err)(`Failed to parse let..in in else if block, unexpected ${whitespaceToken.kind}`);
}
const indent = whitespaceToken.body.split("\n")[1].length;
letEnd = goToTheEndOfPattern(tokens.slice(firstThenIndex, tokens.length), [whitespaceToken, (0, Tokens_1.KeywordToken)({ body: "in" })]);
if (letEnd === -1) {
return (0, result_1.Err)("Failed to parse let..in in else if block");
}
letEnd += firstThenIndex + 1;
const unparsedBlocks = (0, Blocks_1.intoBlocks)((0, Tokens_1.tokensToString)(deIndentTokens(tokens.slice(letIndex + 2, letEnd - 1), indent + 4)));
const parsedBlocks = unparsedBlocks.map((block) => parseBlock(block));
const blockErrors = [];
for (const block of parsedBlocks) {
if (block.kind === "Err") {
blockErrors.push(block.error);
}
else {
blocks.push(block.value);
}
}
if (blockErrors.length > 0) {
return (0, result_1.Err)(`Failed to parse let..in due to: ${blockErrors.join("\n\n")}`);
}
}
const elseIfBody = blocks.length === 0
? tokens.slice(firstThenIndex + 1)
: tokens.slice(letEnd);
const parsedBody = parseExpression((0, Tokens_1.tokensToString)(elseIfBody));
if (parsedBody.kind === "Err") {
return (0, result_1.Err)(`Failed to parse else if body due to: ${parsedBody.error}`);
}
return (0, result_1.Ok)((0, types_1.ElseIfStatement)(predicate.value, parsedBody.value, blocks));
}
function parseIfStatement(body) {
const isSingleLine = body.trim().split("\n").length === 1;
if (isSingleLine) {
return parseIfStatementSingleLine(body);
}
const lines = body.split("\n").filter((line) => line.trim().length > 0);
const parsedPredicate = parseIfPredicate((0, Tokens_1.tokenize)(body));
const indentLevel = getIndentLevel(lines[0]);
const elseIfIndexes = lines.reduce((previous, currentLine, index) => {
if (currentLine.startsWith(" ".repeat(indentLevel) + "else if")) {
return {
indexes: [...previous.indexes, index],
};
}
else {
return previous;
}
}, { indexes: [] }).indexes;
const elseIndex = lines.reduce((previous, currentLine, index) => {
if (previous.found)
return previous;
if (currentLine.trimEnd() === " ".repeat(indentLevel) + "else") {
return {
found: true,
index,
};
}
else {
return previous;
}
}, { found: false, index: -1 }).index;
if (elseIndex === -1) {
return (0, result_1.Err)("Missing else block");
}
const elseIfBodies = [];
for (var i = 0; i < elseIfIndexes.length; i++) {
const index = elseIfIndexes[i];
if (i === elseIfIndexes.length - 1) {
elseIfBodies.push(lines.slice(index, elseIndex));
}
else {
elseIfBodies.push(lines.slice(index, elseIfIndexes[i + 1]));
}
}
const elseIfs = elseIfBodies.map((body) => {
return parseElseIfStatement(body.join("\n"));
});
const ifBody = elseIfIndexes.length > 0
? lines.slice(1, elseIfIndexes[0])
: lines.slice(1, elseIndex);
const ifLetStart = ifBody.findIndex((line) => line.startsWith(" ".repeat(indentLevel + 4) + "let") &&
line.endsWith("let"));
const ifLetEnd = ifBody.findIndex((line) => line.startsWith(" ".repeat(indentLevel + 4) + "i