Skip to content

Commit

Permalink
[Voi-88] Generic parameter parsing (#26)
Browse files Browse the repository at this point in the history
* Standardized reader macro match function

* Semi working prototype

* Add tests
  • Loading branch information
drew-y authored Aug 29, 2024
1 parent aabcfa1 commit e28babe
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 56 deletions.
45 changes: 32 additions & 13 deletions src/parser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,14 @@ export function parse(file: File, opts: ParseOpts = {}): List {
value: !opts.nested ? ["ast", ","] : [],
});

let prev: Token | undefined = undefined;
let cur: Token | undefined = undefined;
while (file.hasCharacters) {
const token = lexer(file);
prev = cur;
cur = token;

const readerMacro = getReaderMacroForToken(token);

if (readerMacro) {
const result = readerMacro(file, {
token,
reader: (file, terminator, parent) =>
parse(file, {
nested: true,
terminator,
parent: parent ?? opts.parent,
}),
});
if (typeof result !== "undefined") list.push(result);
if (processWithReaderMacro(token, prev, file, opts, list)) {
continue;
}

Expand Down Expand Up @@ -154,3 +146,30 @@ const consumeSpaces = (file: File, token: Token) => {
token.addChar(file.consumeChar());
}
};

/** Returns true if token was matched with and processed by a macro */
const processWithReaderMacro = (
token: Token,
prev: Token | undefined,
file: File,
opts: ParseOpts,
list: List
) => {
const readerMacro = getReaderMacroForToken(token, prev);
if (!readerMacro) return undefined;

const result = readerMacro(file, {
token,
reader: (file, terminator, parent) =>
parse(file, {
nested: true,
terminator,
parent: parent ?? opts.parent,
}),
});

if (!result) return undefined;

list.push(result);
return true;
};
2 changes: 1 addition & 1 deletion src/reader-macros/array-literal.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReaderMacro } from "./types.mjs";

