From e28babec3153cf27aa55f611eea9cbe7cdea1e2d Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Wed, 28 Aug 2024 19:04:03 -0700 Subject: [PATCH] [Voi-88] Generic parameter parsing (#26) * Standardized reader macro match function * Semi working prototype * Add tests --- src/parser.mts | 45 ++++++++++++----- src/reader-macros/array-literal.mts | 2 +- src/reader-macros/boolean.mts | 2 +- src/reader-macros/comment.mts | 2 +- src/reader-macros/dictionary-literal.mts | 2 +- src/reader-macros/float.mts | 2 +- src/reader-macros/generics.mts | 11 +++++ src/reader-macros/index.mts | 30 +++--------- src/reader-macros/int.mts | 2 +- src/reader-macros/object-literal.mts | 2 +- src/reader-macros/scientific-e-notation.mts | 2 +- src/reader-macros/string.mts | 2 +- src/reader-macros/types.mts | 2 +- .../__tests__/fixtures/desugarred-ast.mts | 3 ++ .../__tests__/fixtures/void-file.mts | 8 ++++ .../surface-language/functional-notation.mts | 48 ++++++++++++++++--- .../surface-language/interpret-whitespace.mts | 6 +-- 17 files changed, 115 insertions(+), 56 deletions(-) create mode 100644 src/reader-macros/generics.mts diff --git a/src/parser.mts b/src/parser.mts index 60aa3643..16ba61d9 100644 --- a/src/parser.mts +++ b/src/parser.mts @@ -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; } @@ -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; +}; diff --git a/src/reader-macros/array-literal.mts b/src/reader-macros/array-literal.mts index 0263bdf8..8fa1a9fd 100644 --- a/src/reader-macros/array-literal.mts +++ b/src/reader-macros/array-literal.mts @@ -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); diff --git a/src/reader-macros/boolean.mts b/src/reader-macros/boolean.mts index 9a56b3b2..1f3d3f8c 100644 --- a/src/reader-macros/boolean.mts +++ b/src/reader-macros/boolean.mts @@ -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"), diff --git a/src/reader-macros/comment.mts b/src/reader-macros/comment.mts index 78e61945..d159b6b2 100644 --- a/src/reader-macros/comment.mts +++ b/src/reader-macros/comment.mts @@ -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; diff --git a/src/reader-macros/dictionary-literal.mts b/src/reader-macros/dictionary-literal.mts index 764d0212..972a752a 100644 --- a/src/reader-macros/dictionary-literal.mts +++ b/src/reader-macros/dictionary-literal.mts @@ -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, "}"); diff --git a/src/reader-macros/float.mts b/src/reader-macros/float.mts index b1e729af..b9de0c89 100644 --- a/src/reader-macros/float.mts +++ b/src/reader-macros/float.mts @@ -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), diff --git a/src/reader-macros/generics.mts b/src/reader-macros/generics.mts new file mode 100644 index 00000000..514ee76e --- /dev/null +++ b/src/reader-macros/generics.mts @@ -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); + }, +}; diff --git a/src/reader-macros/index.mts b/src/reader-macros/index.mts index 56c21246..95ec77dc 100644 --- a/src/reader-macros/index.mts +++ b/src/reader-macros/index.mts @@ -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, @@ -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(), - 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; diff --git a/src/reader-macros/int.mts b/src/reader-macros/int.mts index 6d021b09..602c09cc 100644 --- a/src/reader-macros/int.mts +++ b/src/reader-macros/int.mts @@ -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), diff --git a/src/reader-macros/object-literal.mts b/src/reader-macros/object-literal.mts index d148b0aa..a24613aa 100644 --- a/src/reader-macros/object-literal.mts +++ b/src/reader-macros/object-literal.mts @@ -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); diff --git a/src/reader-macros/scientific-e-notation.mts b/src/reader-macros/scientific-e-notation.mts index 6db321e5..1a5d633a 100644 --- a/src/reader-macros/scientific-e-notation.mts +++ b/src/reader-macros/scientific-e-notation.mts @@ -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" })) diff --git a/src/reader-macros/string.mts b/src/reader-macros/string.mts index 8eef285f..893447eb 100644 --- a/src/reader-macros/string.mts +++ b/src/reader-macros/string.mts @@ -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 = ""; diff --git a/src/reader-macros/types.mts b/src/reader-macros/types.mts index 1c110b55..2df5ee00 100644 --- a/src/reader-macros/types.mts +++ b/src/reader-macros/types.mts @@ -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: { diff --git a/src/syntax-macros/surface-language/__tests__/fixtures/desugarred-ast.mts b/src/syntax-macros/surface-language/__tests__/fixtures/desugarred-ast.mts index bbe1a137..70d1f7e7 100644 --- a/src/syntax-macros/surface-language/__tests__/fixtures/desugarred-ast.mts +++ b/src/syntax-macros/surface-language/__tests__/fixtures/desugarred-ast.mts @@ -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"], diff --git a/src/syntax-macros/surface-language/__tests__/fixtures/void-file.mts b/src/syntax-macros/surface-language/__tests__/fixtures/void-file.mts index 1e81c792..8a5c2c70 100644 --- a/src/syntax-macros/surface-language/__tests__/fixtures/void-file.mts +++ b/src/syntax-macros/surface-language/__tests__/fixtures/void-file.mts @@ -54,6 +54,14 @@ closure_param_test(1, () => a, 3, () => let (x, y) = (1, 2) +Array(1, 2, 3) + 3 + +obj Test { + c: i32 +} + +fn test(a: 1) -> i32 + fn main() let a = ...test.hey + &other.now let x = 10 + diff --git a/src/syntax-macros/surface-language/functional-notation.mts b/src/syntax-macros/surface-language/functional-notation.mts index 822835e6..96249eea 100644 --- a/src/syntax-macros/surface-language/functional-notation.mts +++ b/src/syntax-macros/surface-language/functional-notation.mts @@ -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; @@ -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(",")) { @@ -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); +}; diff --git a/src/syntax-macros/surface-language/interpret-whitespace.mts b/src/syntax-macros/surface-language/interpret-whitespace.mts index 9270184c..bebd750b 100644 --- a/src/syntax-macros/surface-language/interpret-whitespace.mts +++ b/src/syntax-macros/surface-language/interpret-whitespace.mts @@ -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; @@ -197,7 +197,7 @@ const addSibling = (child: Expr, siblings: List) => { return; } - if (!olderSibling?.isList()) { + if (!olderSibling?.isList() || olderSibling.calls("generics")) { siblings.push(child); return; }