From 2a0e24213d0dd4384d7dd351c1c793a32f6cf038 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Wed, 3 Jun 2020 09:19:30 +0200 Subject: [PATCH] Add a POC test runner for the parser and scanner --- src/ast-spec.ts | 2 +- src/ast.ts | 4 ++-- src/diagnostics.ts | 12 ++++++----- src/marktest.ts | 51 +++++++++++++++++++++++++++++++++++++++++++ src/parser.ts | 2 +- src/util.ts | 54 +++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/marktest.ts diff --git a/src/ast-spec.ts b/src/ast-spec.ts index ad910db1d..b1f00e81f 100644 --- a/src/ast-spec.ts +++ b/src/ast-spec.ts @@ -163,7 +163,7 @@ export interface BoltBracketed extends BoltPunctuated {} export interface BoltSourceFile extends BoltSyntax, SourceFile { elements: BoltSourceElement[], - pkg: Package, + pkg: Package | null, } export interface BoltQualName extends BoltSyntax { diff --git a/src/ast.ts b/src/ast.ts index 94b773cec..6a981b1e2 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -575,7 +575,7 @@ type BoltBracketedChild = never; export class BoltSourceFile extends SyntaxBase { parentNode: null | BoltSourceFileParent = null; kind: SyntaxKind.BoltSourceFile = SyntaxKind.BoltSourceFile; - constructor(public elements: BoltSourceElement[], public pkg: Package, span: TextSpan | null = null) { super(span); } + constructor(public elements: BoltSourceElement[], public pkg: Package | null, span: TextSpan | null = null) { super(span); } *getChildNodes(): IterableIterator { for (let element of this.elements) yield element; } } @@ -1991,7 +1991,7 @@ export function createBoltBraced(text: string, span: TextSpan | null = null): Bo export function createBoltBracketed(text: string, span: TextSpan | null = null): BoltBracketed { return new BoltBracketed(text, span); } -export function createBoltSourceFile(elements: BoltSourceElement[], pkg: Package, span: TextSpan | null = null): BoltSourceFile { return new BoltSourceFile(elements, pkg, span); } +export function createBoltSourceFile(elements: BoltSourceElement[], pkg: Package | null, span: TextSpan | null = null): BoltSourceFile { return new BoltSourceFile(elements, pkg, span); } export function createBoltQualName(isAbsolute: boolean, modulePath: BoltIdentifier[], name: BoltSymbol, span: TextSpan | null = null): BoltQualName { return new BoltQualName(isAbsolute, modulePath, name, span); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts index 260e63c79..db7e4050b 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -62,14 +62,16 @@ function firstIndexOfNonEmpty(str: string) { return j } -export class DiagnosticWriter { +export class DiagnosticIndex { - constructor(private fd: number) { - - } + private diagnostics = new Array(); public add(diagnostic: Diagnostic) { - fs.writeSync(this.fd, JSON.stringify(diagnostic) + '\n'); + this.diagnostics.push(diagnostic); + } + + public getAllDiagnostics(): IterableIterator { + return this.diagnostics[Symbol.iterator](); } } diff --git a/src/marktest.ts b/src/marktest.ts new file mode 100644 index 000000000..20cf7aae8 --- /dev/null +++ b/src/marktest.ts @@ -0,0 +1,51 @@ +import { SyntaxKind, Syntax } from "./ast"; +import { serialize, Json } from "./util"; +import { DiagnosticIndex } from "./diagnostics"; +import { Test } from "@marktest/core" + +export async function scanner(test: Test): Promise { + const { DiagnosticIndex } = await import("./diagnostics") + const diagnostics = new DiagnosticIndex; + const { Scanner } = await import("./scanner"); + const scanner = new Scanner(test.file, test.text, test.startPos); + const tokens = [] + while (true) { + const token = scanner.scan(); + if (token.kind === SyntaxKind.EndOfFile) { + break; + } + tokens.push(token); + } + return serialize({ + diagnostics, + tokens, + }); +} + +export async function parser(test: Test): Promise { + const kind = test.args.kind ?? 'SourceFile'; + const { Scanner } = await import("./scanner") + const { Parser } = await import("./parser"); + const diagnostics = new DiagnosticIndex; + const parser = new Parser(); + const tokens = new Scanner(test.file, test.text); + let results: Syntax[]; + switch (kind) { + case 'SourceFile': + results = [ parser.parseSourceFile(tokens) ]; + break; + case 'SourceElements': + results = parser.parseSourceElements(tokens); + break; + default: + throw new Error(`I did not know how to parse ${kind}`) + } + return serialize({ + diagnostics, + results, + }) +} + +export function check(test: Test): Json { + // TODO +} diff --git a/src/parser.ts b/src/parser.ts index 31e8ad41f..dceaf71aa 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1534,7 +1534,7 @@ export class Parser { // return lhs //} - public parseSourceFile(tokens: BoltTokenStream, pkg: Package): BoltSourceFile { + public parseSourceFile(tokens: BoltTokenStream, pkg: Package | null = null): BoltSourceFile { const elements = this.parseSourceElements(tokens); const t1 = tokens.peek(); assertToken(t1, SyntaxKind.EndOfFile); diff --git a/src/util.ts b/src/util.ts index 231af23e2..734361626 100644 --- a/src/util.ts +++ b/src/util.ts @@ -7,6 +7,7 @@ import moment from "moment" import chalk from "chalk" import { LOG_DATETIME_FORMAT } from "./constants" import { E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER } from "./diagnostics" +import { isPrimitive } from "util" export function isPowerOf(x: number, n: number):boolean { const a = Math.log(x) / Math.log(n); return Math.pow(a, n) == x; @@ -152,7 +153,7 @@ function isObjectLike(value: any) { return typeof value === 'object' && value !== null; } -export function isPlainObject(value: any): value is object { +export function isPlainObject(value: any): value is MapLike { if (!isObjectLike(value) || getTag(value) != '[object Object]') { return false } @@ -166,10 +167,16 @@ export function isPlainObject(value: any): value is object { return Object.getPrototypeOf(value) === proto } -export function mapValues(obj: T, func: (value: keyof T) => R): { [K in keyof T]: R } { +export function* values(obj: T): IterableIterator { + for (const key of Object.keys(obj)) { + yield obj[key as keyof T]; + } +} + +export function mapValues(obj: T, func: (value: T[keyof T]) => R): { [K in keyof T]: R } { const newObj: any = {} for (const key of Object.keys(obj)) { - newObj[key] = func((obj as any)[key]); + newObj[key as keyof T] = func(obj[key as keyof T]); } return newObj; } @@ -183,6 +190,47 @@ export function prettyPrint(value: any): string { return value.toString(); } +export type Newable = { + new(...args: any): T; +} + +export const serializeTag = Symbol('serializer tag'); + +const serializableClasses = new Map>(); + +export function serialize(value: any): Json { + if (isPrimitive(value)) { + return { + type: 'primitive', + value, + } + } else if (isObjectLike(value)) { + if (isPlainObject(value)) { + return { + type: 'object', + elements: [...map(values(value), element => serialize(element))] + }; + } else if (value[serializeTag] !== undefined + && typeof(value[serializeTag]) === 'function' + && typeof(value.constructor.name) === 'string') { + return { + type: 'class', + name: value.constructor.name, + data: value[serializeTag](), + } + } else { + throw new Error(`Could not serialize ${value}: it was a non-primitive object and has no serializer tag.`) + } + } else if (Array.isArray(value)) { + return { + type: 'array', + elements: value.map(serialize) + } + } else { + throw new Error(`Could not serialize ${value}: is was not recognised as a primitive type, an object, a class instance, or an array.`) + } +} + export type TransparentProxy = T & { updateHandle(value: T): void } export function createTransparentProxy(value: T): TransparentProxy {