mirror of
https://github.com/GrammaticalFramework/gf-core.git
synced 2026-04-10 13:29:32 -06:00
Add proper tests, exception handling, implement getAbstractName
This commit is contained in:
49
src/runtime/javascript/errno.ts
Normal file
49
src/runtime/javascript/errno.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
interface ErrorInfo {
|
||||
errno: number
|
||||
code: string
|
||||
description: string
|
||||
}
|
||||
|
||||
// taken from /usr/include/asm-generic/errno-base.h
|
||||
const errs: ErrorInfo[] = [
|
||||
{ code: 'EPERM', errno: 1, description: 'Operation not permitted' },
|
||||
{ code: 'ENOENT', errno: 2, description: 'No such file or directory' },
|
||||
{ code: 'ESRCH', errno: 3, description: 'No such process' },
|
||||
{ code: 'EINTR', errno: 4, description: 'Interrupted system call' },
|
||||
{ code: 'EIO', errno: 5, description: 'I/O error' },
|
||||
{ code: 'ENXIO', errno: 6, description: 'No such device or address' },
|
||||
{ code: 'E2BIG', errno: 7, description: 'Argument list too long' },
|
||||
{ code: 'ENOEXEC', errno: 8, description: 'Exec format error' },
|
||||
{ code: 'EBADF', errno: 9, description: 'Bad file number' },
|
||||
{ code: 'ECHILD', errno: 10, description: 'No child processes' },
|
||||
{ code: 'EAGAIN', errno: 11, description: 'Try again' },
|
||||
{ code: 'ENOMEM', errno: 12, description: 'Out of memory' },
|
||||
{ code: 'EACCES', errno: 13, description: 'Permission denied' },
|
||||
{ code: 'EFAULT', errno: 14, description: 'Bad address' },
|
||||
{ code: 'ENOTBLK', errno: 15, description: 'Block device required' },
|
||||
{ code: 'EBUSY', errno: 16, description: 'Device or resource busy' },
|
||||
{ code: 'EEXIST', errno: 17, description: 'File exists' },
|
||||
{ code: 'EXDEV', errno: 18, description: 'Cross-device link' },
|
||||
{ code: 'ENODEV', errno: 19, description: 'No such device' },
|
||||
{ code: 'ENOTDIR', errno: 20, description: 'Not a directory' },
|
||||
{ code: 'EISDIR', errno: 21, description: 'Is a directory' },
|
||||
{ code: 'EINVAL', errno: 22, description: 'Invalid argument' },
|
||||
{ code: 'ENFILE', errno: 23, description: 'File table overflow' },
|
||||
{ code: 'EMFILE', errno: 24, description: 'Too many open files' },
|
||||
{ code: 'ENOTTY', errno: 25, description: 'Not a typewriter' },
|
||||
{ code: 'ETXTBSY', errno: 26, description: 'Text file busy' },
|
||||
{ code: 'EFBIG', errno: 27, description: 'File too large' },
|
||||
{ code: 'ENOSPC', errno: 28, description: 'No space left on device' },
|
||||
{ code: 'ESPIPE', errno: 29, description: 'Illegal seek' },
|
||||
{ code: 'EROFS', errno: 30, description: 'Read-only file system' },
|
||||
{ code: 'EMLINK', errno: 31, description: 'Too many links' },
|
||||
{ code: 'EPIPE', errno: 32, description: 'Broken pipe' },
|
||||
{ code: 'EDOM', errno: 33, description: 'Math argument out of domain of func' },
|
||||
{ code: 'ERANGE', errno: 34, description: 'Math result not representable' }
|
||||
]
|
||||
|
||||
export default {
|
||||
lookup(errno: number): string | undefined {
|
||||
return errs.find(err => err.errno === errno)?.description
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,122 @@
|
||||
import errno from './errno'
|
||||
import ffi from 'ffi'
|
||||
import { deref } from 'ref'
|
||||
import * as ref from 'ref'
|
||||
import * as ffi from 'ffi'
|
||||
import ref from 'ref'
|
||||
import Struct from 'ref-struct'
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// FFI "Types"
|
||||
|
||||
const PgfDB = ref.types.int
|
||||
const PgfDBPtr = ref.refType(PgfDB)
|
||||
const PgfRevision = ref.types.int
|
||||
const PgfRevisionPtr = ref.refType(PgfRevision)
|
||||
const PgfExn = ref.types.int
|
||||
const PgfExnPtr = ref.refType(PgfExn)
|
||||
// const stringPtr = ref.refType(ref.types.CString)
|
||||
|
||||
const PGF = ffi.Library('libpgf', {
|
||||
'pgf_read_pgf': [ PgfDBPtr, [ 'string', PgfRevisionPtr, PgfExnPtr ] ],
|
||||
'pgf_write_pgf': [ 'void', [ 'string', PgfDBPtr, PgfRevisionPtr, PgfExnPtr ] ],
|
||||
const PgfExn = Struct({
|
||||
type: ref.types.int,
|
||||
code: ref.types.int,
|
||||
msg: ref.types.CString
|
||||
})
|
||||
const PgfExnPtr = ref.refType(PgfExn)
|
||||
const PgfText = Struct({
|
||||
size: ref.types.size_t,
|
||||
text: ref.types.char // char[]
|
||||
})
|
||||
const PgfTextPtr = ref.refType(PgfText)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TypeScript Types
|
||||
|
||||
interface Pointer extends Buffer {
|
||||
deref(): any
|
||||
}
|
||||
|
||||
interface State {
|
||||
db: Pointer,
|
||||
revision: number
|
||||
}
|
||||
const state: State = {
|
||||
db: ref.NULL_POINTER as Pointer,
|
||||
revision: 0
|
||||
}
|
||||
|
||||
export default {
|
||||
readPGF: function (path: string) {
|
||||
const rev = ref.alloc(PgfRevision) as Pointer
|
||||
const err = ref.alloc(PgfExn) as Pointer
|
||||
const db = PGF.pgf_read_pgf(path, rev, err)
|
||||
if (err.deref() === 0) {
|
||||
state.db = db
|
||||
state.revision = rev.deref()
|
||||
console.log(`ok`)
|
||||
} else {
|
||||
console.error(`error ${err.deref()}`)
|
||||
}
|
||||
class PGFError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// FFI
|
||||
|
||||
const runtime = ffi.Library('libpgf', {
|
||||
'pgf_read_pgf': [ PgfDBPtr, [ ref.types.CString, PgfRevisionPtr, PgfExnPtr ] ],
|
||||
'pgf_write_pgf': [ ref.types.void, [ ref.types.CString, PgfDBPtr, PgfRevisionPtr, PgfExnPtr ] ],
|
||||
'pgf_abstract_name': [ PgfTextPtr, [ PgfDBPtr, PgfRevision, PgfExnPtr ] ],
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
||||
function handleError (errPtr: Pointer): void {
|
||||
const err = errPtr.deref()
|
||||
switch (err.type) {
|
||||
// PGF_EXN_NONE
|
||||
case 0: return
|
||||
|
||||
// PGF_EXN_SYSTEM_ERROR
|
||||
case 1:
|
||||
const desc = errno.lookup(err.code)
|
||||
throw new Error(`${desc}: ${err.msg}`)
|
||||
|
||||
// PGF_EXN_PGF_ERROR
|
||||
case 2:
|
||||
throw new PGFError(err.msg)
|
||||
|
||||
// PGF_EXN_OTHER_ERROR
|
||||
case 3:
|
||||
throw new Error(err.msg)
|
||||
|
||||
default:
|
||||
throw new Error(`unknown error type: ${err.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
function PgfText_AsString (txtPtr: Pointer) {
|
||||
const txtSize = txtPtr.deref().size
|
||||
const charPtr = ref.reinterpret(txtPtr, txtSize, ref.types.size_t.size)
|
||||
return charPtr.toString('utf8')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PGF grammar object
|
||||
|
||||
class PGF {
|
||||
db: Pointer
|
||||
revision: number
|
||||
|
||||
constructor () {
|
||||
this.db = ref.NULL_POINTER as Pointer
|
||||
this.revision = 0
|
||||
}
|
||||
|
||||
getAbstractName () {
|
||||
const err = ref.alloc(PgfExn) as Pointer
|
||||
const txt = runtime.pgf_abstract_name(this.db, this.revision, err)
|
||||
handleError(err)
|
||||
return PgfText_AsString(txt)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PGF module functions
|
||||
|
||||
function readPGF (path: string): PGF {
|
||||
const rev = ref.alloc(PgfRevision) as Pointer
|
||||
const err = ref.alloc(PgfExn) as Pointer
|
||||
const db = runtime.pgf_read_pgf(path, rev, err)
|
||||
handleError(err)
|
||||
|
||||
const state = new PGF()
|
||||
state.db = db
|
||||
state.revision = rev.deref()
|
||||
return state
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Exposed library API
|
||||
|
||||
export default {
|
||||
readPGF
|
||||
}
|
||||
|
||||
5
src/runtime/javascript/jest.config.js
Normal file
5
src/runtime/javascript/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node'
|
||||
};
|
||||
3196
src/runtime/javascript/package-lock.json
generated
3196
src/runtime/javascript/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "npx tsc",
|
||||
"test": "ts-node ./tests/basic.ts"
|
||||
"test": "npx jest"
|
||||
},
|
||||
"author": "John J. Camilleri",
|
||||
"license": "ISC",
|
||||
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ffi": "^0.2.3",
|
||||
"ts-node": "^10.2.1",
|
||||
"@types/jest": "^27.0.2",
|
||||
"jest": "^27.2.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
16
src/runtime/javascript/tests/basic.test.ts
Normal file
16
src/runtime/javascript/tests/basic.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import PGF from '../index'
|
||||
|
||||
test('readPGF successful', () => {
|
||||
let gr = PGF.readPGF('../haskell/tests/basic.pgf')
|
||||
})
|
||||
|
||||
test('readPGF missing file', () => {
|
||||
expect(() => {
|
||||
PGF.readPGF('abc.pgf')
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
test('abstract name', () => {
|
||||
let gr = PGF.readPGF('../haskell/tests/basic.pgf')
|
||||
expect(gr.getAbstractName()).toBe('basic')
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
import PGF from '../index'
|
||||
|
||||
PGF.readPGF('../haskell/tests/basic.pgf')
|
||||
Reference in New Issue
Block a user