From e8ba7ab549124087ff53a668f2333c0a5a4a7b07 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Wed, 11 Sep 2024 21:09:35 -0700 Subject: [PATCH 01/18] WIP --- .../__snapshots__/parser.test.ts.snap | 36 ++++ .../__tests__/fixtures/desugarred-ast.ts | 178 ------------------ src/parser/__tests__/fixtures/void-file.ts | 6 + src/parser/grammar.ts | 3 +- .../syntax-macros/interpret-whitespace.ts | 12 +- src/syntax-objects/types.ts | 2 + 6 files changed, 55 insertions(+), 182 deletions(-) delete mode 100644 src/parser/__tests__/fixtures/desugarred-ast.ts diff --git a/src/parser/__tests__/__snapshots__/parser.test.ts.snap b/src/parser/__tests__/__snapshots__/parser.test.ts.snap index c598e8f8..96141541 100644 --- a/src/parser/__tests__/__snapshots__/parser.test.ts.snap +++ b/src/parser/__tests__/__snapshots__/parser.test.ts.snap @@ -186,6 +186,42 @@ exports[`parser can parse a file into a syntax expanded ast 1`] = ` 10, 3, ], + [ + "mul", + [ + "&", + "self", + ], + [ + ":", + "other", + [ + "Vec", + [ + "generics", + "T", + ], + ], + ], + ], + [ + "mul", + [ + "&", + "self", + ], + [ + ":", + "other", + [ + "Vec", + [ + "generics", + "T", + ], + ], + ], + ], [ "let", [ diff --git a/src/parser/__tests__/fixtures/desugarred-ast.ts b/src/parser/__tests__/fixtures/desugarred-ast.ts deleted file mode 100644 index 70d1f7e7..00000000 --- a/src/parser/__tests__/fixtures/desugarred-ast.ts +++ /dev/null @@ -1,178 +0,0 @@ -export const desugarredAst = [ - "ast", - ["use", ["::", ["::", "std", "macros"], "all"]], - [ - "use", - ["::", ["::", "std", "io"], ["object", "read", [":", "write", "io_write"]]], - ], - [ - "fn", - ["fib", [":", "n", "i32"]], - "->", - "i32", - [ - "block", - [ - "if", - ["<=", "n", 1], - [":", "then", ["block", "n"]], - [ - ":", - "else", - ["block", ["+", ["fib", ["-", "n", 1]], ["fib", ["-", "n", 2]]]], - ], - ], - ], - ], - [ - "macro_let", - [ - "=", - "extract_parameters", - [ - "=>", - ["definitions"], - ["block", ["concat", ["`", "parameters"], ["slice", "definitions", 1]]], - ], - ], - ], - [ - "if", - [">", "x", 10], - [":", "then", ["block", 10]], - [":", "else", ["block", 20]], - ], - [ - "reduce", - "array", - 0, - 1, - 2, - [":", "hey", ["=>", [], ["block", ["log", "val"], ["+", "acc", "val"]]]], - [":", "with", ["=>", [], 0]], - ], - ["+", 10, 3], - [ - "let", - [ - "=", - "a", - [ - "*", - [ - "+", - [ - "reduce", - "array", - 0, - ["=>", ["acc", "val"], ["block", ["+", "acc", "val"]]], - ], - 10, - ], - 3, - ], - ], - ], - [ - "let", - [ - "=", - "x", - ["my_func", ["add", 1, 2], ["=>", [], ["block", ["hello"]]], ["+", 3, 4]], - ], - ], - [ - "closure_param_test", - 1, - ["=>", [], "a"], - 3, - ["=>", [], ["block", ["hey", "there"]]], - 4, - ["=>", [], 5], - ["=>", [], ["block", 6]], - ["=>", [], ["block", 7]], - 8, - ], - ["let", ["=", ["tuple", "x", "y"], ["tuple", 1, 2]]], - ["+", ["Array", ["generics", "Hey", "There"], 1, 2, 3], 3], - ["obj", ["Test", ["generics", "T"]], ["object", [":", "c", "i32"]]], - ["fn", ["test", ["generics", "T"], [":", "a", 1]], "->", "i32"], - [ - "fn", - ["main"], - [ - "block", - [ - "let", - ["=", "a", ["+", ["...", ["hey", "test"]], ["now", ["&", "other"]]]], - ], - ["let", ["=", "x", ["+", 10, ["block", ["+", 20, 30]]]]], - [ - "let", - [ - "=", - "y", - [ - "if", - [">", "x", 10], - [":", "then", ["block", 10]], - [":", "else", ["block", 20]], - ], - ], - ], - [ - "let", - [ - "=", - "n", - [ - "block", - [ - "if", - [">", ["len", "args"], 1], - [ - ":", - "then", - [ - "block", - ["log", "console", ["string", "Hey there!"]], - ["unwrap", ["parseInt", ["at", "args", 1]]], - ], - ], - [":", "else", ["block", 10]], - ], - ], - ], - ], - ["let", ["=", "x2", 10]], - ["let", ["=", "z", ["nothing"]]], - ["let", ["=", "a", ["boop", "hello", 1]]], - ["let", ["=", "test_spacing", ["fib", "n"]]], - ["let", ["=", "result", ["fib", "n"]]], - ["let", ["=", "x", ["&", "hey"]]], - ["$", "hey"], - ["$@", ["hey"]], - ["$", ["hey"]], - ["$", ["extract", "equals_expr", 2]], - ["block", ["$", "body"]], - ["+", "x", 5], - ["+", ["+", "x", "y"], 10], - ["+", ["*", "x", "y"], 10], - ["x"], - "x", - [ - "let", - [ - "=", - "vec", - [ - "object", - [":", "x", 10], - [":", "y", ["Point", ["object", [":", "x", 10], [":", "y", 20]]]], - [":", "z", ["object", [":", "a", 10], [":", "b", 20]]], - ], - ], - ], - ], - ], -]; diff --git a/src/parser/__tests__/fixtures/void-file.ts b/src/parser/__tests__/fixtures/void-file.ts index 4c303633..61a664ab 100644 --- a/src/parser/__tests__/fixtures/void-file.ts +++ b/src/parser/__tests__/fixtures/void-file.ts @@ -24,6 +24,12 @@ with: () => 0 10 + 3 +mul(&self, other: Vec) +mul( + &self, + other: Vec +) + let a = array .reduce(0) (acc, val) => acc + val diff --git a/src/parser/grammar.ts b/src/parser/grammar.ts index aca8f619..acdd9c91 100644 --- a/src/parser/grammar.ts +++ b/src/parser/grammar.ts @@ -86,12 +86,13 @@ 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], ["%", 7], ["$", 7], + ["@", 7], ["$@", 7], ["...", 5], ]); diff --git a/src/parser/syntax-macros/interpret-whitespace.ts b/src/parser/syntax-macros/interpret-whitespace.ts index e5e582e6..9ce6e29c 100644 --- a/src/parser/syntax-macros/interpret-whitespace.ts +++ b/src/parser/syntax-macros/interpret-whitespace.ts @@ -4,10 +4,12 @@ import { Expr, List } from "../../syntax-objects/index.js"; export const interpretWhitespace = (list: List, indentLevel?: number): List => { const transformed = new List({ ...list.metadata }); + let hadComma = false; while (list.hasChildren) { const child = elideParens(list, indentLevel); if (child?.isList() && child.length === 0) continue; - addSibling(child, transformed); + addSibling(child, transformed, hadComma); + hadComma = nextIsComma(list); } if (transformed.length === 1 && transformed.first()?.isList()) { @@ -166,6 +168,10 @@ const consumeLeadingWhitespace = (list: List) => { const isNewline = (v?: Expr) => v?.isWhitespace() && v.isNewline; const isIndent = (v: Expr) => v.isWhitespace() && v.isIndent; +const nextIsComma = (list: List) => { + const next = list.first(); + return !!(next?.isIdentifier() && next.is(",")); +}; const isNamedParameter = (v: List) => { const identifier = v.at(0); @@ -184,10 +190,10 @@ const isNamedParameter = (v: List) => { return true; }; -const addSibling = (child: Expr, siblings: List) => { +const addSibling = (child: Expr, siblings: List, hadComma?: boolean) => { const olderSibling = siblings.at(-1); - if (!child.isList()) { + if (!child.isList() || hadComma) { siblings.push(child); return; } diff --git a/src/syntax-objects/types.ts b/src/syntax-objects/types.ts index bc2df8be..d05ae591 100644 --- a/src/syntax-objects/types.ts +++ b/src/syntax-objects/types.ts @@ -3,6 +3,7 @@ import { Parameter } from "./parameter.js"; import { NamedEntity, NamedEntityOpts } from "./named-entity.js"; import { Id, Identifier } from "./identifier.js"; import { getIdStr } from "./get-id-str.js"; +import { LexicalContext } from "./lexical-context.js"; export type Type = | PrimitiveType @@ -162,6 +163,7 @@ export type ObjectField = { name: string; typeExpr: Expr; type?: Type }; export class ObjectType extends BaseType { readonly kindOfType = "object"; + namespace: LexicalContext = new LexicalContext(); typeParameters?: Identifier[]; appliedTypeArgs?: Type[]; genericInstances?: ObjectType[]; From b4aa4db9bfa807a271f4f66aaefc395c9e932e1b Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Thu, 12 Sep 2024 23:00:03 -0700 Subject: [PATCH 02/18] Add classes and move stuff around --- src/semantics/regular-macros.ts | 2 +- src/syntax-objects/README.md | 19 ++ src/syntax-objects/call.ts | 2 +- src/syntax-objects/lib/child-list.ts | 210 ++++++++++++++++++ src/syntax-objects/lib/child-map.ts | 37 +++ src/syntax-objects/{ => lib}/get-id-str.ts | 2 +- .../{ => lib}/lexical-context.ts | 6 +- src/syntax-objects/list.ts | 2 +- src/syntax-objects/module.ts | 2 +- src/syntax-objects/named-entity.ts | 2 +- src/syntax-objects/scoped-entity.ts | 2 +- src/syntax-objects/syntax.ts | 18 +- src/syntax-objects/types.ts | 4 +- 13 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 src/syntax-objects/README.md create mode 100644 src/syntax-objects/lib/child-list.ts create mode 100644 src/syntax-objects/lib/child-map.ts rename src/syntax-objects/{ => lib}/get-id-str.ts (77%) rename src/syntax-objects/{ => lib}/lexical-context.ts (90%) diff --git a/src/semantics/regular-macros.ts b/src/semantics/regular-macros.ts index b94d0d49..96f4ef3d 100644 --- a/src/semantics/regular-macros.ts +++ b/src/semantics/regular-macros.ts @@ -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, diff --git a/src/syntax-objects/README.md b/src/syntax-objects/README.md new file mode 100644 index 00000000..386530bb --- /dev/null +++ b/src/syntax-objects/README.md @@ -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) diff --git a/src/syntax-objects/call.ts b/src/syntax-objects/call.ts index 7005df0e..e6fbfe70 100644 --- a/src/syntax-objects/call.ts +++ b/src/syntax-objects/call.ts @@ -1,7 +1,7 @@ import { Expr } from "./expr.js"; import { Fn } from "./fn.js"; import { Identifier } from "./identifier.js"; -import { LexicalContext } from "./lexical-context.js"; +import { LexicalContext } from "./lib/lexical-context.js"; import { List } from "./list.js"; import { Syntax, SyntaxMetadata } from "./syntax.js"; import { ObjectType, Type } from "./types.js"; diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts new file mode 100644 index 00000000..72044913 --- /dev/null +++ b/src/syntax-objects/lib/child-list.ts @@ -0,0 +1,210 @@ +import { FastShiftArray } from "../../lib/fast-shift-array.js"; +import { Expr } from "../expr.js"; +import { Float } from "../float.js"; +import { Id, Identifier } from "../identifier.js"; +import { Int } from "../int.js"; +import { List, ListValue } from "../list.js"; +import { NamedEntity } from "../named-entity.js"; +import { getIdStr } from "./get-id-str.js"; + +export type ChildListValue = Expr | string | number | ChildListValue[]; + +export class ChildList { + private store: FastShiftArray = new FastShiftArray(); + private parent: Expr; + + constructor(children: ChildListValue[] = [], parent: Expr) { + this.push(...children); + this.parent = parent; + } + + get children() { + return this.store.toArray(); + } + + get hasChildren() { + return !!this.store.length; + } + + get length() { + return this.store.length; + } + + at(index: number): Expr | undefined { + return this.store.at(index); + } + + exprAt(index: number): Expr { + const expr = this.store.at(index); + if (!expr) { + throw new Error(`No expr at ${index}`); + } + return expr; + } + + identifierAt(index: number): Identifier { + const id = this.at(index); + if (!id?.isIdentifier()) { + throw new Error(`No identifier at index ${index}`); + } + return id; + } + + optionalIdentifierAt(index: number): Identifier | undefined { + const id = this.at(index); + if (id?.isIdentifier()) { + return id; + } + } + + listAt(index: number): List { + const id = this.at(index); + if (!id?.isList()) { + throw new Error(`No list at index ${index}`); + } + return id; + } + + set(index: number, expr: Expr | string) { + const result = typeof expr === "string" ? Identifier.from(expr) : expr; + result.parent = this.parent; + this.store.set(index, result); + return this; + } + + calls(fnId: Id, atIndex = 0) { + return this.getIdStrAt(atIndex) === getIdStr(fnId); + } + + getIdStrAt(index: number): string | undefined { + const v = this.at(index); + return v?.isIdentifier() || v?.isStringLiteral() ? v.value : undefined; + } + + consume(): Expr { + const next = this.store.shift(); + if (!next) throw new Error("No remaining expressions"); + return next; + } + + first(): Expr | undefined { + return this.store.at(0); + } + + last(): Expr | undefined { + return this.store.at(-1); + } + + /** Returns all but the first element in an array */ + argsArray(): Expr[] { + return this.store.toArray().slice(1); + } + + pop(): Expr | undefined { + return this.store.pop(); + } + + push(...expr: ListValue[]) { + expr.forEach((ex) => { + if (typeof ex === "string") { + this.store.push(new Identifier({ value: ex, parent: this.parent })); + return; + } + + if (typeof ex === "number" && Number.isInteger(ex)) { + this.store.push(new Int({ value: ex, parent: this.parent })); + return; + } + + if (typeof ex === "number") { + this.store.push(new Float({ value: ex, parent: this.parent })); + return; + } + + if (ex instanceof Array) { + this.push(new List({ value: ex, parent: this.parent })); + return; + } + + ex.parent = this.parent; + + if (ex instanceof NamedEntity) { + this.parent.registerEntity(ex); + } + + if (ex.isList() && ex.calls("splice_quote")) { + this.store.push(...ex.argsArray()); + return; + } + + this.store.push(ex); + }); + + return this; + } + + findIndex(cb: (expr: Expr) => boolean) { + return this.toArray().findIndex(cb); + } + + insert(expr: Expr | string, at = 0) { + const result = typeof expr === "string" ? Identifier.from(expr) : expr; + result.parent = this.parent; + this.store.splice(at, 0, result); + return this; + } + + remove(index: number, count = 1) { + this.store.splice(index, count); + return this; + } + + filter(fn: (expr: Expr, index: number, array: Expr[]) => boolean): ChildList { + return new ChildList(this.toArray().filter(fn), this.parent); + } + + each(fn: (expr: Expr, index: number, array: Expr[]) => void): ChildList { + this.toArray().forEach(fn); + return this; + } + + map(fn: (expr: Expr, index: number, array: Expr[]) => Expr): ChildList { + return new ChildList(this.toArray().map(fn), this.parent); + } + + /** Like a regular map, but omits undefined values returned from the mapper */ + mapFilter( + fn: (expr: Expr, index: number, array: Expr[]) => Expr | undefined + ): ChildList { + const list = new ChildList([], this.parent); + return this.toArray().reduce((newList: ChildList, expr, index, array) => { + if (!expr) return newList; + const result = fn(expr, index, array); + if (!result) return newList; + return newList.push(result); + }, list); + } + + slice(start?: number, end?: number): ChildList { + return new ChildList(this.store.slice(start, end), this.parent); + } + + sliceAsArray(start?: number, end?: number) { + return this.store.slice(start, end); + } + + toArray() { + return this.store.toArray(); + } + + toJSON() { + return this.toArray(); + } + + clone(parent?: Expr) { + return new ChildList( + this.toArray().map((expr) => expr.clone()), + parent ?? this.parent + ); + } +} diff --git a/src/syntax-objects/lib/child-map.ts b/src/syntax-objects/lib/child-map.ts new file mode 100644 index 00000000..8900ba4f --- /dev/null +++ b/src/syntax-objects/lib/child-map.ts @@ -0,0 +1,37 @@ +import { Expr } from "../expr.js"; + +export class ChildMap { + private map: T; + private parent: Expr; + + constructor(map: T, parent: Expr) { + this.parent = parent; + this.map = Object.fromEntries( + Object.entries(map).map(([key, value]) => { + value.parent = parent; + return [key, value]; + }) + ) as T; + } + + get(key: keyof T): T[keyof T] { + return this.map[key]; + } + + set(key: keyof T, value: T[keyof T]) { + this.map[key] = value; + value.parent = this.parent; + } + + toJSON() { + return this.map; + } + + clone(parent?: Expr) { + parent = parent ?? this.parent; + const newMap: T = Object.fromEntries( + Object.entries(this.map).map(([key, value]) => [key, value.clone(parent)]) + ) as T; + return new ChildMap(newMap, parent); + } +} diff --git a/src/syntax-objects/get-id-str.ts b/src/syntax-objects/lib/get-id-str.ts similarity index 77% rename from src/syntax-objects/get-id-str.ts rename to src/syntax-objects/lib/get-id-str.ts index dd54decd..837754da 100644 --- a/src/syntax-objects/get-id-str.ts +++ b/src/syntax-objects/lib/get-id-str.ts @@ -1,4 +1,4 @@ -import { Id } from "./identifier.js"; +import { Id } from "../identifier.js"; export const getIdStr = (id: Id) => { if (!id) { diff --git a/src/syntax-objects/lexical-context.ts b/src/syntax-objects/lib/lexical-context.ts similarity index 90% rename from src/syntax-objects/lexical-context.ts rename to src/syntax-objects/lib/lexical-context.ts index ecb7eb0d..27825ffa 100644 --- a/src/syntax-objects/lexical-context.ts +++ b/src/syntax-objects/lib/lexical-context.ts @@ -1,7 +1,7 @@ -import type { Fn } from "./fn.js"; +import type { Fn } from "../fn.js"; import { getIdStr } from "./get-id-str.js"; -import type { Id } from "./identifier.js"; -import { NamedEntity } from "./named-entity.js"; +import type { Id } from "../identifier.js"; +import { NamedEntity } from "../named-entity.js"; export class LexicalContext { private readonly fns: Map = new Map(); diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 47d2e603..91f1530a 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -1,7 +1,7 @@ import { FastShiftArray } from "../lib/fast-shift-array.js"; import { Expr } from "./expr.js"; import { Float } from "./float.js"; -import { getIdStr } from "./get-id-str.js"; +import { getIdStr } from "./lib/get-id-str.js"; import { Id, Identifier } from "./identifier.js"; import { Int } from "./int.js"; import { NamedEntity } from "./named-entity.js"; diff --git a/src/syntax-objects/module.ts b/src/syntax-objects/module.ts index e6ccd263..f57cc86f 100644 --- a/src/syntax-objects/module.ts +++ b/src/syntax-objects/module.ts @@ -2,7 +2,7 @@ import { Expr } from "./expr.js"; import { Float } from "./float.js"; import { Id, Identifier } from "./identifier.js"; import { Int } from "./int.js"; -import { LexicalContext } from "./lexical-context.js"; +import { LexicalContext } from "./lib/lexical-context.js"; import { List, ListValue } from "./list.js"; import { NamedEntity, diff --git a/src/syntax-objects/named-entity.ts b/src/syntax-objects/named-entity.ts index ea8e8b9e..a8698de9 100644 --- a/src/syntax-objects/named-entity.ts +++ b/src/syntax-objects/named-entity.ts @@ -1,6 +1,6 @@ import { Expr } from "./expr.js"; import { Id, Identifier } from "./identifier.js"; -import { LexicalContext } from "./lexical-context.js"; +import { LexicalContext } from "./lib/lexical-context.js"; import { Syntax, SyntaxMetadata } from "./syntax.js"; export type NamedEntityOpts = SyntaxMetadata & { diff --git a/src/syntax-objects/scoped-entity.ts b/src/syntax-objects/scoped-entity.ts index 33d3a073..afcf32a6 100644 --- a/src/syntax-objects/scoped-entity.ts +++ b/src/syntax-objects/scoped-entity.ts @@ -1,5 +1,5 @@ import { Expr } from "./expr.js"; -import { LexicalContext } from "./lexical-context.js"; +import { LexicalContext } from "./lib/lexical-context.js"; import { Syntax, SyntaxMetadata } from "./syntax.js"; export type ScopedEntity = Expr & { diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index 07580c97..6c0ea3c1 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -8,7 +8,7 @@ import type { Global } from "./global.js"; import type { Id, Identifier } from "./identifier.js"; import type { Int } from "./int.js"; import { type VoidModule } from "./module.js"; -import { LexicalContext } from "./lexical-context.js"; +import { LexicalContext } from "./lib/lexical-context.js"; import type { List } from "./list.js"; import type { MacroLambda } from "./macro-lambda.js"; import type { MacroVariable } from "./macro-variable.js"; @@ -35,12 +35,14 @@ import { Match } from "./match.js"; export type SyntaxMetadata = { location?: SourceLocation; parent?: Expr; + attributes?: Map; }; export abstract class Syntax { /** For tagged unions */ abstract readonly syntaxType: string; readonly syntaxId = getSyntaxId(); + private attributes: Map; location?: SourceLocation; parent?: Expr; @@ -48,6 +50,7 @@ export abstract class Syntax { const { location, parent } = metadata; this.location = location; this.parent = parent; + this.attributes = metadata.attributes ?? new Map(); } get parentFn(): Fn | undefined { @@ -62,6 +65,7 @@ export abstract class Syntax { return { location: this.location, parent: this.parent, + attributes: structuredClone(this.attributes), }; } @@ -126,6 +130,18 @@ export abstract class Syntax { /** Should emit in compliance with core language spec */ abstract toJSON(): unknown; + setAttribute(key: string, value: unknown) { + this.attributes.set(key, value); + } + + getAttribute(key: string): unknown { + return this.attributes.get(key); + } + + hasAttribute(key: string): boolean { + return this.attributes.has(key); + } + isScopedEntity(): this is ScopedEntity { return (this as unknown as ScopedEntity).lexicon instanceof LexicalContext; } diff --git a/src/syntax-objects/types.ts b/src/syntax-objects/types.ts index d05ae591..96efeeba 100644 --- a/src/syntax-objects/types.ts +++ b/src/syntax-objects/types.ts @@ -2,8 +2,8 @@ import { Expr } from "./expr.js"; import { Parameter } from "./parameter.js"; import { NamedEntity, NamedEntityOpts } from "./named-entity.js"; import { Id, Identifier } from "./identifier.js"; -import { getIdStr } from "./get-id-str.js"; -import { LexicalContext } from "./lexical-context.js"; +import { getIdStr } from "./lib/get-id-str.js"; +import { LexicalContext } from "./lib/lexical-context.js"; export type Type = | PrimitiveType From 29b8ffdbc50bd8d44ab95990421df5d45894b1cc Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 01:10:23 -0700 Subject: [PATCH 03/18] WIP --- src/lib/fast-shift-array.ts | 4 + src/semantics/resolution/resolve-fn-type.ts | 7 +- src/syntax-objects/block.ts | 17 ++- src/syntax-objects/call.ts | 16 +-- src/syntax-objects/fn.ts | 90 ++++++--------- src/syntax-objects/identifier.ts | 6 +- src/syntax-objects/lib/child-list.ts | 118 +++++++++----------- src/syntax-objects/lib/child-map.ts | 37 ------ src/syntax-objects/lib/child.ts | 44 ++++++++ 9 files changed, 156 insertions(+), 183 deletions(-) delete mode 100644 src/syntax-objects/lib/child-map.ts create mode 100644 src/syntax-objects/lib/child.ts diff --git a/src/lib/fast-shift-array.ts b/src/lib/fast-shift-array.ts index 08887d20..651c1fd7 100644 --- a/src/lib/fast-shift-array.ts +++ b/src/lib/fast-shift-array.ts @@ -85,4 +85,8 @@ export class FastShiftArray { 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); + } } diff --git a/src/semantics/resolution/resolve-fn-type.ts b/src/semantics/resolution/resolve-fn-type.ts index 09c2b475..ac0e8981 100644 --- a/src/semantics/resolution/resolve-fn-type.ts +++ b/src/semantics/resolution/resolve-fn-type.ts @@ -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); }); }; diff --git a/src/syntax-objects/block.ts b/src/syntax-objects/block.ts index 12a29b2c..2deeabb5 100644 --- a/src/syntax-objects/block.ts +++ b/src/syntax-objects/block.ts @@ -1,11 +1,12 @@ import { Expr } from "./expr.js"; +import { Child } from "./lib/child.js"; import { List } from "./list.js"; import { ScopedSyntax, ScopedSyntaxMetadata } from "./scoped-entity.js"; import { Type } from "./types.js"; export class Block extends ScopedSyntax { readonly syntaxType = "block"; - private _body!: List; + #body: Child; type?: Type; constructor( @@ -15,9 +16,9 @@ export class Block extends ScopedSyntax { } ) { super(opts); - this.body = - opts.body instanceof Array ? new List({ value: opts.body }) : opts.body; - this.type = opts.type; + const { body, type } = opts; + this.#body = new Child(body instanceof Array ? new List(body) : body, this); + this.type = type; } get children() { @@ -25,15 +26,11 @@ export class Block extends ScopedSyntax { } get body() { - return this._body; + return this.#body.value; } set body(body: List) { - if (body) { - body.parent = this; - } - - this._body = body; + this.#body.value = body; } lastExpr() { diff --git a/src/syntax-objects/call.ts b/src/syntax-objects/call.ts index e6fbfe70..81a66b08 100644 --- a/src/syntax-objects/call.ts +++ b/src/syntax-objects/call.ts @@ -13,7 +13,7 @@ export class Call extends Syntax { fnName: Identifier; args: List; typeArgs?: List; - _type?: Type; + #type?: Type; constructor( opts: SyntaxMetadata & { @@ -32,7 +32,7 @@ export class Call extends Syntax { this.args.parent = this; this.typeArgs = opts.typeArgs; if (this.typeArgs) this.typeArgs.parent = this; - this._type = opts.type; + this.#type = opts.type; } get children() { @@ -40,19 +40,19 @@ export class Call extends Syntax { } set type(type: Type | undefined) { - this._type = type; + this.#type = type; } get type() { - if (!this._type && this.fn?.isFn()) { - this._type = this.fn.returnType; + if (!this.#type && this.fn?.isFn()) { + this.#type = this.fn.returnType; } - if (!this._type && this.fn?.isObjectType()) { - this._type = this.fn; + if (!this.#type && this.fn?.isObjectType()) { + this.#type = this.fn; } - return this._type; + return this.#type; } eachArg(fn: (expr: Expr) => void) { diff --git a/src/syntax-objects/fn.ts b/src/syntax-objects/fn.ts index 636c8a0a..ff9553f4 100644 --- a/src/syntax-objects/fn.ts +++ b/src/syntax-objects/fn.ts @@ -1,5 +1,7 @@ import type { Expr } from "./expr.js"; import { Identifier } from "./identifier.js"; +import { ChildList } from "./lib/child-list.js"; +import { Child } from "./lib/child.js"; import { ScopedNamedEntity, ScopedNamedEntityOpts } from "./named-entity.js"; import { Parameter } from "./parameter.js"; import { FnType, Type } from "./types.js"; @@ -7,83 +9,72 @@ import { Variable } from "./variable.js"; export class Fn extends ScopedNamedEntity { readonly syntaxType = "fn"; + readonly #parameters = new ChildList([], this); + readonly #body = new Child(undefined, this); + readonly #returnTypeExpr = new Child(undefined, this); + readonly #genericInstances = new ChildList([], this); + #typeParams = new ChildList([], this); variables: Variable[] = []; - _parameters: Parameter[] = []; - typeParameters?: Identifier[]; - appliedTypeArgs?: Type[] = []; - /** When a function has generics, resolved versions of the functions go here */ - genericInstances?: Fn[] = []; - returnType?: Type; - _returnTypeExpr?: Expr; + returnType?: Type; // When a function has generics, resolved versions of the functions go here inferredReturnType?: Type; annotatedReturnType?: Type; + appliedTypeArgs?: Type[] = []; typesResolved?: boolean; - private _body?: Expr; constructor( opts: ScopedNamedEntityOpts & { - returnType?: Type; returnTypeExpr?: Expr; variables?: Variable[]; - parameters: Parameter[]; + parameters?: Parameter[]; typeParameters?: Identifier[]; - genericInstances?: Fn[]; body?: Expr; } ) { super(opts); - this.returnType = opts.returnType; - this.parameters = opts.parameters ?? []; - this.variables = opts.variables ?? []; - this.typeParameters = opts.typeParameters; - this.genericInstances = opts.genericInstances; + this.#parameters.push(...(opts.parameters ?? [])); + this.#typeParams.push(...(opts.typeParameters ?? [])); this.returnTypeExpr = opts.returnTypeExpr; + this.variables = opts.variables ?? []; this.body = opts.body; } get body() { - return this._body; + return this.#body.value; } set body(body: Expr | undefined) { - if (body) { - body.parent = this; - } - - this._body = body; + this.#body.value = body; } get parameters() { - return this._parameters; - } - - set parameters(parameters: Parameter[]) { - this._parameters = parameters; - parameters.forEach((p) => { - p.parent = this; - this.registerEntity(p); - }); + return this.#parameters.toArray(); } get returnTypeExpr() { - return this._returnTypeExpr; + return this.#returnTypeExpr.value; } set returnTypeExpr(returnTypeExpr: Expr | undefined) { - if (returnTypeExpr) { - returnTypeExpr.parent = this; - } + this.#returnTypeExpr.value = returnTypeExpr; + } - this._returnTypeExpr = returnTypeExpr; + get genericInstances() { + const instances = this.#genericInstances.toArray(); + return !instances.length ? undefined : instances; + } + + get typeParameters() { + const params = this.#typeParams.toArray(); + return !params.length ? undefined : params; + } + + set typeParameters(params: Identifier[] | undefined) { + this.#typeParams = new ChildList(params ?? [], this); } // Register a version of this function with resolved generics registerGenericInstance(fn: Fn) { - if (!this.genericInstances) { - this.genericInstances = []; - } - - this.genericInstances.push(fn); + this.#genericInstances.push(fn); } getNameStr(): string { @@ -127,15 +118,6 @@ export class Fn extends ScopedNamedEntity { ); } - registerLocal(local: Variable | Parameter) { - if (local.syntaxType === "variable") { - this.variables.push(local); - return; - } - - this.parameters.push(local); - } - toString() { return this.id; } @@ -144,11 +126,11 @@ export class Fn extends ScopedNamedEntity { // Don't clone generic instances return new Fn({ ...super.getCloneOpts(parent), - variables: this.variables.map((v) => v.clone()), - parameters: this.parameters.map((p) => p.clone()), + variables: this.variables, returnTypeExpr: this.returnTypeExpr?.clone(), + parameters: this.#parameters.clone().toArray(), + typeParameters: this.#typeParams.clone().toArray(), body: this.body?.clone(), - typeParameters: this.typeParameters?.map((tp) => tp.clone()), }); } @@ -157,7 +139,7 @@ export class Fn extends ScopedNamedEntity { "fn", this.id, ["parameters", ...this.parameters], - ["type-parameters", ...(this.typeParameters ?? [])], + ["type-parameters", ...(this.#typeParams.toArray() ?? [])], ["return-type", this.returnType], this.body, ]; diff --git a/src/syntax-objects/identifier.ts b/src/syntax-objects/identifier.ts index 6b0c21f7..d528e5ef 100644 --- a/src/syntax-objects/identifier.ts +++ b/src/syntax-objects/identifier.ts @@ -75,17 +75,17 @@ export class Identifier extends Syntax { } export class MockIdentifier extends Identifier { - private readonly _entity?: NamedEntity; + readonly #entity?: NamedEntity; constructor( opts: IdentifierOpts & { entity?: NamedEntity; // The entity this identifier resolves to } ) { super(opts); - this._entity = opts.entity; + this.#entity = opts.entity; } resolve() { - return this._entity; + return this.#entity; } } diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts index 72044913..ae0d3b2e 100644 --- a/src/syntax-objects/lib/child-list.ts +++ b/src/syntax-objects/lib/child-list.ts @@ -1,21 +1,28 @@ import { FastShiftArray } from "../../lib/fast-shift-array.js"; import { Expr } from "../expr.js"; -import { Float } from "../float.js"; import { Id, Identifier } from "../identifier.js"; -import { Int } from "../int.js"; -import { List, ListValue } from "../list.js"; +import { List } from "../list.js"; import { NamedEntity } from "../named-entity.js"; import { getIdStr } from "./get-id-str.js"; -export type ChildListValue = Expr | string | number | ChildListValue[]; +export class ChildList { + private store: FastShiftArray = new FastShiftArray(); + #parent: Expr; -export class ChildList { - private store: FastShiftArray = new FastShiftArray(); - private parent: Expr; - - constructor(children: ChildListValue[] = [], parent: Expr) { + constructor(children: T[] = [], parent: Expr) { + this.#parent = parent; this.push(...children); - this.parent = parent; + } + + get parent() { + return this.#parent; + } + + set parent(parent: Expr) { + this.#parent = parent; + this.store.forEach((expr) => { + expr.parent = parent; + }); } get children() { @@ -65,10 +72,13 @@ export class ChildList { return id; } - set(index: number, expr: Expr | string) { - const result = typeof expr === "string" ? Identifier.from(expr) : expr; - result.parent = this.parent; - this.store.set(index, result); + set(index: number, expr: T) { + expr.parent = this.parent; + if (expr instanceof NamedEntity) { + this.parent.registerEntity(expr); + } + + this.store.set(index, expr); return this; } @@ -81,62 +91,36 @@ export class ChildList { return v?.isIdentifier() || v?.isStringLiteral() ? v.value : undefined; } - consume(): Expr { + consume(): T { const next = this.store.shift(); if (!next) throw new Error("No remaining expressions"); return next; } - first(): Expr | undefined { + first(): T | undefined { return this.store.at(0); } - last(): Expr | undefined { + last(): T | undefined { return this.store.at(-1); } /** Returns all but the first element in an array */ - argsArray(): Expr[] { + argsArray(): T[] { return this.store.toArray().slice(1); } - pop(): Expr | undefined { + pop(): T | undefined { return this.store.pop(); } - push(...expr: ListValue[]) { + push(...expr: T[]) { expr.forEach((ex) => { - if (typeof ex === "string") { - this.store.push(new Identifier({ value: ex, parent: this.parent })); - return; - } - - if (typeof ex === "number" && Number.isInteger(ex)) { - this.store.push(new Int({ value: ex, parent: this.parent })); - return; - } - - if (typeof ex === "number") { - this.store.push(new Float({ value: ex, parent: this.parent })); - return; - } - - if (ex instanceof Array) { - this.push(new List({ value: ex, parent: this.parent })); - return; - } - ex.parent = this.parent; - if (ex instanceof NamedEntity) { this.parent.registerEntity(ex); } - if (ex.isList() && ex.calls("splice_quote")) { - this.store.push(...ex.argsArray()); - return; - } - this.store.push(ex); }); @@ -147,10 +131,9 @@ export class ChildList { return this.toArray().findIndex(cb); } - insert(expr: Expr | string, at = 0) { - const result = typeof expr === "string" ? Identifier.from(expr) : expr; - result.parent = this.parent; - this.store.splice(at, 0, result); + insert(expr: T, at = 0) { + expr.parent = this.parent; + this.store.splice(at, 0, expr); return this; } @@ -159,33 +142,36 @@ export class ChildList { return this; } - filter(fn: (expr: Expr, index: number, array: Expr[]) => boolean): ChildList { + filter(fn: (expr: T, index: number, array: T[]) => boolean): ChildList { return new ChildList(this.toArray().filter(fn), this.parent); } - each(fn: (expr: Expr, index: number, array: Expr[]) => void): ChildList { + each(fn: (expr: T, index: number, array: T[]) => void): ChildList { this.toArray().forEach(fn); return this; } - map(fn: (expr: Expr, index: number, array: Expr[]) => Expr): ChildList { + map(fn: (expr: T, index: number, array: T[]) => T): ChildList { return new ChildList(this.toArray().map(fn), this.parent); } /** Like a regular map, but omits undefined values returned from the mapper */ mapFilter( - fn: (expr: Expr, index: number, array: Expr[]) => Expr | undefined - ): ChildList { + fn: (expr: T, index: number, array: T[]) => T | undefined + ): ChildList { const list = new ChildList([], this.parent); - return this.toArray().reduce((newList: ChildList, expr, index, array) => { - if (!expr) return newList; - const result = fn(expr, index, array); - if (!result) return newList; - return newList.push(result); - }, list); + return this.toArray().reduce( + (newList: ChildList, expr, index, array) => { + if (!expr) return newList; + const result = fn(expr, index, array); + if (!result) return newList; + return newList.push(result); + }, + list + ); } - slice(start?: number, end?: number): ChildList { + slice(start?: number, end?: number): ChildList { return new ChildList(this.store.slice(start, end), this.parent); } @@ -193,7 +179,7 @@ export class ChildList { return this.store.slice(start, end); } - toArray() { + toArray(): T[] { return this.store.toArray(); } @@ -201,9 +187,9 @@ export class ChildList { return this.toArray(); } - clone(parent?: Expr) { - return new ChildList( - this.toArray().map((expr) => expr.clone()), + clone(parent?: Expr): ChildList { + return new ChildList( + this.toArray().map((expr) => expr.clone()) as T[], parent ?? this.parent ); } diff --git a/src/syntax-objects/lib/child-map.ts b/src/syntax-objects/lib/child-map.ts deleted file mode 100644 index 8900ba4f..00000000 --- a/src/syntax-objects/lib/child-map.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Expr } from "../expr.js"; - -export class ChildMap { - private map: T; - private parent: Expr; - - constructor(map: T, parent: Expr) { - this.parent = parent; - this.map = Object.fromEntries( - Object.entries(map).map(([key, value]) => { - value.parent = parent; - return [key, value]; - }) - ) as T; - } - - get(key: keyof T): T[keyof T] { - return this.map[key]; - } - - set(key: keyof T, value: T[keyof T]) { - this.map[key] = value; - value.parent = this.parent; - } - - toJSON() { - return this.map; - } - - clone(parent?: Expr) { - parent = parent ?? this.parent; - const newMap: T = Object.fromEntries( - Object.entries(this.map).map(([key, value]) => [key, value.clone(parent)]) - ) as T; - return new ChildMap(newMap, parent); - } -} diff --git a/src/syntax-objects/lib/child.ts b/src/syntax-objects/lib/child.ts new file mode 100644 index 00000000..7082f129 --- /dev/null +++ b/src/syntax-objects/lib/child.ts @@ -0,0 +1,44 @@ +import { Expr } from "../expr.js"; +import { NamedEntity } from "../named-entity.js"; + +/** Use to store a child of a syntax object. Will keep track of parent */ +export class Child { + #value: T; + #parent: Expr; + + constructor(value: T, parent: Expr) { + this.#parent = parent; + this.#value = value; + if (this.#value) this.#value.parent = parent; + } + + get parent() { + return this.#parent; + } + + set parent(parent: Expr) { + this.#parent = parent; + if (this.#value) this.#value.parent = parent; + } + + get value() { + return this.#value; + } + + set value(value: T) { + if (value) { + if (value instanceof NamedEntity) this.#parent.registerEntity(value); + value.parent = this.#parent; + } + + this.#value = value; + } + + toJSON() { + return this.#value?.toJSON(); + } + + clone(parent?: Expr) { + return new Child(this.#value?.clone(parent), parent ?? this.#parent); + } +} From e1043428e51ce4db4880624a97131088618dceaf Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 01:21:34 -0700 Subject: [PATCH 04/18] Apply to block --- src/assembler.ts | 2 +- src/semantics/resolution/resolve-use.ts | 2 +- src/syntax-objects/block.ts | 28 ++++++++++++------------- src/syntax-objects/lib/child-list.ts | 7 +++++++ 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/assembler.ts b/src/assembler.ts index 2d9af90c..9adb2cb9 100644 --- a/src/assembler.ts +++ b/src/assembler.ts @@ -118,7 +118,7 @@ const compileModule = (opts: CompileExprOpts) => { const compileBlock = (opts: CompileExprOpts) => { 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 }); } diff --git a/src/semantics/resolution/resolve-use.ts b/src/semantics/resolution/resolve-use.ts index b38a443b..09a4afb7 100644 --- a/src/semantics/resolution/resolve-use.ts +++ b/src/semantics/resolution/resolve-use.ts @@ -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; diff --git a/src/syntax-objects/block.ts b/src/syntax-objects/block.ts index 2deeabb5..748b0b73 100644 --- a/src/syntax-objects/block.ts +++ b/src/syntax-objects/block.ts @@ -1,12 +1,12 @@ import { Expr } from "./expr.js"; -import { Child } from "./lib/child.js"; +import { ChildList } from "./lib/child-list.js"; import { List } from "./list.js"; import { ScopedSyntax, ScopedSyntaxMetadata } from "./scoped-entity.js"; import { Type } from "./types.js"; export class Block extends ScopedSyntax { readonly syntaxType = "block"; - #body: Child; + #body = new ChildList(undefined, this); type?: Type; constructor( @@ -17,52 +17,50 @@ export class Block extends ScopedSyntax { ) { super(opts); const { body, type } = opts; - this.#body = new Child(body instanceof Array ? new List(body) : body, this); + this.#body.push(...(body instanceof Array ? body : body.toArray())); this.type = type; } get children() { - return this.body.toArray(); + return this.#body.toArray(); } get body() { - return this.#body.value; + return this.#body.toArray(); } - set body(body: List) { - this.#body.value = body; + set body(body: Expr[]) { + this.#body = new ChildList(body, this); } lastExpr() { - return this.body.last(); + return this.body.at(-1); } each(fn: (expr: Expr, index: number, array: Expr[]) => Expr) { - this.body.each(fn); + this.body.forEach(fn); return this; } /** Sets the parent on each element immediately before the mapping of the next */ applyMap(fn: (expr: Expr, index: number, array: Expr[]) => Expr) { - const body = this.body; - this.body = new List({ ...this.body.metadata, value: [] }); - body.each((expr, index, array) => this.body.push(fn(expr, index, array))); + this.#body.applyMap(fn); return this; } /** Calls the evaluator function on the block's body and returns the result of the last evaluation. */ evaluate(evaluator: (expr: Expr) => Expr): Expr | undefined { - return this.body.map(evaluator).last(); + return this.body.map(evaluator).at(-1); } toJSON() { - return ["block", ...this.body.toJSON()]; + return ["block", ...this.body]; } clone(parent?: Expr) { return new Block({ ...this.getCloneOpts(parent), - body: this.body.clone(), + body: this.#body.clone().toArray(), }); } } diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts index ae0d3b2e..d5c27d1d 100644 --- a/src/syntax-objects/lib/child-list.ts +++ b/src/syntax-objects/lib/child-list.ts @@ -155,6 +155,13 @@ export class ChildList { return new ChildList(this.toArray().map(fn), this.parent); } + applyMap(fn: (expr: T, index: number, array: T[]) => T): ChildList { + this.store.forEach((expr, index, array) => { + this.set(index, fn(expr, index, array)); + }); + return this; + } + /** Like a regular map, but omits undefined values returned from the mapper */ mapFilter( fn: (expr: T, index: number, array: T[]) => T | undefined From d686a19f9e3a27d004f13ff25c724b76a2b3f3df Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 01:40:32 -0700 Subject: [PATCH 05/18] Update snapshots --- .../__tests__/__snapshots__/regular-macros.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap b/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap index dbf51abe..1988771a 100644 --- a/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap +++ b/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap @@ -68,7 +68,7 @@ exports[`regular macro expansion 1`] = ` ], [ "regular-macro", - "let#811", + "let#807", [ "parameters", ], @@ -121,7 +121,7 @@ exports[`regular macro expansion 1`] = ` ], [ "regular-macro", - "fn#1462", + "fn#1414", [ "parameters", ], From 25f2fe3fdf668d3b671c3f9a82a40acbca2b3fe3 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 01:56:44 -0700 Subject: [PATCH 06/18] WIP --- src/lib/fast-shift-array.ts | 16 +++---- src/semantics/modules.ts | 4 +- src/syntax-objects/lib/child-list.ts | 35 +++++++++------ src/syntax-objects/list.ts | 47 ++++++++++---------- src/syntax-objects/module.ts | 64 ++++++++-------------------- 5 files changed, 72 insertions(+), 94 deletions(-) diff --git a/src/lib/fast-shift-array.ts b/src/lib/fast-shift-array.ts index 651c1fd7..fa3d1379 100644 --- a/src/lib/fast-shift-array.ts +++ b/src/lib/fast-shift-array.ts @@ -1,3 +1,5 @@ +import { at } from "vitest/dist/chunks/reporters.C_zwCd4j.js"; + // TODO: Add map, filter, reduce, amd findIndex export class FastShiftArray { private items: T[]; @@ -18,15 +20,6 @@ export class FastShiftArray { 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; @@ -37,6 +30,11 @@ export class FastShiftArray { 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; diff --git a/src/semantics/modules.ts b/src/semantics/modules.ts index fb50c814..d4a31284 100644 --- a/src/semantics/modules.ts +++ b/src/semantics/modules.ts @@ -51,7 +51,7 @@ const registerModule = ({ } if (!existingModule && (name === "index" || name === "mod")) { - parentModule.unshift(...ast.toArray()); + parentModule.unshift(...ast.toArray().reverse()); return; } @@ -73,7 +73,7 @@ const registerModule = ({ } if (existingModule && !rest.length) { - module.unshift(...ast.toArray()); + module.unshift(...ast.toArray().reverse()); } if (!rest.length) return; diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts index d5c27d1d..536fc356 100644 --- a/src/syntax-objects/lib/child-list.ts +++ b/src/syntax-objects/lib/child-list.ts @@ -37,6 +37,13 @@ export class ChildList { return this.store.length; } + private registerExpr(expr: T) { + expr.parent = this.parent; + if (expr instanceof NamedEntity) { + this.parent.registerEntity(expr); + } + } + at(index: number): Expr | undefined { return this.store.at(index); } @@ -73,11 +80,7 @@ export class ChildList { } set(index: number, expr: T) { - expr.parent = this.parent; - if (expr instanceof NamedEntity) { - this.parent.registerEntity(expr); - } - + this.registerExpr(expr); this.store.set(index, expr); return this; } @@ -116,14 +119,9 @@ export class ChildList { push(...expr: T[]) { expr.forEach((ex) => { - ex.parent = this.parent; - if (ex instanceof NamedEntity) { - this.parent.registerEntity(ex); - } - + this.registerExpr(ex); this.store.push(ex); }); - return this; } @@ -132,7 +130,7 @@ export class ChildList { } insert(expr: T, at = 0) { - expr.parent = this.parent; + this.registerExpr(expr); this.store.splice(at, 0, expr); return this; } @@ -186,6 +184,19 @@ export class ChildList { return this.store.slice(start, end); } + shift(): T | undefined { + return this.store.shift(); + } + + unshift(...expr: T[]) { + expr.forEach((ex) => { + this.registerExpr(ex); + this.store.unshift(ex); + }); + + return this; + } + toArray(): T[] { return this.store.toArray(); } diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 91f1530a..61227a0f 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -1,4 +1,3 @@ -import { FastShiftArray } from "../lib/fast-shift-array.js"; import { Expr } from "./expr.js"; import { Float } from "./float.js"; import { getIdStr } from "./lib/get-id-str.js"; @@ -6,12 +5,13 @@ import { Id, Identifier } from "./identifier.js"; import { Int } from "./int.js"; import { NamedEntity } from "./named-entity.js"; import { Syntax, SyntaxMetadata } from "./syntax.js"; +import { ChildList } from "./lib/child-list.js"; export class List extends Syntax { readonly syntaxType = "list"; /** True when the list was defined by the user using parenthesis i.e. (hey, there) */ mayBeTuple?: boolean; - store: FastShiftArray = new FastShiftArray(); + #store = new ChildList(undefined, this); constructor( opts: @@ -35,23 +35,23 @@ export class List extends Syntax { } get children() { - return this.store.toArray(); + return this.toArray(); } get hasChildren() { - return !!this.store.length; + return !!this.#store.length; } get length() { - return this.store.length; + return this.#store.length; } at(index: number): Expr | undefined { - return this.store.at(index); + return this.#store.at(index); } exprAt(index: number): Expr { - const expr = this.store.at(index); + const expr = this.#store.at(index); if (!expr) { throw new Error(`No expr at ${index}`); } @@ -84,7 +84,7 @@ export class List extends Syntax { set(index: number, expr: Expr | string) { const result = typeof expr === "string" ? Identifier.from(expr) : expr; result.parent = this; - this.store.set(index, result); + this.#store.set(index, result); return this; } @@ -98,42 +98,42 @@ export class List extends Syntax { } consume(): Expr { - const next = this.store.shift(); + const next = this.#store.shift(); if (!next) throw new Error("No remaining expressions"); return next; } first(): Expr | undefined { - return this.store.at(0); + return this.#store.at(0); } last(): Expr | undefined { - return this.store.at(-1); + return this.#store.at(-1); } /** Returns all but the first element in an array */ argsArray(): Expr[] { - return this.store.toArray().slice(1); + return this.#store.toArray().slice(1); } pop(): Expr | undefined { - return this.store.pop(); + return this.#store.pop(); } push(...expr: ListValue[]) { expr.forEach((ex) => { if (typeof ex === "string") { - this.store.push(new Identifier({ value: ex, parent: this })); + this.#store.push(new Identifier({ value: ex, parent: this })); return; } if (typeof ex === "number" && Number.isInteger(ex)) { - this.store.push(new Int({ value: ex, parent: this })); + this.#store.push(new Int({ value: ex, parent: this })); return; } if (typeof ex === "number") { - this.store.push(new Float({ value: ex, parent: this })); + this.#store.push(new Float({ value: ex, parent: this })); return; } @@ -149,11 +149,11 @@ export class List extends Syntax { } if (ex.isList() && ex.calls("splice_quote")) { - this.store.push(...ex.argsArray()); + this.#store.push(...ex.argsArray()); return; } - this.store.push(ex); + this.#store.push(ex); }); return this; @@ -165,13 +165,12 @@ export class List extends Syntax { insert(expr: Expr | string, at = 0) { const result = typeof expr === "string" ? Identifier.from(expr) : expr; - result.parent = this; - this.store.splice(at, 0, result); + this.#store.insert(result, at); return this; } remove(index: number, count = 1) { - this.store.splice(index, count); + this.#store.remove(index, count); return this; } @@ -210,16 +209,16 @@ export class List extends Syntax { slice(start?: number, end?: number): List { return new List({ ...super.getCloneOpts(), - value: this.store.slice(start, end), + value: this.#store.slice(start, end).toArray(), }); } sliceAsArray(start?: number, end?: number) { - return this.store.slice(start, end); + return this.children.slice(start, end); } toArray(): Expr[] { - return this.store.toArray(); + return this.#store.toArray(); } toJSON() { diff --git a/src/syntax-objects/module.ts b/src/syntax-objects/module.ts index f57cc86f..48ad4340 100644 --- a/src/syntax-objects/module.ts +++ b/src/syntax-objects/module.ts @@ -1,9 +1,7 @@ import { Expr } from "./expr.js"; -import { Float } from "./float.js"; -import { Id, Identifier } from "./identifier.js"; -import { Int } from "./int.js"; +import { Id } from "./identifier.js"; +import { ChildList } from "./lib/child-list.js"; import { LexicalContext } from "./lib/lexical-context.js"; -import { List, ListValue } from "./list.js"; import { NamedEntity, ScopedNamedEntity, @@ -11,7 +9,7 @@ import { } from "./named-entity.js"; export type VoidModuleOpts = ScopedNamedEntityOpts & { - value?: ListValue[]; + value?: Expr[]; phase?: number; isIndex?: boolean; exports?: LexicalContext; @@ -23,7 +21,7 @@ export class VoidModule extends ScopedNamedEntity { readonly isRoot: boolean = false; /** This module is the entry point of the user src code */ isIndex = false; - value: Expr[] = []; + #value = new ChildList(undefined, this); /** * 0 = init, * 1 = expanding regular macros, @@ -41,6 +39,15 @@ export class VoidModule extends ScopedNamedEntity { this.isIndex = opts.isIndex ?? false; } + get value() { + return this.#value.toArray(); + } + + set value(value: Expr[]) { + this.#value = new ChildList(undefined, this); + this.push(...value); + } + registerExport(entity: NamedEntity, alias?: string) { this.exports.registerEntity(entity, alias); } @@ -104,50 +111,13 @@ export class VoidModule extends ScopedNamedEntity { ]; } - push(...expr: ListValue[]) { - expr.forEach((ex) => { - if (typeof ex === "string") { - this.value.push(new Identifier({ value: ex, parent: this })); - return; - } - - if (ex instanceof Array) { - this.push(new List({ value: ex, parent: this })); - return; - } - - if (typeof ex === "number" && Number.isInteger(ex)) { - this.value.push(new Int({ value: ex, parent: this })); - return; - } - - if (typeof ex === "number") { - this.value.push(new Float({ value: ex, parent: this })); - return; - } - - ex.parent = this; - - if (ex instanceof NamedEntity) { - this.registerEntity(ex); - } - - if (ex.isList() && ex.calls("splice_quote")) { - this.value.push(...ex.argsArray()); - return; - } - - this.value.push(ex); - }); - + push(...expr: Expr[]) { + this.#value.push(...expr); return this; } - unshift(...expr: ListValue[]) { - const old = this.value; - this.value = []; - this.push(...expr); - this.push(...old); + unshift(...expr: Expr[]) { + this.#value.unshift(...expr); return this; } } From 0ebd273a8ccbb944ff75f14a9035aa479e7343b8 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 02:21:16 -0700 Subject: [PATCH 07/18] Cleanup --- src/syntax-objects/list.ts | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 61227a0f..cc2b574c 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -3,24 +3,23 @@ import { Float } from "./float.js"; import { getIdStr } from "./lib/get-id-str.js"; import { Id, Identifier } from "./identifier.js"; import { Int } from "./int.js"; -import { NamedEntity } from "./named-entity.js"; import { Syntax, SyntaxMetadata } from "./syntax.js"; import { ChildList } from "./lib/child-list.js"; +type ListOpts = + | ListValue[] + | (SyntaxMetadata & { + value?: ListValue[] | List; + isParentheticalList?: boolean; + }); + export class List extends Syntax { readonly syntaxType = "list"; /** True when the list was defined by the user using parenthesis i.e. (hey, there) */ mayBeTuple?: boolean; #store = new ChildList(undefined, this); - constructor( - opts: - | ListValue[] - | (SyntaxMetadata & { - value?: ListValue[] | List; - isParentheticalList?: boolean; - }) - ) { + constructor(opts: ListOpts) { opts = Array.isArray(opts) ? { value: opts } : opts; super(opts); @@ -83,7 +82,6 @@ export class List extends Syntax { set(index: number, expr: Expr | string) { const result = typeof expr === "string" ? Identifier.from(expr) : expr; - result.parent = this; this.#store.set(index, result); return this; } @@ -142,12 +140,6 @@ export class List extends Syntax { return; } - ex.parent = this; - - if (ex instanceof NamedEntity) { - this.registerEntity(ex); - } - if (ex.isList() && ex.calls("splice_quote")) { this.#store.push(...ex.argsArray()); return; @@ -229,7 +221,7 @@ export class List extends Syntax { clone(parent?: Expr): List { return new List({ ...super.getCloneOpts(parent), - value: this.toArray().map((v) => v.clone()), + value: this.#store.clone().toArray(), isParentheticalList: this.mayBeTuple, }); } From e50b69b68e36d16a728fc217b7b6bf24bf4aa30a Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 02:39:35 -0700 Subject: [PATCH 08/18] Better map cloning? --- src/syntax-objects/syntax.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index 6c0ea3c1..d21a18bb 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -65,7 +65,7 @@ export abstract class Syntax { return { location: this.location, parent: this.parent, - attributes: structuredClone(this.attributes), + attributes: new Map(this.attributes), }; } From 8d660d593ef5e7bcf3e3de2e20bf98e494ab6f5a Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 02:40:56 -0700 Subject: [PATCH 09/18] Remove attributes for a sec --- src/syntax-objects/syntax.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index d21a18bb..772d159c 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -42,7 +42,7 @@ export abstract class Syntax { /** For tagged unions */ abstract readonly syntaxType: string; readonly syntaxId = getSyntaxId(); - private attributes: Map; + // private attributes: Map; location?: SourceLocation; parent?: Expr; @@ -50,7 +50,7 @@ export abstract class Syntax { const { location, parent } = metadata; this.location = location; this.parent = parent; - this.attributes = metadata.attributes ?? new Map(); + // this.attributes = metadata.attributes ?? new Map(); } get parentFn(): Fn | undefined { @@ -65,7 +65,7 @@ export abstract class Syntax { return { location: this.location, parent: this.parent, - attributes: new Map(this.attributes), + // attributes: new Map(this.attributes), }; } @@ -130,17 +130,17 @@ export abstract class Syntax { /** Should emit in compliance with core language spec */ abstract toJSON(): unknown; - setAttribute(key: string, value: unknown) { - this.attributes.set(key, value); - } + // setAttribute(key: string, value: unknown) { + // this.attributes.set(key, value); + // } - getAttribute(key: string): unknown { - return this.attributes.get(key); - } + // getAttribute(key: string): unknown { + // return this.attributes.get(key); + // } - hasAttribute(key: string): boolean { - return this.attributes.has(key); - } + // hasAttribute(key: string): boolean { + // return this.attributes.has(key); + // } isScopedEntity(): this is ScopedEntity { return (this as unknown as ScopedEntity).lexicon instanceof LexicalContext; From 6b18aa634b34a3b5ef9835df44351f4aab74844f Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 11:31:38 -0700 Subject: [PATCH 10/18] Overoptimize --- src/syntax-objects/list.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index cc2b574c..afffd549 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -9,7 +9,7 @@ import { ChildList } from "./lib/child-list.js"; type ListOpts = | ListValue[] | (SyntaxMetadata & { - value?: ListValue[] | List; + value?: ListValue[] | List | ChildList; isParentheticalList?: boolean; }); @@ -17,18 +17,23 @@ export class List extends Syntax { readonly syntaxType = "list"; /** True when the list was defined by the user using parenthesis i.e. (hey, there) */ mayBeTuple?: boolean; - #store = new ChildList(undefined, this); + #store: ChildList; constructor(opts: ListOpts) { opts = Array.isArray(opts) ? { value: opts } : opts; super(opts); - const value = opts.value; this.mayBeTuple = opts.isParentheticalList; + if (value instanceof ChildList) { + this.#store = value.clone(this); + return; + } + + this.#store = new ChildList(undefined, this); if (!value || value instanceof Array) { this.push(...(value ?? [])); - } else { + } else if (value instanceof List) { this.push(...value.toArray()); } } @@ -221,7 +226,7 @@ export class List extends Syntax { clone(parent?: Expr): List { return new List({ ...super.getCloneOpts(parent), - value: this.#store.clone().toArray(), + value: this.#store, isParentheticalList: this.mayBeTuple, }); } From a7e2c8ad71807ee1f3c712e4eb2c59c6f724436d Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 12:08:55 -0700 Subject: [PATCH 11/18] Add attributes --- src/syntax-objects/syntax.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index 772d159c..27dd1d6f 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -32,17 +32,19 @@ import { Declaration } from "./declaration.js"; import { Use } from "./use.js"; import { Match } from "./match.js"; +export type Attributes = { [key: string]: unknown }; + export type SyntaxMetadata = { location?: SourceLocation; parent?: Expr; - attributes?: Map; + attributes?: Attributes; }; export abstract class Syntax { /** For tagged unions */ abstract readonly syntaxType: string; readonly syntaxId = getSyntaxId(); - // private attributes: Map; + #attributes?: Attributes; location?: SourceLocation; parent?: Expr; @@ -50,7 +52,7 @@ export abstract class Syntax { const { location, parent } = metadata; this.location = location; this.parent = parent; - // this.attributes = metadata.attributes ?? new Map(); + this.#attributes = metadata.attributes; } get parentFn(): Fn | undefined { @@ -65,7 +67,7 @@ export abstract class Syntax { return { location: this.location, parent: this.parent, - // attributes: new Map(this.attributes), + attributes: this.#attributes ? { ...this.#attributes } : undefined, }; } @@ -130,17 +132,20 @@ export abstract class Syntax { /** Should emit in compliance with core language spec */ abstract toJSON(): unknown; - // setAttribute(key: string, value: unknown) { - // this.attributes.set(key, value); - // } + setAttribute(key: string, value: unknown) { + if (!this.#attributes) this.#attributes = {}; + this.#attributes[key] = value; + } - // getAttribute(key: string): unknown { - // return this.attributes.get(key); - // } + getAttribute(key: string): unknown { + if (!this.#attributes) return undefined; + return this.#attributes[key]; + } - // hasAttribute(key: string): boolean { - // return this.attributes.has(key); - // } + hasAttribute(key: string): boolean { + if (!this.#attributes) return false; + return this.#attributes[key] !== undefined; + } isScopedEntity(): this is ScopedEntity { return (this as unknown as ScopedEntity).lexicon instanceof LexicalContext; From 4794fa45a49112791ac697ed9635c68d19816239 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 12:19:06 -0700 Subject: [PATCH 12/18] Cleanup cloning logic --- src/syntax-objects/list.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index afffd549..63b2e90d 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -26,7 +26,8 @@ export class List extends Syntax { this.mayBeTuple = opts.isParentheticalList; if (value instanceof ChildList) { - this.#store = value.clone(this); + this.#store = value; + this.#store.parent = this; return; } @@ -226,7 +227,7 @@ export class List extends Syntax { clone(parent?: Expr): List { return new List({ ...super.getCloneOpts(parent), - value: this.#store, + value: this.#store.clone(), isParentheticalList: this.mayBeTuple, }); } From 9b4be98aff7b36dfec39a67a2210428b5cbcfeb5 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 13:04:20 -0700 Subject: [PATCH 13/18] Cleanup maybe --- src/syntax-objects/block.ts | 2 +- src/syntax-objects/fn.ts | 4 ++-- src/syntax-objects/lib/child-list.ts | 12 +++++------- src/syntax-objects/list.ts | 11 ++--------- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/syntax-objects/block.ts b/src/syntax-objects/block.ts index 748b0b73..c058d5f7 100644 --- a/src/syntax-objects/block.ts +++ b/src/syntax-objects/block.ts @@ -60,7 +60,7 @@ export class Block extends ScopedSyntax { clone(parent?: Expr) { return new Block({ ...this.getCloneOpts(parent), - body: this.#body.clone().toArray(), + body: this.#body.toClonedArray(), }); } } diff --git a/src/syntax-objects/fn.ts b/src/syntax-objects/fn.ts index ff9553f4..6b5dabbf 100644 --- a/src/syntax-objects/fn.ts +++ b/src/syntax-objects/fn.ts @@ -128,8 +128,8 @@ export class Fn extends ScopedNamedEntity { ...super.getCloneOpts(parent), variables: this.variables, returnTypeExpr: this.returnTypeExpr?.clone(), - parameters: this.#parameters.clone().toArray(), - typeParameters: this.#typeParams.clone().toArray(), + parameters: this.#parameters.toClonedArray(), + typeParameters: this.#typeParams.toClonedArray(), body: this.body?.clone(), }); } diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts index 536fc356..0dd231a0 100644 --- a/src/syntax-objects/lib/child-list.ts +++ b/src/syntax-objects/lib/child-list.ts @@ -3,6 +3,7 @@ import { Expr } from "../expr.js"; import { Id, Identifier } from "../identifier.js"; import { List } from "../list.js"; import { NamedEntity } from "../named-entity.js"; +import { Syntax } from "../syntax.js"; import { getIdStr } from "./get-id-str.js"; export class ChildList { @@ -201,14 +202,11 @@ export class ChildList { return this.store.toArray(); } - toJSON() { - return this.toArray(); + toClonedArray(parent?: Expr): T[] { + return this.toArray().map((expr: T): T => expr.clone() as T); } - clone(parent?: Expr): ChildList { - return new ChildList( - this.toArray().map((expr) => expr.clone()) as T[], - parent ?? this.parent - ); + toJSON() { + return this.toArray(); } } diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 63b2e90d..341381ea 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -17,7 +17,7 @@ export class List extends Syntax { readonly syntaxType = "list"; /** True when the list was defined by the user using parenthesis i.e. (hey, there) */ mayBeTuple?: boolean; - #store: ChildList; + #store = new ChildList(undefined, this); constructor(opts: ListOpts) { opts = Array.isArray(opts) ? { value: opts } : opts; @@ -25,13 +25,6 @@ export class List extends Syntax { const value = opts.value; this.mayBeTuple = opts.isParentheticalList; - if (value instanceof ChildList) { - this.#store = value; - this.#store.parent = this; - return; - } - - this.#store = new ChildList(undefined, this); if (!value || value instanceof Array) { this.push(...(value ?? [])); } else if (value instanceof List) { @@ -227,7 +220,7 @@ export class List extends Syntax { clone(parent?: Expr): List { return new List({ ...super.getCloneOpts(parent), - value: this.#store.clone(), + value: this.#store.toClonedArray(), isParentheticalList: this.mayBeTuple, }); } From 301a0e226c2fca26854cc0f17e70858ba7b7d0d1 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 13:10:45 -0700 Subject: [PATCH 14/18] Simplify child list --- src/syntax-objects/lib/child-list.ts | 88 +--------------------------- 1 file changed, 2 insertions(+), 86 deletions(-) diff --git a/src/syntax-objects/lib/child-list.ts b/src/syntax-objects/lib/child-list.ts index 0dd231a0..9f0c430a 100644 --- a/src/syntax-objects/lib/child-list.ts +++ b/src/syntax-objects/lib/child-list.ts @@ -1,10 +1,6 @@ import { FastShiftArray } from "../../lib/fast-shift-array.js"; import { Expr } from "../expr.js"; -import { Id, Identifier } from "../identifier.js"; -import { List } from "../list.js"; import { NamedEntity } from "../named-entity.js"; -import { Syntax } from "../syntax.js"; -import { getIdStr } from "./get-id-str.js"; export class ChildList { private store: FastShiftArray = new FastShiftArray(); @@ -49,66 +45,18 @@ export class ChildList { return this.store.at(index); } - exprAt(index: number): Expr { - const expr = this.store.at(index); - if (!expr) { - throw new Error(`No expr at ${index}`); - } - return expr; - } - - identifierAt(index: number): Identifier { - const id = this.at(index); - if (!id?.isIdentifier()) { - throw new Error(`No identifier at index ${index}`); - } - return id; - } - - optionalIdentifierAt(index: number): Identifier | undefined { - const id = this.at(index); - if (id?.isIdentifier()) { - return id; - } - } - - listAt(index: number): List { - const id = this.at(index); - if (!id?.isList()) { - throw new Error(`No list at index ${index}`); - } - return id; - } - set(index: number, expr: T) { this.registerExpr(expr); this.store.set(index, expr); return this; } - calls(fnId: Id, atIndex = 0) { - return this.getIdStrAt(atIndex) === getIdStr(fnId); - } - - getIdStrAt(index: number): string | undefined { - const v = this.at(index); - return v?.isIdentifier() || v?.isStringLiteral() ? v.value : undefined; - } - consume(): T { const next = this.store.shift(); if (!next) throw new Error("No remaining expressions"); return next; } - first(): T | undefined { - return this.store.at(0); - } - - last(): T | undefined { - return this.store.at(-1); - } - /** Returns all but the first element in an array */ argsArray(): T[] { return this.store.toArray().slice(1); @@ -126,10 +74,6 @@ export class ChildList { return this; } - findIndex(cb: (expr: Expr) => boolean) { - return this.toArray().findIndex(cb); - } - insert(expr: T, at = 0) { this.registerExpr(expr); this.store.splice(at, 0, expr); @@ -141,19 +85,11 @@ export class ChildList { return this; } - filter(fn: (expr: T, index: number, array: T[]) => boolean): ChildList { - return new ChildList(this.toArray().filter(fn), this.parent); - } - each(fn: (expr: T, index: number, array: T[]) => void): ChildList { this.toArray().forEach(fn); return this; } - map(fn: (expr: T, index: number, array: T[]) => T): ChildList { - return new ChildList(this.toArray().map(fn), this.parent); - } - applyMap(fn: (expr: T, index: number, array: T[]) => T): ChildList { this.store.forEach((expr, index, array) => { this.set(index, fn(expr, index, array)); @@ -161,27 +97,7 @@ export class ChildList { return this; } - /** Like a regular map, but omits undefined values returned from the mapper */ - mapFilter( - fn: (expr: T, index: number, array: T[]) => T | undefined - ): ChildList { - const list = new ChildList([], this.parent); - return this.toArray().reduce( - (newList: ChildList, expr, index, array) => { - if (!expr) return newList; - const result = fn(expr, index, array); - if (!result) return newList; - return newList.push(result); - }, - list - ); - } - - slice(start?: number, end?: number): ChildList { - return new ChildList(this.store.slice(start, end), this.parent); - } - - sliceAsArray(start?: number, end?: number) { + slice(start?: number, end?: number): T[] { return this.store.slice(start, end); } @@ -202,7 +118,7 @@ export class ChildList { return this.store.toArray(); } - toClonedArray(parent?: Expr): T[] { + toClonedArray(): T[] { return this.toArray().map((expr: T): T => expr.clone() as T); } From 7ee12fda6373fb149846aa83c7369548a79d29c6 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 13:11:02 -0700 Subject: [PATCH 15/18] Simplify list --- src/syntax-objects/list.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 341381ea..f8314fcc 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -95,9 +95,7 @@ export class List extends Syntax { } consume(): Expr { - const next = this.#store.shift(); - if (!next) throw new Error("No remaining expressions"); - return next; + return this.#store.consume(); } first(): Expr | undefined { @@ -200,7 +198,7 @@ export class List extends Syntax { slice(start?: number, end?: number): List { return new List({ ...super.getCloneOpts(), - value: this.#store.slice(start, end).toArray(), + value: this.#store.slice(start, end), }); } From d07133b856888c2ebd1f4709d03e8a9654d1b680 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 14:26:35 -0700 Subject: [PATCH 16/18] Cleanup --- .../syntax-macros/functional-notation.ts | 103 +++++++++++------- 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/src/parser/syntax-macros/functional-notation.ts b/src/parser/syntax-macros/functional-notation.ts index b64900d2..d1bf9c94 100644 --- a/src/parser/syntax-macros/functional-notation.ts +++ b/src/parser/syntax-macros/functional-notation.ts @@ -1,56 +1,86 @@ 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.mayBeTuple && 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 acc; +}; - return processParamList(expr, array, index); +const finalizeResult = (result: ListValue[], isTuple: boolean): List => { + if (isTuple) { + result.unshift(","); + result.unshift("tuple"); + } + return new List(result); }; -const processGenerics = (expr: Expr, array: Expr[], index: number): List => { - const generics = array.splice(index + 1, 1)[0] as List; +const processGenerics = (expr: Expr, generics: List, params?: List): List => { generics.mayBeTuple = false; - const list = array[index + 1]?.isList() - ? (array.splice(index + 1, 1)[0] as List) - : new List({}); - - list.insert(","); + const list = params || new List([]); list.insert(expr); + list.insert(",", 1); list.mayBeTuple = false; const functional = functionalNotation(list); @@ -59,10 +89,9 @@ const processGenerics = (expr: Expr, array: Expr[], index: number): List => { 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.mayBeTuple = false; + return functionalNotation(params); }; From 1170473d715a60539a38d7fb72567a66f9d2146a Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 14:26:39 -0700 Subject: [PATCH 17/18] Cleanup --- src/parser/reader-macros/comment.ts | 4 +-- .../__snapshots__/regular-macros.test.ts.snap | 8 ++--- src/semantics/check-types.ts | 4 +-- src/semantics/regular-macros.ts | 7 ++-- src/semantics/resolution/resolve-types.ts | 4 +-- src/syntax-objects/expr.ts | 4 ++- src/syntax-objects/helpers.ts | 14 ++++++-- src/syntax-objects/list.ts | 32 ++++++++----------- src/syntax-objects/nop.ts | 18 +++++++++++ 9 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 src/syntax-objects/nop.ts diff --git a/src/parser/reader-macros/comment.ts b/src/parser/reader-macros/comment.ts index 7fa82436..067f7769 100644 --- a/src/parser/reader-macros/comment.ts +++ b/src/parser/reader-macros/comment.ts @@ -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 = { @@ -9,6 +9,6 @@ export const comment: ReaderMacro = { file.consumeChar(); } - return noop(); + return nop(); }, }; diff --git a/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap b/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap index 1988771a..8db56d23 100644 --- a/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap +++ b/src/semantics/__tests__/__snapshots__/regular-macros.test.ts.snap @@ -7,7 +7,7 @@ exports[`regular macro expansion 1`] = ` [ "exports", [ - "std#730", + "std#729", ], ], [ @@ -47,7 +47,7 @@ exports[`regular macro expansion 1`] = ` ], [ "regular-macro", - "\`#759", + "\`#758", [ "parameters", ], @@ -68,7 +68,7 @@ exports[`regular macro expansion 1`] = ` ], [ "regular-macro", - "let#807", + "let#805", [ "parameters", ], @@ -121,7 +121,7 @@ exports[`regular macro expansion 1`] = ` ], [ "regular-macro", - "fn#1414", + "fn#1408", [ "parameters", ], diff --git a/src/semantics/check-types.ts b/src/semantics/check-types.ts index 66a5eded..0f70d851 100644 --- a/src/semantics/check-types.ts +++ b/src/semantics/check-types.ts @@ -1,7 +1,7 @@ import { List, Expr, - noop, + nop, Identifier, ObjectType, Type, @@ -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); diff --git a/src/semantics/regular-macros.ts b/src/semantics/regular-macros.ts index 96f4ef3d..f44198ff 100644 --- a/src/semantics/regular-macros.ts +++ b/src/semantics/regular-macros.ts @@ -14,6 +14,7 @@ import { VoidModule, Block, Use, + nop, } from "../syntax-objects/index.js"; import { registerExports, @@ -256,7 +257,7 @@ const functions: Record = { }, 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); @@ -264,7 +265,7 @@ const functions: Record = { 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); @@ -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 diff --git a/src/semantics/resolution/resolve-types.ts b/src/semantics/resolution/resolve-types.ts index 3272b4ac..5a223751 100644 --- a/src/semantics/resolution/resolve-types.ts +++ b/src/semantics/resolution/resolve-types.ts @@ -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"; @@ -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); diff --git a/src/syntax-objects/expr.ts b/src/syntax-objects/expr.ts index ba686fc0..ea452baa 100644 --- a/src/syntax-objects/expr.ts +++ b/src/syntax-objects/expr.ts @@ -20,6 +20,7 @@ import { Declaration } from "./declaration.js"; import { Use } from "./use.js"; import { ObjectLiteral } from "./object-literal.js"; import { Match } from "./match.js"; +import { Nop } from "./nop.js"; export type Expr = | PrimitiveExpr @@ -37,7 +38,8 @@ export type Expr = | Declaration | Use | ObjectLiteral - | Match; + | Match + | Nop; /** * These are the Expr types that must be returned until all macros have been expanded (reader, syntax, and regular) diff --git a/src/syntax-objects/helpers.ts b/src/syntax-objects/helpers.ts index 72dfeb78..60de5b79 100644 --- a/src/syntax-objects/helpers.ts +++ b/src/syntax-objects/helpers.ts @@ -1,5 +1,15 @@ -import { List } from "./list.js"; +import { Nop } from "./nop.js"; import { Whitespace } from "./whitespace.js"; export const newLine = () => new Whitespace({ value: "\n" }); -export const noop = () => new List({ value: ["splice_quote"] }); + +let nopCache: Nop | undefined = undefined; +export const nop = () => { + if (!nopCache) { + const n = new Nop({}); + nopCache = n; + return n; + } + + return nopCache; +}; diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index f8314fcc..267f7228 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -137,8 +137,7 @@ export class List extends Syntax { return; } - if (ex.isList() && ex.calls("splice_quote")) { - this.#store.push(...ex.argsArray()); + if (ex.syntaxType === "nop") { return; } @@ -163,13 +162,6 @@ export class List extends Syntax { return this; } - filter(fn: (expr: Expr, index: number, array: Expr[]) => boolean): List { - return new List({ - ...super.getCloneOpts(), - value: this.toArray().filter(fn), - }); - } - each(fn: (expr: Expr, index: number, array: Expr[]) => void): List { this.toArray().forEach(fn); return this; @@ -182,17 +174,19 @@ export class List extends Syntax { }); } - /** Like a regular map, but omits undefined values returned from the mapper */ - mapFilter( - fn: (expr: Expr, index: number, array: Expr[]) => Expr | undefined + flatMap( + fn: (expr: Expr, index: number, array: Expr[]) => Expr | Expr[] + ): List { + return new List({ + ...super.getCloneOpts(), + value: this.toArray().flatMap(fn), + }); + } + + reduce( + fn: (acc: List, expr: Expr, index: number, array: Expr[]) => List ): List { - const list = new List({ ...super.getCloneOpts() }); - return this.toArray().reduce((newList: List, expr, index, array) => { - if (!expr) return newList; - const result = fn(expr, index, array); - if (!result) return newList; - return newList.push(result); - }, list); + return this.toArray().reduce(fn, new List({ ...super.getCloneOpts() })); } slice(start?: number, end?: number): List { diff --git a/src/syntax-objects/nop.ts b/src/syntax-objects/nop.ts new file mode 100644 index 00000000..56747080 --- /dev/null +++ b/src/syntax-objects/nop.ts @@ -0,0 +1,18 @@ +import { Expr } from "./expr.js"; +import { Syntax, SyntaxMetadata } from "./syntax.js"; + +export class Nop extends Syntax { + readonly syntaxType = "nop"; + + constructor(opts: SyntaxMetadata) { + super(opts); + } + + clone(parent?: Expr): Nop { + return this; + } + + toJSON() { + return "nop"; + } +} From e5649b8a68c95c2c06805159ae9c87f6471a9297 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Fri, 13 Sep 2024 14:33:38 -0700 Subject: [PATCH 18/18] Use attributes --- src/parser/parse-chars.ts | 2 +- src/parser/syntax-macros/functional-notation.ts | 8 ++++---- src/syntax-objects/list.ts | 4 ---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/parser/parse-chars.ts b/src/parser/parse-chars.ts index b41a5290..af7b0535 100644 --- a/src/parser/parse-chars.ts +++ b/src/parser/parse-chars.ts @@ -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; } diff --git a/src/parser/syntax-macros/functional-notation.ts b/src/parser/syntax-macros/functional-notation.ts index d1bf9c94..bbeff4e3 100644 --- a/src/parser/syntax-macros/functional-notation.ts +++ b/src/parser/syntax-macros/functional-notation.ts @@ -28,7 +28,7 @@ export const functionalNotation = (list: List): List => { return handleNextExpression(acc, expr, nextExpr, array, index); } - if (list.mayBeTuple && idIs(expr, ",")) { + if (list.getAttribute("tuple?") && idIs(expr, ",")) { isTuple = true; } @@ -76,12 +76,12 @@ const finalizeResult = (result: ListValue[], isTuple: boolean): List => { }; const processGenerics = (expr: Expr, generics: List, params?: List): List => { - generics.mayBeTuple = false; + generics.setAttribute("tuple?", false); const list = params || new List([]); list.insert(expr); list.insert(",", 1); - list.mayBeTuple = false; + list.setAttribute("tuple?", false); const functional = functionalNotation(list); functional.insert(functionalNotation(generics), 2); @@ -92,6 +92,6 @@ const processGenerics = (expr: Expr, generics: List, params?: List): List => { const processParamList = (expr: Expr, params: List): List => { params.insert(expr); params.insert(",", 1); - params.mayBeTuple = false; + params.setAttribute("tuple?", false); return functionalNotation(params); }; diff --git a/src/syntax-objects/list.ts b/src/syntax-objects/list.ts index 267f7228..2e9db1a1 100644 --- a/src/syntax-objects/list.ts +++ b/src/syntax-objects/list.ts @@ -15,15 +15,12 @@ type ListOpts = export class List extends Syntax { readonly syntaxType = "list"; - /** True when the list was defined by the user using parenthesis i.e. (hey, there) */ - mayBeTuple?: boolean; #store = new ChildList(undefined, this); constructor(opts: ListOpts) { opts = Array.isArray(opts) ? { value: opts } : opts; super(opts); const value = opts.value; - this.mayBeTuple = opts.isParentheticalList; if (!value || value instanceof Array) { this.push(...(value ?? [])); @@ -213,7 +210,6 @@ export class List extends Syntax { return new List({ ...super.getCloneOpts(parent), value: this.#store.toClonedArray(), - isParentheticalList: this.mayBeTuple, }); } }