UNPKG

derw

Version:

An Elm-inspired language that transpiles to TypeScript

1,798 lines (1,518 loc) 121 kB
import { Just, Maybe, Nothing } from "@eeue56/ts-core/build/main/lib/maybe"; import { Err, Ok, Result, mapError, } from "@eeue56/ts-core/build/main/lib/result"; import { intoBlocks, typeBlocks } from "./Blocks"; import { collisions } from "./Collisions"; import { ArrowToken, CloseBracketToken, CloseCurlyBracesToken, ColonToken, FormatStringToken, IdentifierToken, KeywordToken, LiteralToken, MultilineCommentToken, OpenBracketToken, OpenCurlyBracesToken, OperatorToken, RootTypeTokens, StringToken, Token, TypeToken, WhitespaceToken, tokenize, tokenizeType, tokensToString, } from "./Tokens"; import { isBuiltinType, isReservedName } from "./builtins"; import { getValuesInTopLevelScope, validateAllCasesCovered, validateType, } from "./type_checking"; import { Addition, And, AnonFunctionArg, Block, Branch, BranchPattern, CaseStatement, Comment, Const, Constructor, ContextModule, Default, Destructure, Division, DoBlock, DoExpression, ElseIfStatement, EmptyList, Equality, Export, Expression, Field, FixedType, FormatStringValue, Function, FunctionArg, FunctionArgsUnion, FunctionCall, FunctionType, GenericType, GreaterThan, GreaterThanOrEqual, IfStatement, Impl, Import, ImportModule, InEquality, Lambda, LeftPipe, LessThan, LessThanOrEqual, ListDestructure, ListPrepend, ListRange, ListValue, Mod, Module, ModuleReference, MultilineComment, Multiplication, ObjectLiteral, Or, Property, RightPipe, StringValue, Subtraction, Tag, TagArg, Type, TypeAlias, Typeclass, TypeclassFunction, TypedBlock, UnionType, UnionUntaggedType, UnparsedBlock, Value, isLeftPipeableExpression, } from "./types"; function afterArrow(tokens: TypeToken[]): TypeToken[] { let index = 0; while (index < tokens.length) { if (tokens[index].kind !== "ArrowToken") break; index++; } return tokens.slice(index); } function splitOnArrowTokens(tokens: Token[]): Token[][] { 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: TypeToken): Result<string, Type> { switch (token.kind) { case "ArrowToken": { return 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 ( isBuiltinType(rootType.body) && rootType.body !== "any" ) { return Ok(FixedType(rootType.body, [ ])); } else if (rootType.body.toLowerCase() === rootType.body) { return Ok(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 Err(errors.join("\n")); } return Ok(FixedType(rootType.body, correct)); } return Err(`Invalid root type ${rootType.kind}`); } case "CloseBracketToken": { return 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 Err(errors.join("\n")); } return Ok(FunctionType(correct)); } case "IdentifierToken": { if (isBuiltinType(token.body) && token.body !== "any") { return Ok(FixedType(token.body, [ ])); } else if (token.body.toLowerCase() === token.body) { return Ok(GenericType(token.body)); } return Ok(FixedType(token.body, [ ])); } case "OpenBracketToken": { return Err("Unexected open bracket in type"); } case "StringToken": { return Err("Unexpected string token"); } } } function parseRootTypeTokens(token: RootTypeTokens): Result<string, Type> { switch (token.kind) { case "BaseTypeToken": { return parseTypeToken(token); } case "FunctionTypeToken": { return parseTypeToken(token); } } } function parseType(tokens: Token[]): Result<string, Type> { const tokenizedTypes = tokenizeType(tokens); if (tokenizedTypes.kind === "Err") return tokenizedTypes; if (tokenizedTypes.value.length < 1) { return Err("Expected a type but couldn't find one"); } return parseRootTypeTokens(tokenizedTypes.value[0]); } function parseUnionUntaggedType( tokens: Token[] ): Result<string, UnionUntaggedType> { if (tokens[0].kind === "KeywordToken") { if (tokens[0].body !== "type") { return 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 Err( "Failed to parse untagged union type name " + parsedType.error ); } if (parsedType.value.kind === "GenericType") { return 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 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 Err( "Expected a fixed type but got a object lieral type for a union type. Maybe you missed a captal letter?" ); } const branches: StringValue[] = [ ]; for (const token of tokens.slice(assignIndex + 1)) { switch (token.kind) { case "StringToken": { const value = parseStringValue([ token ]); if (value.kind === "Err") return 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 Err( `Expected string, whitespace, or pipe but got ${token.kind} in untagged union type definition.` ); } } } return Ok(UnionUntaggedType(parsedType.value, branches)); } function parseUnionType(tokens: Token[]): Result<string, UnionType> { if (tokens[0].kind === "KeywordToken") { if (tokens[0].body !== "type") { return Err("Expected `type` but got " + tokens[0].body); } } let typeLine: Token[] = [ ]; 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 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: Result<string, Tag>[] = branches.map((tag) => { if (tag.startsWith("|")) { tag = tag.slice(1); } tag = tag.trim(); const tagName = tag.split(" ")[0]; if (tagName.length === 0) { return Err( `Missing expected tag name for union type \`${tokensToString( typeLine )}\`` ); } if (isReservedName(tagName)) { return 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(tokenize(arg)); if (property.kind === "Err") return property; return Ok(TagArg(property.value.name, property.value.type)); }); if ( args.filter((maybeTag) => maybeTag.kind === "Ok").length === args.length ) { return Ok( Tag( tagName, args.map((arg) => (arg as Ok<TagArg>).value) ) ); } return Err( "Error parsing args due to:\n" + args .filter((arg) => arg.kind === "Err") .map((err) => (err as Err<string>).error) ); }); if (tags.length === 0) { return 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 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 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 Err( "Expected a fixed type but got a object literal type for a union type. Maybe you missed a captal letter?" ); } return Ok( UnionType( parsedType.value, tags .filter((tag) => tag.kind === "Ok") .map((tag) => (tag as Ok<Tag>).value) ) ); } function parseProperty(tokens: Token[]): Result<string, Property> { 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 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 Err("Got too many identifiers for property name"); } name = token.body; } else { return Err( `Expected identifier in property name but got ${token.kind}` ); } index++; } index++; if (name === null) { return Err("Expected identifier for property name but found nothing"); } let bitsAfterName = tokens.slice(index); if (tokens.find((token) => token.kind === "ArrowToken")) { bitsAfterName = [ OpenBracketToken({}), ...bitsAfterName, CloseBracketToken({}), ]; } const tokenizedTypes = tokenizeType(bitsAfterName); if (tokenizedTypes.kind === "Err") return tokenizedTypes; const types = tokenizedTypes.value; if (types.length > 1) { return Err("Too many types found in property"); } if (types.length < 1) return Err("Failed to find type"); const type = parseRootTypeTokens(types[0]); if (type.kind === "Err") return type; return Ok(Property(name, type.value)); } function isRootProperty(line: string): boolean { if (line.match(/ .+/)) { return true; } return false; } function parseTypeAlias(tokens: Token[]): Result<string, TypeAlias> { 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 Err("Expected `type alias` but got " + token); } } index++; } index += 2; let typeLine: Token[] = [ ]; 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 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: string[] = [ ]; recordDefinition.split("\n").forEach((line: string) => { 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: string[] = [ ]; const properties: string[] = [ ]; 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(tokenize(x))); const errors = parsedProperties.filter( (property: Result<string, Property>) => property.kind === "Err" ); if (errors.length > 0) { return Err( errors .map( (err: Result<string, Property>) => (err as Err<string>).error ) .join("\n") ); } if (aliasName.kind === "GenericType") { return 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 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 Err( "Expected a fixed type but got an object literal type for a type alias. Maybe you missed a captal letter?" ); } return Ok( TypeAlias( aliasName, parsedProperties.map((property) => (property as Ok<Property>).value) ) ); } function splitOnNewlines(tokens: Token[]): Token[][] { const lines: Token[][] = [ ]; let currentLine: Token[] = [ ]; 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: Token[], splitToken: Token): Token[][] { const pieces: Token[][] = [ ]; let currentPiece: Token[] = [ ]; 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: Token[] ): Result<string, TypeclassFunction> { const functionNameToken = tokens[0]; if (functionNameToken.kind !== "IdentifierToken") { return Err( `Expected identifier token for function name but got ${functionNameToken.kind}` ); } const functionName = functionNameToken.body; const typeSignatureIndex = goToTheEndOfPattern(tokens, [ ColonToken({}) ]); if (typeSignatureIndex === -1) { return Err("Unable to find colon in type signature"); } const typeSignature = tokens.slice(typeSignatureIndex + 1, tokens.length); const typePieces = splitOn(typeSignature, ArrowToken({})); const types = typePieces.map(parseType); const parsedTypes: Type[] = [ ]; const errors: string[] = [ ]; for (const type of types) { if (type.kind === "Err") { errors.push(type.error); } else { parsedTypes.push(type.value); } } if (errors.length > 0) { return Err(errors.join("\n")); } return Ok( TypeclassFunction( functionName, parsedTypes[parsedTypes.length - 1], parsedTypes.slice(0, parsedTypes.length - 1) ) ); } function parseTypeclass(tokens: Token[]): Result<string, Typeclass> { const typeclassNameIndex = goToTheEndOfPattern(tokens, [ KeywordToken({ body: "typeclass" }), WhitespaceToken({ body: " " }), ]); if (typeclassNameIndex === -1) return Err("Failed to find typeclass name"); const typeclassNameToken = tokens[typeclassNameIndex + 1]; if (typeclassNameToken.kind !== "IdentifierToken") { return Err( `Expected an identifier for the typeclass name but got ${typeclassNameToken.kind}` ); } const typeclassName = typeclassNameToken.body; const variableIndex = goToTheEndOfPattern(tokens, [ KeywordToken({ body: "typeclass" }), WhitespaceToken({ body: " " }), typeclassNameToken, WhitespaceToken({ body: " " }), ]); if (variableIndex === -1) return Err("Failed to find typeclass variable"); const variableToken = tokens[variableIndex + 1]; if (variableToken.kind !== "IdentifierToken") { return Err( `Expected an identifier for the typeclass variable name but got ${variableToken.kind}` ); } const variable = GenericType(variableToken.body); const functionsStartIndex = goToTheEndOfPattern(tokens, [ KeywordToken({ body: "typeclass" }), WhitespaceToken({ body: " " }), typeclassNameToken, WhitespaceToken({ body: " " }), variableToken, WhitespaceToken({ body: "\n " }), ]); const functionsAsTokens = splitOnNewlines( tokens.slice(functionsStartIndex + 1, tokens.length) ); const functions = functionsAsTokens.map(parseTypeclassFunction); const parsedFunctions: TypeclassFunction[] = [ ]; const errors: string[] = [ ]; for (const type of functions) { if (type.kind === "Err") { errors.push(type.error); } else { parsedFunctions.push(type.value); } } if (errors.length > 0) { return Err(errors.join("\n")); } return Ok(Typeclass(typeclassName, [ variable ], parsedFunctions)); } function goUpToNewline(tokens: Token[]): number { 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: Token[]): Result<string, Impl> { const implIndex = goToTheEndOfPattern(tokens, [ KeywordToken({ body: "impl" }), WhitespaceToken({ body: " " }), ]); if (implIndex === -1) return Err("Failed to find impl name"); const implNameToken = tokens[implIndex + 1]; if (implNameToken.kind !== "IdentifierToken") { return 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 = intoBlocks( tokensToString( deIndentTokens(tokens.slice(typesEndIndex + 2, tokens.length), 4) ) ); const parsedBlocks = functionBlocks.map(parseBlock); const errors: string[] = [ ]; const blocks: Function[] = [ ]; 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 Ok(Impl(implName, parsedTypes, blocks)); } function parseObjectLiteral(tokens: Token[]): Result<string, ObjectLiteral> { const fields: Field[] = [ ]; let currentName = ""; let currentValue: Expression | null = 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(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(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 = 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 Ok(ObjectLiteral(base, fields)); } function parseValue(tokens: Token[]): Result<string, Value | Constructor> { const body: string[] = [ ]; 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 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 Ok(Value(body.join(""))); } function parseStringValue(tokens: Token[]): Result<string, StringValue> { if (tokens[0].kind === "StringToken") return Ok(StringValue(tokens[0].body.slice(1, -1))); return Err(`Expected string literal, got ${tokens[0].kind}`); } function listRangeDotsNotWithinString(tokens: Token[]): boolean { 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 = 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: Token[]): Result<string, ListRange> { 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(tokenize(pieces[0])); const end = parseValue(tokenize(pieces[1])); if (start.kind === "Err") return start; if (end.kind === "Err") return end; if (start.kind === "Ok" && start.value.kind === "Constructor") return Err("Expected variable but got constructor"); if (end.kind === "Ok" && end.value.kind === "Constructor") return Err("Expected variable but got constructor"); return Ok(ListRange(start.value as Value, end.value as Value)); } default: { return Err( `Unxpected ${token.kind}, expected whitespace or literal` ); } } index++; } return Err("Failed to find list range. They should look like [1..2]"); } function parseListValue(tokens: Token[]): Result<string, ListValue> { 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 = tokensToString(tokens.slice(index)); const trimmed = body.trim(); const innerBody = trimmed.slice(1, trimmed.length - 1).trim(); if (innerBody.trim().length === 0) return Ok(ListValue([ ])); const innerTokens = 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(OpenCurlyBracesToken({})); depth++; break; } case "CloseCurlyBracesToken": { currentBuffer.push(CloseCurlyBracesToken({})); depth--; break; } case "CommaToken": { if (depth === 0) { parsedValues.push( parseExpression(tokensToString(currentBuffer)) ); currentBuffer = [ ]; break; } } default: { currentBuffer.push(token); } } innerIndex++; } if (currentBuffer.length > 0) { parsedValues.push(parseExpression(tokensToString(currentBuffer))); } const errors = parsedValues.filter((part) => part.kind === "Err"); const passedValues = parsedValues.filter((part) => part.kind === "Ok"); if (errors.length > 0) return Err( `Invalid array: ${errors .map((error) => (error as Err<string>).error) .join(",")}` ); if (passedValues.length === 0) { return Ok(ListValue([ ])); } else { return Ok( ListValue( passedValues.map((value) => (value as Ok<Expression>).value) ) ); } } function replaceNewlinesInFormatString(str: string, depth: number): string { const lines: string[] = [ ]; 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: Token[] ): Result<string, FormatStringValue> { if (tokens[0].kind === "FormatStringToken") { if (tokens[0].body.indexOf("\n") === -1) { return Ok(FormatStringValue(tokens[0].body.slice(1, -1))); } else { return Ok( FormatStringValue( replaceNewlinesInFormatString( tokens[0].body.slice(1, -1), tokens[0].indentLevel ) ) ); } } return Err(`Expected format string literal, got ${tokens[0].kind}`); } function parseDestructure(tokens: Token[]): Result<string, Destructure> { 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 Err( `Expected identifier or whitespace but got ${token.kind} while parsing a destructure.` ); } } if (constructor) break; index++; } index++; if (constructor === null) return 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 = tokensToString(patternParts).trim(); return Ok(Destructure(constructor, pattern)); } function parseConstructor(tokens: Token[]): Result<string, Constructor> { 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 Err( `Expected identifier or whitespace but got ${token.kind} while parsing constructor.` ); } } if (constructor) break; index++; } index++; if (constructor === null) return 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 Ok(Constructor(constructor, pattern.value)); } function parseIfPredicate(tokens: Token[]): Result<string, Expression> { const inbetweenTokens = [ ]; let state: "WaitingForIf" | "BetweenIfAndThen" | "PastThen" = "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(tokensToString(inbetweenTokens)); } function parseIfBody(tokens: Token[]): Result<string, Expression> { const inbetweenTokens = [ ]; let state: "WaitingForThen" | "BetweenThenAndElse" | "PastElse" = "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(tokensToString(inbetweenTokens)); } function parseElseBody(tokens: Token[]): Result<string, Expression> { const inbetweenTokens = [ ]; let state: "WaitingForElse" | "BetweenElseAndEnd" | "PastEnd" = "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(tokensToString(inbetweenTokens)); } function parseIfStatementSingleLine(body: string): Result<string, IfStatement> { const parsedPredicate = parseIfPredicate(tokenize(body)); const parsedIfBody = parseIfBody(tokenize(body)); const parsedElseBody = parseElseBody(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 Err(errors.join("\n")); } return Ok( IfStatement( (parsedPredicate as Ok<Expression>).value, (parsedIfBody as Ok<Expression>).value, [ ], [ ], (parsedElseBody as Ok<Expression>).value, [ ] ) ); } function getIndentLevel(line: string): number { 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: Token, second: Token): boolean { 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 as FormatStringToken).body; } case "IdentifierToken": { return first.body === (second as IdentifierToken).body; } case "KeywordToken": { return first.body === (second as KeywordToken).body; } case "LiteralToken": { return first.body === (second as LiteralToken).body; } case "MultilineCommentToken": { return first.body === (second as MultilineCommentToken).body; } case "OperatorToken": { return first.body === (second as OperatorToken).body; } case "StringToken": { return first.body === (second as StringToken).body; } case "WhitespaceToken": { return first.body === (second as WhitespaceToken).body; } } } function goToTheStartOfPattern( tokens: Token[], patternToFind: Token[] ): number { 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: Token[], patternToFind: Token[]): number { 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: Token[], level: number): Token[] { return tokens.map((token: Token): Token => { if (token.kind === "WhitespaceToken") { const lines = token.body.split("\n").map((line): string => { if (token.body.indexOf("\n") === -1) { return line; } return line.slice(level, line.length); }); return WhitespaceToken({ body: lines.join("\n") }); } return token; }); } function parseElseIfStatement(body: string): Result<string, ElseIfStatement> { const tokens = tokenize(body); let startIndex = goToTheEndOfPattern(tokens, [ KeywordToken({ body: "else" }), WhitespaceToken({ body: " " }), KeywordToken({ body: "if" }), ]); if (startIndex === -1) { return Err("Was expecting to find an else if but failed to find one."); } startIndex++; let firstThenIndex = goToTheStartOfPattern( tokens.slice(startIndex, tokens.length), [ KeywordToken({ body: "then" }) ] ); if (firstThenIndex === -1) { return Err( "Was expecting to find a then after an else if but failed to find one." ); } firstThenIndex = startIndex + firstThenIndex + 1; const predicate = parseExpression( tokensToString(tokens.slice(startIndex, firstThenIndex)) ); if (predicate.kind === "Err") { return Err( `Failed to parse else if predicate due to ${predicate.error}` ); } let letIndex = goToTheStartOfPattern( tokens.slice(firstThenIndex, tokens.length), [ KeywordToken({ body: "let" }) ] ); let letEnd = firstThenIndex + 1; let blocks: Block[] = [ ]; if (letIndex > -1) { letIndex += firstThenIndex; const whitespaceToken = tokens[letIndex]; if (whitespaceToken.kind !== "WhitespaceToken") { return 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, KeywordToken({ body: "in" }) ] ); if (letEnd === -1) { return Err("Failed to parse let..in in else if block"); } letEnd += firstThenIndex + 1; const unparsedBlocks = intoBlocks( tokensToString( deIndentTokens( tokens.slice(letIndex + 2, letEnd - 1), indent + 4 ) ) ); const parsedBlocks = unparsedBlocks.map((block) => parseBlock(block)); const blockErrors: string[] = [ ]; for (const block of parsedBlocks) { if (block.kind === "Err") {