From a27cf6a17b2e2edbd0d694a1903eb9fa1939f36f Mon Sep 17 00:00:00 2001 From: "John J. Camilleri" Date: Fri, 8 Oct 2021 12:39:42 +0200 Subject: [PATCH 1/3] Implement all Expr unmarshalling (untested). Put wordsize logic in constants.ts. Some README additions. --- src/runtime/c/README.md | 9 +++- src/runtime/javascript/README.md | 9 ++++ src/runtime/javascript/constants.ts | 39 ++++++++++++++ src/runtime/javascript/expr.ts | 67 +++++++++++++++++++++-- src/runtime/javascript/ffi.ts | 82 +++++++++++++++++++++-------- 5 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 src/runtime/javascript/constants.ts diff --git a/src/runtime/c/README.md b/src/runtime/c/README.md index be9d8d347..011ba33a3 100644 --- a/src/runtime/c/README.md +++ b/src/runtime/c/README.md @@ -2,7 +2,9 @@ ## Requirements -Required system packages (Debian/Ubuntu): +### Debian/Ubuntu + +Required system packages (`apt install ...`): ``` autoconf automake @@ -11,6 +13,11 @@ make g++ ``` +### macOS + +- Install XCode from App Store +- Install XCode command line tools: `xcode-select --install` + ## Installation Installing the runtime (puts libraries in `/usr/local/lib`): diff --git a/src/runtime/javascript/README.md b/src/runtime/javascript/README.md index 164b0f6d8..a0c4052aa 100644 --- a/src/runtime/javascript/README.md +++ b/src/runtime/javascript/README.md @@ -30,3 +30,12 @@ npm run build ``` npm run test ``` + +## Known issues + +There is a known issue with random crashes with Node 14 and 16 on macOS. See here: +- https://github.com/nodejs/node/issues/32463 +- https://github.com/node-ffi-napi/node-ffi-napi/issues/97 +- https://github.com/node-ffi-napi/ref-napi/issues/47 + +It seems to work with Node 12 on macOS, as well as on other platforms. diff --git a/src/runtime/javascript/constants.ts b/src/runtime/javascript/constants.ts new file mode 100644 index 000000000..86d471f15 --- /dev/null +++ b/src/runtime/javascript/constants.ts @@ -0,0 +1,39 @@ +// Untested! +import os from 'os' + +export const BE: boolean = os.endianness() === 'BE' + +export let WORDSIZE_BITS: number +switch (process.arch) { + case 'arm': + case 'ia32': + case 'ppc': + case 's390': + case 'x32': + WORDSIZE_BITS = 32 + break + + case 'arm64': + case 'mips': + case 'mipsel': + case 'ppc64': + case 's390x': + case 'x64': + WORDSIZE_BITS = 64 + break + + default: + throw Error(`Unknown architecture: ${process.arch}`) +} + +export const WORDSIZE_BYTES = WORDSIZE_BITS / 8 + +export let LINT_BASE: bigint + +switch (WORDSIZE_BITS) { + case 64: LINT_BASE = BigInt('10000000000000000000'); break + case 32: LINT_BASE = BigInt('1000000000'); break + case 16: LINT_BASE = BigInt('10000'); break + case 8: LINT_BASE = BigInt('100'); break + default: throw Error(`Unsupported word size: ${WORDSIZE_BITS}`) +} diff --git a/src/runtime/javascript/expr.ts b/src/runtime/javascript/expr.ts index 5b6d7b90b..663c92ae9 100644 --- a/src/runtime/javascript/expr.ts +++ b/src/runtime/javascript/expr.ts @@ -1,7 +1,13 @@ export class Type { - hypos!: Hypo[] - name!: string - exprs!: Expr[] + hypos: Hypo[] + name: string + exprs: Expr[] + + constructor (hs: Hypo[], n: string, es: Expr[]) { + this.hypos = hs + this.name = n + this.exprs = es + } } export class Hypo { @@ -11,6 +17,7 @@ export class Hypo { } export class Expr { + // TODO overload constructor (n?: number) { if (n != null) { return new ExprLit(n) @@ -24,13 +31,32 @@ export class Expr { } export class ExprAbs extends Expr { + bind_type: boolean + name: string + body: Expr + constructor (bt: boolean, n: string, b: Expr) { + super() + this.bind_type = bt + this.name = n + this.body = b + } } + export class ExprApp extends Expr { + fun: Expr + arg: Expr + constructor (f: Expr, a: Expr) { + super() + this.fun = f + this.arg = a + } } + export class ExprLit extends Expr { lit: Literal + constructor (l: Literal | number | bigint | string) { super() if (l instanceof Literal) this.lit = l @@ -41,24 +67,57 @@ export class ExprLit extends Expr { return this.lit.toString() } } + export class ExprMeta extends Expr { + id: number + constructor (i: number) { + super() + this.id = i + } } + export class ExprFun extends Expr { + name: string + constructor (n: string) { + super() + this.name = n + } } + export class ExprVar extends Expr { + var: number + constructor (v: number) { + super() + this.var = v + } } + export class ExprTyped extends Expr { + expr: Expr + type: Type + constructor (e: Expr, t: Type) { + super() + this.expr = e + this.type = t + } } -export class ExprImplArg extends Expr { +export class ExprImplArg extends Expr { + expr: Expr + + constructor (e: Expr) { + super() + this.expr = e + } } export class Literal { val: number | bigint | string + constructor (v: number | bigint | string) { this.val = v } diff --git a/src/runtime/javascript/ffi.ts b/src/runtime/javascript/ffi.ts index 89df95197..b5190a547 100644 --- a/src/runtime/javascript/ffi.ts +++ b/src/runtime/javascript/ffi.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Type, - // Hypo, + Hypo, + Expr, ExprAbs, ExprApp, ExprLit, @@ -12,8 +13,8 @@ import { ExprImplArg, Literal } from './expr' +import { BE, WORDSIZE_BITS, WORDSIZE_BYTES, LINT_BASE } from './constants' -import os from 'os' import ffi from 'ffi-napi' import ref, { Pointer } from 'ref-napi' import ref_struct from 'ref-struct-di' @@ -47,7 +48,7 @@ export const PgfTextPtr = ref.refType(PgfText) export const PgfItorPtr = ref.refType(ref.types.void) -const PgfType = ref.types.void // TODO +const PgfType = ref.refType(ref.types.Object) // TODO // const PgfTypePtr = ref.refType(PgfType) const PgfTypeHypo = ref.types.void // TODO @@ -159,17 +160,24 @@ export function PgfText_FromString (str: string): Pointer { const eabs = ffi.Callback(PgfExpr, [PgfUnmarshaller, ref.types.bool, PgfTextPtr, PgfExpr], function (self, btype: boolean, name: Pointer, body: Pointer): Pointer { - return 0 as any + const jsname = PgfText_AsString(name) + const obj = new ExprAbs(btype, jsname, body) // TODO check body + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const eapp = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr, PgfExpr], - function (self, fun: Pointer, arg: Pointer): Pointer { - return 0 as any + function (self, fun: Pointer, arg: Pointer): Pointer { + const obj = new ExprApp(fun, arg) // TODO check + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const elit = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfLiteral], - function (self, lit: Pointer): Pointer { - const litObj = lit.deref() as Literal + function (self, lit: Pointer): Pointer { + const litObj = lit.deref() const obj = new ExprLit(litObj) const buf = ref.alloc(ref.types.Object) as Pointer ref.writeObject(buf, 0, obj) @@ -177,35 +185,46 @@ const elit = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfLiteral], }) const emeta = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfMetaId], - function (self, meta: Pointer): Pointer { - return 0 as any + function (self, meta: number): Pointer { + const obj = new ExprMeta(meta) + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const efun = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfTextPtr], function (self, name: Pointer): Pointer { - return 0 as any + const jsname = PgfText_AsString(name) + const obj = new ExprFun(jsname) + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const evar = ffi.Callback(PgfExpr, [PgfUnmarshaller, ref.types.int], function (self, index: number): Pointer { - return 0 as any + const obj = new ExprVar(index) + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const etyped = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr, PgfType], - function (self, expr: Pointer, type: Pointer): Pointer { - return 0 as any + function (self, expr: Pointer, type: Pointer): Pointer { + const obj = new ExprTyped(expr, type.deref()) // TODO check + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const eimplarg = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr], - function (self, expr: Pointer): Pointer { - return 0 as any + function (self, expr: Pointer): Pointer { + const obj = new ExprImplArg(expr) // TODO check + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) -// TODO get from platform/runtime -const WORDSIZE_BITS = 64 -const WORDSIZE_BYTES = WORDSIZE_BITS / 8 -const LINT_BASE = BigInt('10000000000000000000') - const lint = ffi.Callback(PgfLiteral, [PgfUnmarshaller, ref.types.size_t, ref.refType(ref.types.int)], function (self, size: number, val: Pointer): Pointer { let jsval: number | bigint = 0 @@ -219,7 +238,17 @@ const lint = ffi.Callback(PgfLiteral, [PgfUnmarshaller, ref.types.size_t, ref.re let thisval switch (WORDSIZE_BITS) { case 64: - thisval = BigInt((os.endianness() === 'LE') ? vals.readUInt64LE(n * WORDSIZE_BYTES) : vals.readUInt64BE(n * WORDSIZE_BYTES)) + thisval = BigInt(BE ? vals.readUInt64BE(n * WORDSIZE_BYTES) : vals.readUInt64LE(n * WORDSIZE_BYTES)) + break + case 32: + thisval = BigInt(BE ? vals.readUInt32BE(n * WORDSIZE_BYTES) : vals.readUInt32LE(n * WORDSIZE_BYTES)) + break + case 16: + thisval = BigInt(BE ? vals.readUInt16BE(n * WORDSIZE_BYTES) : vals.readUInt16LE(n * WORDSIZE_BYTES)) + break + case 8: + thisval = BigInt(vals.readUInt8(n * WORDSIZE_BYTES)) + break } if (thisval == null) { throw Error(`Unsupported word size: ${WORDSIZE_BITS}`) @@ -253,7 +282,14 @@ const lstr = ffi.Callback(PgfLiteral, [PgfUnmarshaller, PgfTextPtr], const dtyp = ffi.Callback(PgfType, [PgfUnmarshaller, ref.types.int, PgfTypeHypoPtr, PgfTextPtr, ref.types.int, ref.refType(PgfExpr)], function (self, n_hypos: number, hypos: Pointer, cat: Pointer, n_exprs: number, exprs: Pointer): Pointer { - return 0 as any + // TODO + const jshypos: Hypo[] = [] + const jsname = PgfText_AsString(cat) + const jsexprs: Expr[] = [] + const obj = new Type(jshypos, jsname, jsexprs) + const buf = ref.alloc(ref.types.Object) as Pointer + ref.writeObject(buf, 0, obj) + return buf }) const free_ref = ffi.Callback(ref.types.void, [PgfUnmarshaller, ref.refType(ref.types.Object)], From 71536e8e37d8c9aea5d74413ce152dd36f12d7f9 Mon Sep 17 00:00:00 2001 From: "John J. Camilleri" Date: Fri, 8 Oct 2021 12:54:36 +0200 Subject: [PATCH 2/3] Handle errors in readExpr --- src/runtime/javascript/index.ts | 3 + src/runtime/javascript/tests/basic.test.ts | 136 +++++++++++---------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/src/runtime/javascript/index.ts b/src/runtime/javascript/index.ts index d22294563..d44c92a07 100644 --- a/src/runtime/javascript/index.ts +++ b/src/runtime/javascript/index.ts @@ -201,6 +201,9 @@ function newNGF (abstract_name: string, path?: string): PGFGrammar { function readExpr (str: string): Expr { const txt = PgfText_FromString(str) const expr = runtime.pgf_read_expr(txt, unmarshaller.ref()) + if (ref.isNull(expr)) { + throw new PGFError('unable to parse expression') + } return ref.readObject(expr) as Expr } diff --git a/src/runtime/javascript/tests/basic.test.ts b/src/runtime/javascript/tests/basic.test.ts index f2303521e..3090dd360 100644 --- a/src/runtime/javascript/tests/basic.test.ts +++ b/src/runtime/javascript/tests/basic.test.ts @@ -210,79 +210,87 @@ describe('abstract syntax', () => { // ---------------------------------------------------------------------------- describe('expressions', () => { - test('small integer', () => { - const e1 = PGF.readExpr('123') - const e2 = new PGF.ExprLit(123) - const e3 = new PGF.ExprLit(456) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) + test('invalid', () => { + expect(() => { + PGF.readExpr('->') + }).toThrow(PGF.PGFError) }) - test('negative integer', () => { - const e1 = PGF.readExpr('-123') - const e2 = new PGF.ExprLit(-123) - const e3 = new PGF.ExprLit(-456) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + describe('literals', () => { + test('small integer', () => { + const e1 = PGF.readExpr('123') + const e2 = new PGF.ExprLit(123) + const e3 = new PGF.ExprLit(456) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('big integer', () => { - const e1 = PGF.readExpr('774763251095801167872') - const e2 = new PGF.ExprLit(BigInt('774763251095801167872')) - expect(e1).toEqual(e2) - }) + test('negative integer', () => { + const e1 = PGF.readExpr('-123') + const e2 = new PGF.ExprLit(-123) + const e3 = new PGF.ExprLit(-456) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('negative big integer', () => { - const e1 = PGF.readExpr('-774763251095801167872') - const e2 = new PGF.ExprLit(BigInt('-774763251095801167872')) - expect(e1).toEqual(e2) - }) + test('big integer', () => { + const e1 = PGF.readExpr('774763251095801167872') + const e2 = new PGF.ExprLit(BigInt('774763251095801167872')) + expect(e1).toEqual(e2) + }) - test('really big integer', () => { - const e1 = PGF.readExpr('7747632510958011678729003251095801167999') - const e2 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167999')) - const e3 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167990')) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + test('negative big integer', () => { + const e1 = PGF.readExpr('-774763251095801167872') + const e2 = new PGF.ExprLit(BigInt('-774763251095801167872')) + expect(e1).toEqual(e2) + }) - test('negative really big integer', () => { - const e1 = PGF.readExpr('-7747632510958011678729003251095801167999') - const e2 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167999')) - const e3 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167990')) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + test('really big integer', () => { + const e1 = PGF.readExpr('7747632510958011678729003251095801167999') + const e2 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167999')) + const e3 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167990')) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('float', () => { - const e1 = PGF.readExpr('3.142') - const e2 = new PGF.ExprLit(3.142) - const e3 = new PGF.ExprLit(2.014) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + test('negative really big integer', () => { + const e1 = PGF.readExpr('-7747632510958011678729003251095801167999') + const e2 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167999')) + const e3 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167990')) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('negative float', () => { - const e1 = PGF.readExpr('-3.142') - const e2 = new PGF.ExprLit(-3.142) - const e3 = new PGF.ExprLit(-2.014) - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + test('float', () => { + const e1 = PGF.readExpr('3.142') + const e2 = new PGF.ExprLit(3.142) + const e3 = new PGF.ExprLit(2.014) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('string', () => { - const e1 = PGF.readExpr('"abc"') - const e2 = new PGF.ExprLit('abc') - const e3 = new PGF.ExprLit('def') - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) - }) + test('negative float', () => { + const e1 = PGF.readExpr('-3.142') + const e2 = new PGF.ExprLit(-3.142) + const e3 = new PGF.ExprLit(-2.014) + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) - test('string unicode', () => { - const e1 = PGF.readExpr('"açġħ"') - const e2 = new PGF.ExprLit('açġħ') - const e3 = new PGF.ExprLit('acgh') - expect(e1).toEqual(e2) - expect(e1).not.toEqual(e3) + test('string', () => { + const e1 = PGF.readExpr('"abc"') + const e2 = new PGF.ExprLit('abc') + const e3 = new PGF.ExprLit('def') + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) + + test('string unicode', () => { + const e1 = PGF.readExpr('"açġħ"') + const e2 = new PGF.ExprLit('açġħ') + const e3 = new PGF.ExprLit('acgh') + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) }) }) From 0e011955be501fb9e485fd1caa0d124a6eb26fbf Mon Sep 17 00:00:00 2001 From: "John J. Camilleri" Date: Fri, 8 Oct 2021 15:06:34 +0200 Subject: [PATCH 3/3] Add tests for reading & equality of various expressions --- src/runtime/javascript/expr.ts | 5 +- src/runtime/javascript/ffi.ts | 10 +- src/runtime/javascript/index.ts | 66 ++++++++- src/runtime/javascript/tests/basic.test.ts | 149 +++++++++++++++++---- 4 files changed, 193 insertions(+), 37 deletions(-) diff --git a/src/runtime/javascript/expr.ts b/src/runtime/javascript/expr.ts index 663c92ae9..e598b55c5 100644 --- a/src/runtime/javascript/expr.ts +++ b/src/runtime/javascript/expr.ts @@ -71,9 +71,10 @@ export class ExprLit extends Expr { export class ExprMeta extends Expr { id: number - constructor (i: number) { + constructor (i?: number) { super() - this.id = i + if (i != null) this.id = i + else this.id = 0 } } diff --git a/src/runtime/javascript/ffi.ts b/src/runtime/javascript/ffi.ts index b5190a547..4dd78a416 100644 --- a/src/runtime/javascript/ffi.ts +++ b/src/runtime/javascript/ffi.ts @@ -159,9 +159,9 @@ export function PgfText_FromString (str: string): Pointer { // Un/marshalling const eabs = ffi.Callback(PgfExpr, [PgfUnmarshaller, ref.types.bool, PgfTextPtr, PgfExpr], - function (self, btype: boolean, name: Pointer, body: Pointer): Pointer { + function (self, btype: boolean, name: Pointer, body: Pointer): Pointer { const jsname = PgfText_AsString(name) - const obj = new ExprAbs(btype, jsname, body) // TODO check body + const obj = new ExprAbs(btype, jsname, body.deref()) const buf = ref.alloc(ref.types.Object) as Pointer ref.writeObject(buf, 0, obj) return buf @@ -169,7 +169,7 @@ const eabs = ffi.Callback(PgfExpr, [PgfUnmarshaller, ref.types.bool, PgfTextPtr, const eapp = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr, PgfExpr], function (self, fun: Pointer, arg: Pointer): Pointer { - const obj = new ExprApp(fun, arg) // TODO check + const obj = new ExprApp(fun.deref(), arg.deref()) const buf = ref.alloc(ref.types.Object) as Pointer ref.writeObject(buf, 0, obj) return buf @@ -211,7 +211,7 @@ const evar = ffi.Callback(PgfExpr, [PgfUnmarshaller, ref.types.int], const etyped = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr, PgfType], function (self, expr: Pointer, type: Pointer): Pointer { - const obj = new ExprTyped(expr, type.deref()) // TODO check + const obj = new ExprTyped(expr.deref(), type.deref()) const buf = ref.alloc(ref.types.Object) as Pointer ref.writeObject(buf, 0, obj) return buf @@ -219,7 +219,7 @@ const etyped = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr, PgfType], const eimplarg = ffi.Callback(PgfExpr, [PgfUnmarshaller, PgfExpr], function (self, expr: Pointer): Pointer { - const obj = new ExprImplArg(expr) // TODO check + const obj = new ExprImplArg(expr.deref()) const buf = ref.alloc(ref.types.Object) as Pointer ref.writeObject(buf, 0, obj) return buf diff --git a/src/runtime/javascript/index.ts b/src/runtime/javascript/index.ts index d44c92a07..753484eeb 100644 --- a/src/runtime/javascript/index.ts +++ b/src/runtime/javascript/index.ts @@ -1,7 +1,20 @@ /* eslint-disable @typescript-eslint/naming-convention */ import errno from './errno' -import { Expr, ExprLit } from './expr' +import { + Type, + Hypo, + Expr, + ExprAbs, + ExprApp, + ExprLit, + ExprMeta, + ExprFun, + ExprVar, + ExprTyped, + ExprImplArg, + Literal +} from './expr' import { voidPtr, PgfRevisionPtr, @@ -67,7 +80,7 @@ function handleError (err: StructObject): void { // ---------------------------------------------------------------------------- // PGF grammar object -export class PGFGrammar { +class PGFGrammar { readonly db: Pointer readonly revision: Pointer @@ -198,6 +211,15 @@ function newNGF (abstract_name: string, path?: string): PGFGrammar { return new PGFGrammar(db, rev) } +function readType (str: string): Type { + const txt = PgfText_FromString(str) + const type = runtime.pgf_read_type(txt, unmarshaller.ref()) + if (ref.isNull(type)) { + throw new PGFError('unable to parse type') + } + return ref.readObject(type) as Type +} + function readExpr (str: string): Expr { const txt = PgfText_FromString(str) const expr = runtime.pgf_read_expr(txt, unmarshaller.ref()) @@ -210,15 +232,51 @@ function readExpr (str: string): Expr { // ---------------------------------------------------------------------------- // Exposed library API -export default { +export { + PGFGrammar, PGFError, readPGF, bootNGF, readNGF, newNGF, + readType, readExpr, + Type, + Hypo, Expr, - ExprLit + ExprAbs, + ExprApp, + ExprLit, + ExprMeta, + ExprFun, + ExprVar, + ExprTyped, + ExprImplArg, + Literal +} +export default { + PGFGrammar, + PGFError, + readPGF, + bootNGF, + readNGF, + newNGF, + + readType, + readExpr, + + Type, + Hypo, + Expr, + ExprAbs, + ExprApp, + ExprLit, + ExprMeta, + ExprFun, + ExprVar, + ExprTyped, + ExprImplArg, + Literal } diff --git a/src/runtime/javascript/tests/basic.test.ts b/src/runtime/javascript/tests/basic.test.ts index 3090dd360..a01046fe2 100644 --- a/src/runtime/javascript/tests/basic.test.ts +++ b/src/runtime/javascript/tests/basic.test.ts @@ -1,4 +1,19 @@ -import PGF, { PGFGrammar } from '../index' +import PGF, { + PGFError, + PGFGrammar, + // Type, + // Hypo, + // Expr, + // ExprAbs, + ExprApp, + ExprLit, + ExprMeta, + ExprFun, + // ExprVar, + // ExprTyped, + ExprImplArg + // Literal +} from '../index' import fs from 'fs' // ---------------------------------------------------------------------------- @@ -17,7 +32,7 @@ describe('readPGF', () => { test('GF', () => { expect(() => { PGF.readPGF('../haskell/tests/basic.gf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) test('NGF', () => { @@ -29,7 +44,7 @@ describe('readPGF', () => { PGF.bootNGF('../haskell/tests/basic.pgf', './basic.ngf') expect(() => { PGF.readPGF('./basic.ngf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) }) @@ -57,13 +72,13 @@ describe('bootNGF', () => { test('GF', () => { expect(() => { PGF.bootNGF('../haskell/tests/basic.gf', './abc.ngf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) test('NGF', () => { expect(() => { PGF.bootNGF('./basic.ngf', './abc.ngf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) test('existing', () => { @@ -98,13 +113,13 @@ describe('readNGF', () => { test('GF', () => { expect(() => { PGF.readNGF('../haskell/tests/basic.gf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) test('PGF', () => { expect(() => { PGF.readNGF('../haskell/tests/basic.pgf') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) }) @@ -209,88 +224,170 @@ describe('abstract syntax', () => { // ---------------------------------------------------------------------------- +describe('types', () => { + test('invalid', () => { + expect(() => { + PGF.readType('->') + }).toThrow(PGFError) + }) +}) + +// ---------------------------------------------------------------------------- + describe('expressions', () => { test('invalid', () => { expect(() => { PGF.readExpr('->') - }).toThrow(PGF.PGFError) + }).toThrow(PGFError) }) describe('literals', () => { test('small integer', () => { const e1 = PGF.readExpr('123') - const e2 = new PGF.ExprLit(123) - const e3 = new PGF.ExprLit(456) + const e2 = new ExprLit(123) + const e3 = new ExprLit(456) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('negative integer', () => { const e1 = PGF.readExpr('-123') - const e2 = new PGF.ExprLit(-123) - const e3 = new PGF.ExprLit(-456) + const e2 = new ExprLit(-123) + const e3 = new ExprLit(-456) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('big integer', () => { const e1 = PGF.readExpr('774763251095801167872') - const e2 = new PGF.ExprLit(BigInt('774763251095801167872')) + const e2 = new ExprLit(BigInt('774763251095801167872')) expect(e1).toEqual(e2) }) test('negative big integer', () => { const e1 = PGF.readExpr('-774763251095801167872') - const e2 = new PGF.ExprLit(BigInt('-774763251095801167872')) + const e2 = new ExprLit(BigInt('-774763251095801167872')) expect(e1).toEqual(e2) }) test('really big integer', () => { const e1 = PGF.readExpr('7747632510958011678729003251095801167999') - const e2 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167999')) - const e3 = new PGF.ExprLit(BigInt('7747632510958011678729003251095801167990')) + const e2 = new ExprLit(BigInt('7747632510958011678729003251095801167999')) + const e3 = new ExprLit(BigInt('7747632510958011678729003251095801167990')) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('negative really big integer', () => { const e1 = PGF.readExpr('-7747632510958011678729003251095801167999') - const e2 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167999')) - const e3 = new PGF.ExprLit(BigInt('-7747632510958011678729003251095801167990')) + const e2 = new ExprLit(BigInt('-7747632510958011678729003251095801167999')) + const e3 = new ExprLit(BigInt('-7747632510958011678729003251095801167990')) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('float', () => { const e1 = PGF.readExpr('3.142') - const e2 = new PGF.ExprLit(3.142) - const e3 = new PGF.ExprLit(2.014) + const e2 = new ExprLit(3.142) + const e3 = new ExprLit(2.014) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('negative float', () => { const e1 = PGF.readExpr('-3.142') - const e2 = new PGF.ExprLit(-3.142) - const e3 = new PGF.ExprLit(-2.014) + const e2 = new ExprLit(-3.142) + const e3 = new ExprLit(-2.014) expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('string', () => { const e1 = PGF.readExpr('"abc"') - const e2 = new PGF.ExprLit('abc') - const e3 = new PGF.ExprLit('def') + const e2 = new ExprLit('abc') + const e3 = new ExprLit('def') expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) test('string unicode', () => { const e1 = PGF.readExpr('"açġħ"') - const e2 = new PGF.ExprLit('açġħ') - const e3 = new PGF.ExprLit('acgh') + const e2 = new ExprLit('açġħ') + const e3 = new ExprLit('acgh') expect(e1).toEqual(e2) expect(e1).not.toEqual(e3) }) }) + + describe('functions', () => { + test('simple', () => { + const e1 = PGF.readExpr('f') + expect(e1).toBeInstanceOf(ExprFun) + expect((e1 as ExprFun).name).toEqual('f') + + const e2 = new ExprFun('f') + const e3 = new ExprFun('g') + expect(e1).toEqual(e2) + expect(e1).not.toEqual(e3) + }) + + test('application 1', () => { + const e1 = PGF.readExpr('f x y') + expect(e1).toBeInstanceOf(ExprApp) + expect((e1 as ExprApp).arg).toEqual(new ExprFun('y')) + + const e2 = new ExprApp( + new ExprApp( + new ExprFun('f'), + new ExprFun('x') + ), new ExprFun('y') + ) + expect(e1).toEqual(e2) + }) + + test('application 2', () => { + const e1 = PGF.readExpr('f (g x)') + const e2 = new ExprApp( + new ExprFun('f'), + new ExprApp( + new ExprFun('g'), + new ExprFun('x') + ) + ) + expect(e1).toEqual(e2) + }) + + test('application 3', () => { + const e1 = PGF.readExpr('f {g x}') + const e2 = new ExprApp( + new ExprFun('f'), + new ExprImplArg( + new ExprApp( + new ExprFun('g'), + new ExprFun('x') + ) + ) + ) + expect(e1).toEqual(e2) + }) + }) + + describe('variables', () => { + test('meta 1', () => { + const e1 = PGF.readExpr('?') + expect(e1).toBeInstanceOf(ExprMeta) + const e2 = new ExprMeta() + const e3 = new ExprMeta(0) + const e4 = new ExprMeta(1) + expect(e1).toEqual(e2) + expect(e1).toEqual(e3) + expect(e1).not.toEqual(e4) + }) + + test('meta 2', () => { + const e1 = PGF.readExpr('?42') + const e2 = new ExprMeta(42) + expect(e1).toEqual(e2) + }) + }) })