derw
Version:
An Elm-inspired language that transpiles to TypeScript
1,798 lines (1,518 loc) • 121 kB
text/typescript
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") {