diff --git a/src/runtime/typescript/.eslintrc b/src/runtime/typescript/.eslintrc new file mode 100644 index 000000000..c0fa0b95e --- /dev/null +++ b/src/runtime/typescript/.eslintrc @@ -0,0 +1,18 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": ["plugin:@typescript-eslint/recommended"], + "rules": { + "indent": "off", + "@typescript-eslint/indent": ["warn", 2], + "@typescript-eslint/no-use-before-define": ["error", { + "functions": false, + "classes": false + }], + "semi": "off", + "@typescript-eslint/semi": ["warn", "never"], + "quotes": ["warn", "single"], + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], + } +} diff --git a/src/runtime/typescript/gflib.ts b/src/runtime/typescript/gflib.ts index 088e36f0f..f6a18954b 100644 --- a/src/runtime/typescript/gflib.ts +++ b/src/runtime/typescript/gflib.ts @@ -10,6 +10,10 @@ // String.prototype.tag = ""; // String.prototype.setTag = function (tag) { this.tag = tag; }; +interface Taggable { + tag?: string; +} + /** * A GF grammar is one abstract and multiple concretes */ @@ -191,7 +195,7 @@ class GFAbstract { public handleLiterals(tree: Fun, type: string): Fun { if (tree.name != '?') { - if (type === 'String' || type === 'Int' || type === 'Float') { + if (type == 'String' || type == 'Int' || type == 'Float') { tree.name = type + '_Literal_' + tree.name } else { let typ = this.types[tree.name] @@ -232,7 +236,7 @@ class GFAbstract { tokens.shift() return tree } else if (t == '?') { - let tree = this.parseTree_(tokens, 0) + // let tree = this.parseTree_(tokens, 0) return new Fun('?') } else { let tree = new Fun(t) @@ -261,24 +265,22 @@ class Type { } } -type ApplyOrCoerce = Apply | Coerce - /** * Concrete syntax */ class GFConcrete { - private flags: {[key: string]: string} - // private productions: {[key: number]: ApplyOrCoerce[]} + public flags: {[key: string]: string} + // private productions: {[key: number]: Rule[]} private functions: CncFun[] - private sequences: Sym[][] + // private sequences: Sym[][] public startCats: {[key: string]: {s: number; e: number}} public totalFIds: number - public pproductions: {[key: number]: ApplyOrCoerce[]} + public pproductions: {[key: number]: Rule[]} private lproductions: {[key: string]: {fid: FId; fun: CncFun}[]} public constructor( flags: {[key: string]: string}, - productions: {[key: number]: ApplyOrCoerce[]}, + productions: {[key: number]: Rule[]}, functions: CncFun[], sequences: Sym[][], startCats: {[key: string]: {s: number; e: number}}, @@ -287,7 +289,7 @@ class GFConcrete { this.flags = flags // this.productions = productions this.functions = functions - this.sequences = sequences + // this.sequences = sequences this.startCats = startCats this.totalFIds = totalFIds @@ -299,7 +301,7 @@ class GFConcrete { for (let i in productions[fid]) { let rule = productions[fid][i] - if (rule.id === 'Apply') { + if (rule.id == 'Apply') { rule = rule as Apply let fun: CncFun = this.functions[rule.fun as FId] let lproductions = this.lproductions @@ -313,7 +315,7 @@ class GFConcrete { for (let k in productions[arg]) { let rule = productions[arg][k] - if (rule.id === 'Coerce') { + if (rule.id == 'Coerce') { rule = rule as Coerce register(args, key + '_' + rule.arg, i+1) c++ @@ -345,7 +347,7 @@ class GFConcrete { } - private linearizeSyms(tree: Fun, tag: string): {fid: FId; table: any[][]}[] { + private linearizeSyms(tree: Fun, tag: string): {fid: FId; table: Sym[][]}[] { let res = [] if (tree.isString()) { @@ -388,26 +390,29 @@ class GFConcrete { table: [] } for (let j in rule.fun.lins) { - let lin = rule.fun.lins[j] + let lin = rule.fun.lins[j] as Sym[] let toks = [] row.table[j] = toks - for (let k in lin) { - let sym = lin[k] - switch (sym.id) { + lin.forEach((sym0: Sym): void => { + switch (sym0.id) { case 'Arg': - case 'Lit': + case 'Lit': { + let sym = sym0 as SymCat | SymLit let ts = cs[sym.i].table[sym.label] for (let l in ts) { toks.push(ts[l]) } break + } case 'KS': - case 'KP': + case 'KP': { + let sym = sym0 as SymKS | SymKP toks.push(this.tagIt(sym,tag)) break + } } - } + }) } res.push(row) } @@ -418,23 +423,24 @@ class GFConcrete { private syms2toks(syms: Sym[]): string[] { let ts = [] - for (let i in syms) { - let sym = syms[i] - switch (sym.id) { - case 'KS': - sym = sym as SymKS + syms.forEach((sym0: Sym): void => { + switch (sym0.id) { + case 'KS': { + let sym = sym0 as SymKS for (let j in sym.tokens) { ts.push(this.tagIt(sym.tokens[j],sym.tag)) } break - case 'KP': - sym = sym as SymKP + } + case 'KP': { + let sym = sym0 as SymKP for (let j in sym.tokens) { ts.push(this.tagIt(sym.tokens[j],sym.tag)) } break + } } - } + }) return ts } @@ -476,7 +482,7 @@ class GFConcrete { return s } - private tagIt(obj: any, tag: string): any { + private tagIt(obj: Taggable, tag: string): Taggable { if (isString(obj)) { let o = new String(obj) o.setTag(tag) @@ -506,7 +512,8 @@ class GFConcrete { let start: number, end: number let tokens = [] - for (let i = 0; i < string.length; i++) { + let i: number + for (i = 0; i < string.length; i++) { if (string.charAt(i) == ' ' // space || string.charAt(i) == '\f' // form feed || string.charAt(i) == '\n' // newline @@ -593,19 +600,16 @@ class GFConcrete { // Format into just a list of strings & return // (I know the multiple nesting looks horrible) - let suggs = [] + let suggs: string[] = [] if (acc.value) { - // Iterate over all acc.value[] - for (let v = 0; v < acc.value.length; v++) { - // Iterate over all acc.value[].seq[] - for (let s = 0; s < acc.value[v].seq.length; s++) { - if (acc.value[v].seq[s].tokens == null) continue - // Iterate over all acc.value[].seq[].tokens - for (let t = 0; t < acc.value[v].seq[s].tokens.length; t++) { - suggs.push( acc.value[v].seq[s].tokens[t] ) - } - } - } + acc.value.forEach((a: ActiveItem): void =>{ + a.seq.forEach((s: SymKS | SymKP): void => { + if (s.tokens == null) return + s.tokens.forEach((t: string): void => { + suggs.push(t) + }) + }) + }) } // Note: return used tokens too @@ -618,6 +622,11 @@ class GFConcrete { */ type FId = number +/** + * Rule + */ +type Rule = Apply | Coerce | Const + /** * Apply */ @@ -688,8 +697,8 @@ class PArg { * Const */ class Const { - private id: string - private lit: Fun + public id: string + public lit: Fun public toks: string[] public constructor(lit: Fun, toks: string[]) { @@ -759,9 +768,10 @@ class SymCat { /** * SymKS: Object to represent terminals in grammar rules */ -class SymKS { +class SymKS implements Taggable { public id: string public tokens: string[] + public tag?: string public constructor(...tokens: string[]) { this.id = 'KS' @@ -778,10 +788,11 @@ class SymKS { /** * SymKP: Object to represent pre in grammar rules */ -class SymKP { +class SymKP implements Taggable { public id: string public tokens: string[] public alts: Alt[] + public tag?: string public constructor(tokens: string[], alts: Alt[]) { this.id = 'KP' @@ -859,7 +870,7 @@ class Trie { node.value = obj } - private insertChain1(keys: string[], obj: T): void { + public insertChain1(keys: string[], obj: T): void { let node = this for (let i in keys) { let nnode = node.items[keys[i]] @@ -875,7 +886,7 @@ class Trie { node.value.push(obj) } - public lookup(key: string): T { + public lookup(key: string): Trie { return this.items[key] } @@ -897,7 +908,7 @@ class Trie { class ParseState { private concrete: GFConcrete private startCat: string - private items: Trie + private items: Trie private chart: Chart public constructor(concrete: GFConcrete, startCat: string) { @@ -910,14 +921,22 @@ class ParseState { let fids = concrete.startCats[startCat] if (fids != null) { - let fid + let fid: FId for (fid = fids.s; fid <= fids.e; fid++) { let exProds = this.chart.expandForest(fid) for (let j in exProds) { let rule = exProds[j] as Apply let fun = rule.fun as CncFun for (let lbl in fun.lins) { - items.push(new ActiveItem(0,0,rule.fun,fun.lins[lbl],rule.args,fid,lbl)) + items.push(new ActiveItem( + 0, + 0, + rule.fun as CncFun, + fun.lins[lbl] as Sym[], + rule.args, + fid, + parseInt(lbl)) + ) } } } @@ -926,7 +945,7 @@ class ParseState { this.items.insertChain([], items) } - private next(token: string): boolean { + public next(token: string): boolean { let acc = this.items.lookup(token) if (acc == null) { acc = new Trie() @@ -958,7 +977,7 @@ class ParseState { return null }, - function (tokens: string[], item): void { + function (tokens: string[], item: ActiveItem): void { if (tokens[0] == token) { let tokens1 = [] for (let i = 1; i < tokens.length; i++) { @@ -980,7 +999,7 @@ class ParseState { * Based closely on ParseState.next() * currentToken could be empty or a partial string */ - private complete(currentToken: string): Trie { + public complete(currentToken: string): Trie { // Initialise accumulator for suggestions let acc = this.items.lookup(currentToken) @@ -992,13 +1011,13 @@ class ParseState { this.items.value, // Deal with literal categories - function (fid: FId): null { + function (_fid: FId): null { // Always return null, as suggested by Krasimir return null }, // Takes an array of tokens and populates the accumulator - function (tokens: string[], item): void { + function (tokens: string[], item: ActiveItem): void { if (currentToken == '' || tokens[0].indexOf(currentToken) == 0) { //if begins with... let tokens1 = [] for (let i = 1; i < tokens.length; i++) { @@ -1013,13 +1032,13 @@ class ParseState { return acc } - private extractTrees(): Fun[] { + public extractTrees(): Fun[] { this.process( this.items.value, - function (fid: FId): null { + function (_fid: FId): null { return null }, - function (tokens: string[], item): void { + function (_tokens: string[], _item: ActiveItem): void { } ) @@ -1033,10 +1052,11 @@ class ParseState { let trees = [] let rules = forest[fid] - rules.forEach((rule): void => { + rules.forEach((rule: Rule): void => { if (rule.id == 'Const') { - trees.push(rule.lit) + trees.push((rule as Const).lit) } else { + rule = rule as Apply let arg_ix = [] let arg_ts = [] for (let k in rule.args) { @@ -1045,8 +1065,8 @@ class ParseState { } while (true) { - let t = new Fun(rule.fun.name) - for (let k in arg_ts) { + let t = new Fun((rule.fun as CncFun).name) + for (let k = 0; k < arg_ts.length; k++) { t.setArg(k,arg_ts[k][arg_ix[k]]) } trees.push(t) @@ -1180,12 +1200,12 @@ class ParseState { let fid = item.args[sym.i].fid let rules = this.chart.forest[fid] if (rules != null) { - tokenCallback(rules[0].toks, item.shiftOverTokn()) // TODO investigate + tokenCallback((rules[0] as Const).toks, item.shiftOverTokn()) } else { let rule = literalCallback(fid) if (rule != null) { fid = this.chart.nextId++ - this.chart.forest[fid] = [rule] // TODO investigate + this.chart.forest[fid] = [rule] tokenCallback(rule.toks, item.shiftOverArg(sym.i, fid)) } } @@ -1215,7 +1235,7 @@ class ParseState { this.chart.offset, 0, item.fun, - item.fun.lins[lbl], + item.fun.lins[lbl] as Sym[], item.args, fid, parseInt(lbl)) @@ -1241,14 +1261,20 @@ class ParseState { } } +/** + * Map of label to list of ActiveItems + */ +interface ActiveItemMap {[key: number]: ActiveItem[]} + /** * Chart */ class Chart { - private active: {[key: number]: ActiveItem} // key: FId - private actives: {[key: number]: ActiveItem}[] // key: FId + // private active: {[key: number]: ActiveItem} // key: FId + private active: {[key: number]: ActiveItemMap} // key: FId + private actives: {[key: number]: ActiveItemMap}[] // key: FId private passive: {[key: string]: FId} - public forest: {[key: number]: ApplyOrCoerce[]} // key: FId + public forest: {[key: number]: Rule[]} // key: FId public nextId: number public offset: number @@ -1273,7 +1299,7 @@ class Chart { } public lookupACo(offset: number, fid: FId, label: number): ActiveItem[] | null { - let tmp: ActiveItem + let tmp: ActiveItemMap if (offset == this.offset) tmp = this.active[fid] @@ -1286,14 +1312,14 @@ class Chart { return tmp[label] } - public labelsAC(fid: FId): ActiveItem { + public labelsAC(fid: FId): ActiveItemMap { return this.active[fid] } - public insertAC(fid: FId, label: number, items: any[]): void { - let tmp: ActiveItem = this.active[fid] + public insertAC(fid: FId, label: number, items: ActiveItem[]): void { + let tmp: ActiveItemMap = this.active[fid] if (tmp == null) { - tmp = new Object() + tmp = {} this.active[fid] = tmp } tmp[label] = items @@ -1320,7 +1346,7 @@ class Chart { let rules = [] let forest = this.forest - let go = function (rules0: ApplyOrCoerce[]): void { + let go = function (rules0: Rule[]): void { for (let i in rules0) { let rule = rules0[i] switch (rule.id) { @@ -1397,24 +1423,30 @@ class ActiveItem { * Utilities */ +/* +eslint-disable + @typescript-eslint/no-explicit-any, + @typescript-eslint/no-unused-vars +*/ + /* from Remedial JavaScript by Douglas Crockford, http://javascript.crockford.com/remedial.html */ function isString(a: any): boolean { - return typeof a === 'string' + return typeof a == 'string' } function isArray(a: any): boolean { - return a && typeof a === 'object' && a.constructor === Array + return a && typeof a == 'object' && a.constructor == Array } function isUndefined(a: any): boolean { - return typeof a === 'undefined' + return typeof a == 'undefined' } function isBoolean(a: any): boolean { - return typeof a === 'boolean' + return typeof a == 'boolean' } function isNumber(a: any): boolean { - return typeof a === 'number' && isFinite(a) + return typeof a == 'number' && isFinite(a) } function isFunction(a: any): boolean { - return typeof a === 'function' + return typeof a == 'function' } function dumpObject (obj: any): string { if (isUndefined(obj)) { diff --git a/src/runtime/typescript/tsconfig.json b/src/runtime/typescript/tsconfig.json new file mode 100644 index 000000000..45c1de1e5 --- /dev/null +++ b/src/runtime/typescript/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "noImplicitAny": false + } +}