export const arrayLiteralMacro: ReaderMacro = {
tag: "[",
match: (t) => t.value === "[",
macro: (file, { reader }) => {
const items = reader(file, "]");
return items.insert("array").insert(",", 1);
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/boolean.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Bool } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const booleanMacro: ReaderMacro = {
tag: /^true|false$/,
match: (t) => /^true|false$/.test(t.value),
macro: (_, { token }) =>
new Bool({
value: token.is("true"),
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/comment.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { noop } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const comment: ReaderMacro = {
tag: /^\/\/[^\s]*$/,
match: (t) => /^\/\/[^\s]*$/.test(t.value),
macro: (file) => {
while (file.hasCharacters) {
if (file.next === "\n") break;
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/dictionary-literal.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Identifier, List } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const dictionaryLiteralMacro: ReaderMacro = {
tag: "#{",
match: (t) => t.value === "#{",
macro: (file, { reader }) => {
const dict = new Identifier({ value: "dict" });
const items = reader(file, "}");
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/float.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Float } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const floatMacro: ReaderMacro = {
tag: /^[+-]?\d+\.\d+$/,
match: (t) => /^[+-]?\d+\.\d+$/.test(t.value),
macro: (_, { token }) =>
new Float({
value: Number(token.value),
Expand Down
11 changes: 11 additions & 0 deletions src/reader-macros/generics.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ReaderMacro } from "./types.mjs";

export const genericsMacro: ReaderMacro = {
match: (t, prev) => {
return t.value === "<" && !!prev && /\w+/.test(prev.value);
},
macro: (file, { reader }) => {
const items = reader(file, ">");
return items.insert("generics").insert(",", 1);
},
};
30 changes: 6 additions & 24 deletions src/reader-macros/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { scientificENotationMacro } from "./scientific-e-notation.mjs";
import { stringMacro } from "./string.mjs";
import { objectLiteralMacro } from "./object-literal.mjs";
import { ReaderMacro } from "./types.mjs";
import { genericsMacro } from "./generics.mjs";

const macros = [
objectLiteralMacro,
Expand All @@ -20,30 +21,11 @@ const macros = [
stringMacro,
comment,
booleanMacro,
genericsMacro,
];

const readerMacros = macros.reduce(
({ map, patterns }, reader) => {
if (typeof reader.tag === "string") {
map.set(reader.tag, reader.macro);
return { map, patterns };
}

patterns.push({ pattern: reader.tag, macro: reader.macro });
return { map, patterns };
},
{
map: new Map<string, ReaderMacro["macro"]>(),
patterns: [] as { pattern: RegExp; macro: ReaderMacro["macro"] }[],
}
);

export const getReaderMacroForToken = (
token: Token
): ReaderMacro["macro"] | undefined => {
return (
readerMacros.map.get(token.value) ??
readerMacros.patterns.find(({ pattern }) => pattern.test(token.value))
?.macro
);
};
token: Token,
prev?: Token
): ReaderMacro["macro"] | undefined =>
macros.find((m) => m.match(token, prev))?.macro;
2 changes: 1 addition & 1 deletion src/reader-macros/int.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Int } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const intMacro: ReaderMacro = {
tag: /^[+-]?\d+$/,
match: (t) => /^[+-]?\d+$/.test(t.value),
macro: (_, { token }) =>
new Int({
value: Number(token.value),
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/object-literal.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReaderMacro } from "./types.mjs";

export const objectLiteralMacro: ReaderMacro = {
tag: "{",
match: (t) => t.value === "{",
macro: (dream, { reader }) => {
const items = reader(dream, "}");
return items.insert("object").insert(",", 1);
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/scientific-e-notation.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReaderMacro } from "./types.mjs";

export const scientificENotationMacro: ReaderMacro = {
/** Regex from Michael Dumas https://regexlib.com/REDetails.aspx?regexp_id=2457 */
tag: /^[+-]?\d(\.\d+)?[Ee][+-]?\d+$/,
match: (t) => /^[+-]?\d(\.\d+)?[Ee][+-]?\d+$/.test(t.value),
macro: (_, { token }) =>
new List({ location: token.location })
.push(new Identifier({ value: "scientific-e-notion" }))
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/string.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Identifier, StringLiteral } from "../syntax-objects/index.mjs";
import { ReaderMacro } from "./types.mjs";

export const stringMacro: ReaderMacro = {
tag: /^[\"\']$/,
match: (t) => /^[\"\']$/.test(t.value),
macro: (file, { token }) => {
const startChar = token.value;
token.value = "";
Expand Down
2 changes: 1 addition & 1 deletion src/reader-macros/types.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Expr, List } from "../syntax-objects/index.mjs";
import { Token } from "../lib/token.mjs";

export interface ReaderMacro {
tag: string | RegExp;
match: (token: Token, prev?: Token) => boolean;
macro: (
file: File,
opts: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export const desugarredAst = [
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"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ closure_param_test(1, () => a, 3, () =>
let (x, y) = (1, 2)
Array<Hey, There>(1, 2, 3) + 3
obj Test<T> {
c: i32
}
fn test<T>(a: 1) -> i32
fn main()
let a = ...test.hey + &other.now
let x = 10 +
Expand Down
48 changes: 42 additions & 6 deletions src/syntax-macros/surface-language/functional-notation.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isOp } from "../../lib/grammar.mjs";
import { List } from "../../syntax-objects/index.mjs";
import { Expr, List } from "../../syntax-objects/index.mjs";

export const functionalNotation = (list: List): List => {
let isTuple = false;
Expand All @@ -10,11 +10,7 @@ export const functionalNotation = (list: List): List => {

const nextExpr = array[index + 1];
if (nextExpr && nextExpr.isList() && !isOp(expr)) {
const list = array.splice(index + 1, 1)[0] as List;
list.insert(expr);
list.insert(",", 1);
list.mayBeTuple = false;
return functionalNotation(list);
return processFnCall(expr, nextExpr, array, index);
}

if (list.mayBeTuple && expr.isIdentifier() && expr.is(",")) {
Expand All @@ -26,7 +22,47 @@ export const functionalNotation = (list: List): List => {

if (isTuple) {
result.insert("tuple");
result.insert(",");
}

return result;
};

const processFnCall = (
expr: Expr,
nextExpr: List,
array: Expr[],
index: number
): List => {
if (nextExpr.calls("generics")) {
return processGenerics(expr, array, index);
}

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

const processGenerics = (expr: Expr, array: Expr[], index: number): List => {
const generics = array.splice(index + 1, 1)[0] as List;
generics.mayBeTuple = false;

const list = array[index + 1]?.isList()
? (array.splice(index + 1, 1)[0] as List)
: new List({});

list.insert(",");
list.insert(expr);
list.mayBeTuple = 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);
};
6 changes: 3 additions & 3 deletions src/syntax-macros/surface-language/interpret-whitespace.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const interpretWhitespace = (list: List, indentLevel?: number): List => {
return transformed;
};

const elideParens = (list: Expr, indentLevel?: number): Expr => {
const elideParens = (list: Expr, startIndentLevel?: number): Expr => {
if (!list.isList()) return list;
const transformed = new List({});
indentLevel = indentLevel ?? nextExprIndentLevel(list);
const indentLevel = startIndentLevel ?? nextExprIndentLevel(list);

const nextLineHasChildExpr = () => nextExprIndentLevel(list) > indentLevel;

Expand Down Expand Up @@ -197,7 +197,7 @@ const addSibling = (child: Expr, siblings: List) => {
return;
}

if (!olderSibling?.isList()) {
if (!olderSibling?.isList() || olderSibling.calls("generics")) {
siblings.push(child);
return;
}
Expand Down

0 comments on commit e28babe

Please sign in to comment.