Skip to content

Commit

Permalink
add support for case statements
Browse files Browse the repository at this point in the history
  • Loading branch information
eeue56 committed Jul 20, 2021
1 parent 2ea9720 commit 126169c
Show file tree
Hide file tree
Showing 5 changed files with 507 additions and 7 deletions.
79 changes: 75 additions & 4 deletions src/blocks.ts
@@ -1,25 +1,94 @@
import {
Just,
map,
Maybe,
Nothing,
withDefault,
} from "@eeue56/ts-core/build/main/lib/maybe";

function isFunctionOrConst(line: string): boolean {
return !line.startsWith("type ") && !line.startsWith(" ");
}

function isType(line: string): boolean {
return line.startsWith("type ");
}

function getPreviousRootLine(lines: string[]): Maybe<string> {
for (var i = lines.length - 1; i >= 0; i--) {
const isIndented = lines[i].startsWith(" ");

if (!isIndented) {
return Just(lines[i]);
}
}
return Nothing();
}

export function intoBlocks(body: string): string[] {
const blocks = [ ];

let currentBlock = [ ];
let currentBlock: string[] = [ ];
let isInBlock = false;
const lines = body.split("\n");
const registeredLines = [ ];

for (var i = 0; i < lines.length; i++) {
const line = lines[i];
const isPreviousFunctionDef =
i === 0 ? false : isFunctionOrConst(lines[i - 1]);
// empty line
if (line.trim().length === 0) continue;

// very first line
const isFirstLine = registeredLines.length === 0;

// get the previous line with 0 indentation
const previousRootLine: Maybe<string> = isFirstLine
? Nothing()
: getPreviousRootLine(registeredLines.map((i) => lines[i]));

// was the previous root line a type definition?
const isPreviousLineATypeDef = withDefault(
false,
map((line) => isType(line), previousRootLine)
);

// was the previous root line a function definition?
const isPreviousLineAFunctionDef = withDefault(
false,
map((line) => isFunctionOrConst(line), previousRootLine)
);

// is the current line a function or a constant?
const inFunctionDef = isFunctionOrConst(line);

// is the current line a type definition?
const isTypeDef = isType(line);

// is the line indented, i.e not a root line
const isIndentedLine = line.startsWith(" ");

if (isInBlock) {
if (isIndentedLine || (inFunctionDef && isPreviousFunctionDef)) {
// if we have a new type def being made
if (isTypeDef && isPreviousLineATypeDef) {
blocks.push(currentBlock.join("\n").trim());

currentBlock = [ line ];
isInBlock = true;
} else if (
isIndentedLine ||
(inFunctionDef &&
isPreviousLineAFunctionDef &&
!isPreviousLineATypeDef)
) {
currentBlock.push(line);
} else if (isPreviousLineATypeDef) {
blocks.push(currentBlock.join("\n").trim());

currentBlock = [ line ];
isInBlock = true;
} else {
blocks.push(currentBlock.join("\n").trim());

currentBlock = [ ];
isInBlock = false;
}
Expand All @@ -29,6 +98,8 @@ export function intoBlocks(body: string): string[] {
isInBlock = true;
}
}

registeredLines.push(i);
}

if (currentBlock.length > 0) {
Expand Down
36 changes: 36 additions & 0 deletions src/generator.ts
Expand Up @@ -9,6 +9,9 @@ import {
IfStatement,
Type,
FixedType,
CaseStatement,
Branch,
Constructor,
} from "./types";

function prefixLines(body: string, indent: number): string {
Expand Down Expand Up @@ -87,6 +90,35 @@ function generateIfStatement(ifStatement: IfStatement): string {
}`;
}

function generateConstructor(constructor: Constructor): string {
// TODO: This should be handled in the parser.
const innerArguments = constructor.pattern
.split("{")
.slice(1)
.join("{")
.split("}")[0]
.trim();
return `${constructor.constructor}(${innerArguments})`;
}

function generateBranch(predicate: string, branch: Branch): string {
return `case "${branch.pattern.constructor}": {
const ${branch.pattern.pattern} = ${predicate};
return ${generateExpression(branch.body)};
}`;
}

function generateCaseStatement(caseStatement: CaseStatement): string {
const predicate = generateExpression(caseStatement.predicate);
const branches = caseStatement.branches.map((branch) =>
generateBranch(predicate, branch)
);

return `switch (${predicate}.kind) {
${prefixLines(branches.join("\n"), 4)}
}`.trim();
}

function generateType(type_: Type): string {
switch (type_.kind) {
case "GenericType": {
Expand All @@ -111,6 +143,10 @@ function generateExpression(expression: Expression): string {
return generateValue(expression);
case "IfStatement":
return generateIfStatement(expression);
case "CaseStatement":
return generateCaseStatement(expression);
case "Constructor":
return generateConstructor(expression);
}
}

Expand Down
132 changes: 130 additions & 2 deletions src/parser.ts
Expand Up @@ -17,6 +17,10 @@ import {
FunctionArg,
FixedType,
GenericType,
CaseStatement,
Branch,
Destructure,
Constructor,
} from "./types";

export function blockKind(block: string): Result<string, BlockKinds> {
Expand Down Expand Up @@ -139,6 +143,22 @@ function parseValue(body: string): Result<string, Value> {
}
}

function parseDestructure(body: string): Result<string, Destructure> {
body = body.trim();
const constructor = body.split(" ")[0];
const pattern = body.split(" ").slice(1).join(" ");

return Ok(Destructure(constructor, pattern));
}

function parseConstructure(body: string): Result<string, Constructor> {
body = body.trim();
const constructor = body.split(" ")[0];
const pattern = body.split(" ").slice(1).join(" ");

return Ok(Constructor(constructor, pattern));
}

function parseIfStatementSingleLine(body: string): Result<string, IfStatement> {
const predicate = body.split("then")[0].split("if")[1];
const ifBody = body.split("then")[1].split("else")[0];
Expand Down Expand Up @@ -244,18 +264,126 @@ function parseIfStatement(body: string): Result<string, IfStatement> {
);
}

function parseCaseStatement(body: string): Result<string, CaseStatement> {
body = body
.split("\n")
.filter((l) => l.trim().length > 0)
.join("\n");

const rootIndentLevel = getIndentLevel(body.split("\n")[0]);
const casePredicate = parseExpression(
body.split("case ")[1].split(" of")[0]
);
const lines = body.split("\n");

let branches = [ ];
let branchPattern = "";
let branchLines: string[] = [ ];

for (var i = 1; i < lines.length; i++) {
const line = lines[i];
const indent = getIndentLevel(line);

if (rootIndentLevel + 4 === indent) {
if (branchPattern === "") {
branchPattern = line.split("->")[0];
branchLines.push(line.split("->")[1]);
}

const branchExpression = parseExpression(branchLines.join("\n"));

const parsedBranchPattern = parseDestructure(branchPattern);

if (
branchExpression.kind === "err" ||
parsedBranchPattern.kind === "err"
) {
if (branchExpression.kind === "err")
branches.push(branchExpression);
if (parsedBranchPattern.kind === "err")
branches.push(parsedBranchPattern);
} else {
branches.push(
Ok(
Branch(
(parsedBranchPattern as Ok<Destructure>).value,
(branchExpression as Ok<Expression>).value
)
)
);
}

branchPattern = "";
branchLines = [ ];
} else {
branchLines.push(line);
}
}

if (branchLines.length > 0) {
const branchExpression = parseExpression(branchLines.join("\n"));

const parsedBranchPattern = parseDestructure(branchPattern);

if (
branchExpression.kind === "err" ||
parsedBranchPattern.kind === "err"
) {
if (branchExpression.kind === "err")
branches.push(branchExpression);
if (parsedBranchPattern.kind === "err")
branches.push(parsedBranchPattern);
} else {
branches.push(
Ok(
Branch(
(parsedBranchPattern as Ok<Destructure>).value,
(branchExpression as Ok<Expression>).value
)
)
);
}
}

const errors = [ ];
if (casePredicate.kind === "err") errors.push(casePredicate.error);
branches.forEach((branch) => {
if (branch.kind === "err") {
errors.push(branch.error);
}
});

if (errors.length > 0) {
return Err(errors.join("\n"));
}

const validBranches = branches.map((value) => (value as Ok<Branch>).value);

return Ok(
CaseStatement((casePredicate as Ok<Expression>).value, validBranches)
);
}

function parseExpression(body: string): Result<string, Expression> {
if (body.trim().startsWith("if ")) {
return parseIfStatement(body);
} else if (body.trim().startsWith("case ")) {
return parseCaseStatement(body);
} else if (body.trim().split(" ").length === 1) {
return parseValue(body);
} else {
const firstChar = body.trim().slice(0, 1);
if (firstChar.toUpperCase() === firstChar) {
return parseConstructure(body);
}
}
return Err("No expression found.");

return Err(`No expression found: '${body}'`);
}

function parseFunction(block: string): Result<string, Function> {
const typeLine = block.split("\n")[0];
const functionName = typeLine.split(":")[0];
const functionName = typeLine.split(":")[0].trim();
const types = typeLine
.split(":")
.slice(1)
Expand Down

0 comments on commit 126169c

Please sign in to comment.