Skip to content

Commit

Permalink
[VOI-98] Simplify the mechanisms behind cloning and parents (#44)
Browse files Browse the repository at this point in the history
* WIP

* Add classes and move stuff around

* WIP

* Apply to block

* Update snapshots

* WIP

* Cleanup

* Better map cloning?

* Remove attributes for a sec

* Overoptimize

* Add attributes

* Cleanup cloning logic

* Cleanup maybe

* Simplify child list

* Simplify list

* Cleanup

* Cleanup

* Use attributes
  • Loading branch information
drew-y authored Sep 13, 2024
1 parent 0a59dae commit f9cc66b
Show file tree
Hide file tree
Showing 31 changed files with 483 additions and 289 deletions.
2 changes: 1 addition & 1 deletion src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const compileModule = (opts: CompileExprOpts<VoidModule>) => {
const compileBlock = (opts: CompileExprOpts<Block>) => {
return opts.mod.block(
null,
opts.expr.body.toArray().map((expr, index, array) => {
opts.expr.body.map((expr, index, array) => {
if (index === array.length - 1) {
return compileExpression({ ...opts, expr, isReturnExpr: true });
}
Expand Down
20 changes: 11 additions & 9 deletions src/lib/fast-shift-array.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { at } from "vitest/dist/chunks/reporters.C_zwCd4j.js";

// TODO: Add map, filter, reduce, amd findIndex
export class FastShiftArray<T> {
private items: T[];
Expand All @@ -18,15 +20,6 @@ export class FastShiftArray<T> {
return value;
}

unshift(...items: T[]): number {
if (items.length === 0) return this.length;
this.headIndex = Math.max(this.headIndex - items.length, 0);
for (let i = 0; i < items.length; i++) {
this.items[this.headIndex + i] = items[i];
}
return this.length;
}

push(...items: T[]): number {
this.items.push(...items);
return this.length;
Expand All @@ -37,6 +30,11 @@ export class FastShiftArray<T> {
return this.items.pop();
}

unshift(...items: T[]): number {
this.items.splice(this.headIndex, 0, ...items);
return this.length;
}

at(index: number): T | undefined {
if (index < 0) {
index = this.length + index;
Expand Down Expand Up @@ -85,4 +83,8 @@ export class FastShiftArray<T> {
this.items.splice(0, this.headIndex);
this.headIndex = 0;
}

forEach(callbackfn: (value: T, index: number, array: T[]) => void): void {
this.items.slice(this.headIndex).forEach(callbackfn);
}
}
2 changes: 1 addition & 1 deletion src/parser/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const isInfixOpIdentifier = (op?: Identifier) =>
export const isOp = (op?: Expr): boolean => isInfixOp(op) || isPrefixOp(op);

export const prefixOps: OpMap = new Map([
["#", 7],
["#", 0],
["&", 7],
["!", 7],
["~", 7],
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parse-chars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const parseChars = (

if (token.is("(")) {
const subList = parseChars(file, { nested: true });
subList.mayBeTuple = true;
subList.setAttribute("tuple?", true);
list.push(subList);
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/parser/reader-macros/comment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { noop } from "../../syntax-objects/index.js";
import { nop } from "../../syntax-objects/index.js";
import { ReaderMacro } from "./types.js";

export const comment: ReaderMacro = {
Expand All @@ -9,6 +9,6 @@ export const comment: ReaderMacro = {
file.consumeChar();
}

return noop();
return nop();
},
};
107 changes: 68 additions & 39 deletions src/parser/syntax-macros/functional-notation.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,97 @@
import { idIs, isOp } from "../grammar.js";
import { Expr, List } from "../../syntax-objects/index.js";
import { Expr, List, ListValue } from "../../syntax-objects/index.js";

export const functionalNotation = (list: List): List => {
const array = list.toArray();
let isTuple = false;

const result = list.mapFilter((expr, index, array) => {
if (expr.isList()) return functionalNotation(expr);
if (expr.isWhitespace()) return expr;
const { result } = array.reduce(
(acc, expr, index) => {
if (acc.skip > 0) {
acc.skip--;
return acc;
}

const nextExpr = array[index + 1];
if (nextExpr && nextExpr.isList() && !(isOp(expr) || idIs(expr, ","))) {
return processFnCall(expr, nextExpr, array, index);
}
if (expr.isList()) {
acc.result.push(functionalNotation(expr));
return acc;
}

if (list.mayBeTuple && idIs(expr, ",")) {
isTuple = true;
}
if (expr.isWhitespace()) {
acc.result.push(expr);
return acc;
}

return expr;
});
const nextExpr = array[index + 1];

if (isTuple) {
result.insert("tuple");
result.insert(",");
}
if (nextExpr && nextExpr.isList() && !(isOp(expr) || idIs(expr, ","))) {
return handleNextExpression(acc, expr, nextExpr, array, index);
}

return result;
if (list.getAttribute("tuple?") && idIs(expr, ",")) {
isTuple = true;
}

acc.result.push(expr);
return acc;
},
{ result: [], skip: 0 } as Accumulator
);

return finalizeResult(result, isTuple);
};

const processFnCall = (
type Accumulator = { result: ListValue[]; skip: number };

const handleNextExpression = (
acc: Accumulator,
expr: Expr,
nextExpr: List,
nextExpr: Expr,
array: Expr[],
index: number
): List => {
if (nextExpr.calls("generics")) {
return processGenerics(expr, array, index);
) => {
if ((nextExpr as List).calls("generics")) {
const generics = nextExpr as List;
const nextNextExpr = array[index + 2];
if (nextNextExpr && nextNextExpr.isList()) {
acc.result.push(processGenerics(expr, generics, nextNextExpr as List));
acc.skip = 2; // Skip next two expressions
} else {
acc.result.push(processGenerics(expr, generics));
acc.skip = 1; // Skip next expression
}
} else {
acc.result.push(processParamList(expr, nextExpr as List));
acc.skip = 1; // Skip next expression
}

return processParamList(expr, array, index);
return acc;
};

const processGenerics = (expr: Expr, array: Expr[], index: number): List => {
const generics = array.splice(index + 1, 1)[0] as List;
generics.mayBeTuple = false;
const finalizeResult = (result: ListValue[], isTuple: boolean): List => {
if (isTuple) {
result.unshift(",");
result.unshift("tuple");
}
return new List(result);
};

const list = array[index + 1]?.isList()
? (array.splice(index + 1, 1)[0] as List)
: new List({});
const processGenerics = (expr: Expr, generics: List, params?: List): List => {
generics.setAttribute("tuple?", false);

list.insert(",");
const list = params || new List([]);
list.insert(expr);
list.mayBeTuple = false;
list.insert(",", 1);
list.setAttribute("tuple?", false);
const functional = functionalNotation(list);

functional.insert(functionalNotation(generics), 2);
functional.insert(",", 3);
return functional;
};

const processParamList = (expr: Expr, array: Expr[], index: number): List => {
const list = array.splice(index + 1, 1)[0] as List;
list.insert(expr);
list.insert(",", 1);
list.mayBeTuple = false;
return functionalNotation(list);
const processParamList = (expr: Expr, params: List): List => {
params.insert(expr);
params.insert(",", 1);
params.setAttribute("tuple?", false);
return functionalNotation(params);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ exports[`regular macro expansion 1`] = `
[
"exports",
[
"std#730",
"std#729",
],
],
[
Expand Down Expand Up @@ -47,7 +47,7 @@ exports[`regular macro expansion 1`] = `
],
[
"regular-macro",
"\`#759",
"\`#758",
[
"parameters",
],
Expand All @@ -68,7 +68,7 @@ exports[`regular macro expansion 1`] = `
],
[
"regular-macro",
"let#811",
"let#805",
[
"parameters",
],
Expand Down Expand Up @@ -121,7 +121,7 @@ exports[`regular macro expansion 1`] = `
],
[
"regular-macro",
"fn#1462",
"fn#1408",
[
"parameters",
],
Expand Down
4 changes: 2 additions & 2 deletions src/semantics/check-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
List,
Expr,
noop,
nop,
Identifier,
ObjectType,
Type,
Expand All @@ -22,7 +22,7 @@ import { getExprType } from "./resolution/get-expr-type.js";
import { typesAreEquivalent } from "./resolution/index.js";

export const checkTypes = (expr: Expr | undefined): Expr => {
if (!expr) return noop();
if (!expr) return nop();
if (expr.isBlock()) return checkBlockTypes(expr);
if (expr.isCall()) return checkCallTypes(expr);
if (expr.isFn()) return checkFnTypes(expr);
Expand Down
4 changes: 2 additions & 2 deletions src/semantics/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const registerModule = ({
}

if (!existingModule && (name === "index" || name === "mod")) {
parentModule.unshift(...ast.toArray());
parentModule.unshift(...ast.toArray().reverse());
return;
}

Expand All @@ -73,7 +73,7 @@ const registerModule = ({
}

if (existingModule && !rest.length) {
module.unshift(...ast.toArray());
module.unshift(...ast.toArray().reverse());
}

if (!rest.length) return;
Expand Down
9 changes: 4 additions & 5 deletions src/semantics/regular-macros.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getIdStr } from "../syntax-objects/get-id-str.js";
import { getIdStr } from "../syntax-objects/lib/get-id-str.js";
import {
Bool,
Expr,
Expand All @@ -14,6 +14,7 @@ import {
VoidModule,
Block,
Use,
nop,
} from "../syntax-objects/index.js";
import {
registerExports,
Expand Down Expand Up @@ -256,15 +257,15 @@ const functions: Record<string, MacroFn | undefined> = {
},
quote: (quote: List) => {
const expand = (body: List): List =>
body.mapFilter((exp) => {
body.flatMap((exp) => {
if (exp.isList() && exp.calls("$")) {
const val = exp.at(1) ?? nop();
return evalMacroExpr(val);
}

if (exp.isList() && exp.calls("$@")) {
const val = exp.at(1) ?? nop();
return (evalMacroExpr(val) as List).insert("splice_quote");
return (evalMacroExpr(val) as List).toArray();
}

if (exp.isList()) return expand(exp);
Expand Down Expand Up @@ -385,8 +386,6 @@ const handleOptionalConditionParenthesis = (expr: Expr): Expr => {
return expr;
};

const nop = () => new List({}).push(Identifier.from("splice_quote"));

/** Binary logical comparison */
const bl = (args: List, fn: (l: any, r: any) => boolean) => {
// TODO Assertions / validation
Expand Down
7 changes: 2 additions & 5 deletions src/semantics/resolution/resolve-fn-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,14 @@ export const resolveFnTypes = (fn: Fn, call?: Call): Fn => {

const resolveParameters = (params: Parameter[]) => {
params.forEach((p) => {
if (p.type) {
return;
}
if (p.type) return;

if (!p.typeExpr) {
throw new Error(`Unable to determine type for ${p}`);
}

p.typeExpr = resolveTypes(p.typeExpr);
const type = getExprType(p.typeExpr);
p.type = type;
p.type = getExprType(p.typeExpr);
});
};

Expand Down
4 changes: 2 additions & 2 deletions src/semantics/resolution/resolve-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Block } from "../../syntax-objects/block.js";
import { Expr } from "../../syntax-objects/expr.js";
import { noop } from "../../syntax-objects/helpers.js";
import { nop } from "../../syntax-objects/helpers.js";
import { List } from "../../syntax-objects/list.js";
import { VoidModule } from "../../syntax-objects/module.js";
import { ObjectLiteral } from "../../syntax-objects/object-literal.js";
Expand All @@ -22,7 +22,7 @@ import { resolveUse } from "./resolve-use.js";
* Returned tree not guaranteed to be same as supplied tree
*/
export const resolveTypes = (expr: Expr | undefined): Expr => {
if (!expr) return noop();
if (!expr) return nop();
if (expr.isBlock()) return resolveBlockTypes(expr);
if (expr.isCall()) return resolveCallTypes(expr);
if (expr.isFn()) return resolveFnTypes(expr);
Expand Down
2 changes: 1 addition & 1 deletion src/semantics/resolution/resolve-use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const resolveExport = (call: Call) => {
const block = call.argAt(0);
if (!block?.isBlock()) return call;

const entities = block.body.toArray().map(resolveTypes);
const entities = block.body.map(resolveTypes);
registerExports(call, entities, resolveModuleTypes);

return call;
Expand Down
19 changes: 19 additions & 0 deletions src/syntax-objects/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Syntax Objects

Syntax objects are data structures that represent concepts within the
language. Such as functions, function calls, variables etc.

# Guidelines

- Each Syntax Object should be part of the `Expr` union
- Syntax objects must track their parent-child relationship. A child typically
belongs to a parent when it was directly defined with the parent. I.E.
parameters of functions, expressions / variables of block. These parent
relationships are stored via the parent pointer and must stay up to date
during clones. Use `ChildList` and `ChildMap` data types to help keep
make this easier.
- Resolved values should not be considered a child of the expression they
were resolved from. The type expression (`typeExpr`) of a parameter is
a child of the parameter, but the type it resolves to should never
accidentally be marked as a child of the parameter. Nor should it be
included in the clone (instead, they type resolution can be run again)
Loading

0 comments on commit f9cc66b

Please sign in to comment.