UNPKG

derw

Version:

An Elm-inspired language that transpiles to TypeScript

1,398 lines 114 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseExpression = parseExpression; exports.parseBlock = parseBlock; exports.stripComments = stripComments; exports.parse = parse; exports.addTypeErrors = addTypeErrors; exports.parseWithContext = parseWithContext; 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 =