Avoid modifying String prototype in TypeScript runtime

Adds new class TaggedString
This commit is contained in:
John J. Camilleri
2019-06-10 11:39:31 +02:00
parent a229507392
commit ab94e93b94
2 changed files with 37 additions and 48 deletions

View File

@@ -6,11 +6,6 @@
* A port of the pure JavaScript runtime (/src/runtime/javascript/gflib.js) into TypeScript * A port of the pure JavaScript runtime (/src/runtime/javascript/gflib.js) into TypeScript
*/ */
// We use wrapper type String (instead of primitive string) as we
// extend its prototype with tagging information.
// This linting rule doesn't allow use of String, thus must be disabled:
/* eslint-disable @typescript-eslint/ban-types */
/** /**
* A GF grammar is one abstract and multiple concretes * A GF grammar is one abstract and multiple concretes
*/ */
@@ -27,8 +22,8 @@ class GFGrammar { // eslint-disable-line @typescript-eslint/no-unused-vars
input: string, input: string,
fromLang: string, fromLang: string,
toLang: string toLang: string
): {[key: string]: {[key: string]: String}[]} { ): {[key: string]: {[key: string]: string}[]} {
let outputs: {[key: string]: {[key: string]: String}[]} = {} let outputs: {[key: string]: {[key: string]: string}[]} = {}
let fromConcs = this.concretes let fromConcs = this.concretes
if (fromLang) { if (fromLang) {
fromConcs = {} fromConcs = {}
@@ -420,15 +415,15 @@ class GFConcrete {
return res return res
} }
private syms2toks(syms: Sym[]): String[] { private syms2toks(syms: Sym[]): TaggedString[] {
let ts: String[] = [] let ts: TaggedString[] = []
for (let i = 0; i < syms.length; i++) { for (let i = 0; i < syms.length; i++) {
let sym0 = syms[i] let sym0 = syms[i]
switch (sym0.id) { switch (sym0.id) {
case 'KS': { case 'KS': {
let sym = sym0 as SymKS let sym = sym0 as SymKS
for (let j in sym.tokens) { for (let j in sym.tokens) {
ts.push(sym.tokens[j].tagWith(sym.tag as string)) ts.push(new TaggedString(sym.tokens[j], sym.tag as string))
} }
break break
} }
@@ -444,7 +439,7 @@ class GFConcrete {
if (alt.prefixes.some((p: string): boolean => nextToken.startsWith(p))) { if (alt.prefixes.some((p: string): boolean => nextToken.startsWith(p))) {
alt.tokens.forEach((symks: SymKS): void => { alt.tokens.forEach((symks: SymKS): void => {
symks.tokens.forEach((t: string): void => { symks.tokens.forEach((t: string): void => {
ts.push(t.tagWith(sym.tag as string)) ts.push(new TaggedString(t, sym.tag as string))
}) })
}) })
addedAlt = true addedAlt = true
@@ -457,7 +452,7 @@ class GFConcrete {
// Fall through here when no alts (or none apply) // Fall through here when no alts (or none apply)
sym.tokens.forEach((symks: SymKS): void => { sym.tokens.forEach((symks: SymKS): void => {
symks.tokens.forEach((t: string): void => { symks.tokens.forEach((t: string): void => {
ts.push(t.tagWith(sym.tag as string)) ts.push(new TaggedString(t, sym.tag as string))
}) })
}) })
break break
@@ -467,23 +462,23 @@ class GFConcrete {
return ts return ts
} }
public linearizeAll(tree: Fun): String[] { public linearizeAll(tree: Fun): string[] {
return this.linearizeSyms(tree,'0').map((r): String => { return this.linearizeSyms(tree,'0').map((r): string => {
return this.unlex(this.syms2toks(r.table[0])) return this.unlex(this.syms2toks(r.table[0]))
}) })
} }
public linearize(tree: Fun): String { public linearize(tree: Fun): string {
let res = this.linearizeSyms(tree,'0') let res = this.linearizeSyms(tree,'0')
return this.unlex(this.syms2toks(res[0].table[0])) return this.unlex(this.syms2toks(res[0].table[0]))
} }
public tagAndLinearize(tree: Fun): String[] { public tagAndLinearize(tree: Fun): TaggedString[] {
let res = this.linearizeSyms(tree,'0') let res = this.linearizeSyms(tree,'0')
return this.syms2toks(res[0].table[0]) return this.syms2toks(res[0].table[0])
} }
private unlex(ts: String[]): String { private unlex(ts: TaggedString[]): string {
if (ts.length == 0) { if (ts.length == 0) {
return '' return ''
} }
@@ -493,8 +488,8 @@ class GFConcrete {
let s = '' let s = ''
for (let i = 0; i < ts.length; i++) { for (let i = 0; i < ts.length; i++) {
let t: String = ts[i] let t: string = ts[i].s
let after: String | null = i < ts.length-1 ? ts[i+1] : null let after: string | null = i < ts.length-1 ? ts[i+1].s : null
s += t s += t
if (after != null if (after != null
&& !t.match(noSpaceAfter) && !t.match(noSpaceAfter)
@@ -654,25 +649,17 @@ class GFConcrete {
} }
/** /**
* A type which can be tagged * A string with a tag
* This avoids modifying the String prototype, which was messy
*/ */
interface Taggable { class TaggedString {
tag?: string; public s: string
tagWith: (tag: string) => Taggable; public tag: string
}
/** public constructor(s: string, tag: string) {
* Strings can also be tagged in the same way this.s = s
*/ this.tag = tag
interface String {
tag?: string;
tagWith: (tag: string) => String;
} }
String.prototype.tagWith = function (tag: string): String {
// returns a copy
let s2 = this
s2.tag = tag
return s2
} }
/** /**
@@ -828,7 +815,7 @@ class SymCat {
/** /**
* SymKS: Object to represent terminals in grammar rules * SymKS: Object to represent terminals in grammar rules
*/ */
class SymKS implements Taggable { class SymKS {
public id: string public id: string
public tokens: string[] public tokens: string[]
public tag?: string public tag?: string
@@ -855,7 +842,7 @@ class SymKS implements Taggable {
/** /**
* SymKP: Object to represent pre in grammar rules * SymKP: Object to represent pre in grammar rules
*/ */
class SymKP implements Taggable { class SymKP {
public id: string public id: string
public tokens: SymKS[] public tokens: SymKS[]
public alts: Alt[] public alts: Alt[]

View File

@@ -351,7 +351,7 @@ var GFConcrete = (function () {
case 'KS': { case 'KS': {
var sym = sym0; var sym = sym0;
for (var j in sym.tokens) { for (var j in sym.tokens) {
ts.push(sym.tokens[j].tagWith(sym.tag)); ts.push(new TaggedString(sym.tokens[j], sym.tag));
} }
break; break;
} }
@@ -366,7 +366,7 @@ var GFConcrete = (function () {
if (alt.prefixes.some(function (p) { return nextToken_1.startsWith(p); })) { if (alt.prefixes.some(function (p) { return nextToken_1.startsWith(p); })) {
alt.tokens.forEach(function (symks) { alt.tokens.forEach(function (symks) {
symks.tokens.forEach(function (t) { symks.tokens.forEach(function (t) {
ts.push(t.tagWith(sym_1.tag)); ts.push(new TaggedString(t, sym_1.tag));
}); });
}); });
addedAlt_1 = true; addedAlt_1 = true;
@@ -379,7 +379,7 @@ var GFConcrete = (function () {
break; break;
sym_1.tokens.forEach(function (symks) { sym_1.tokens.forEach(function (symks) {
symks.tokens.forEach(function (t) { symks.tokens.forEach(function (t) {
ts.push(t.tagWith(sym_1.tag)); ts.push(new TaggedString(t, sym_1.tag));
}); });
}); });
break; break;
@@ -413,8 +413,8 @@ var GFConcrete = (function () {
var noSpaceBefore = /^[\.\,\?\!\)\:\;\-\]]/; var noSpaceBefore = /^[\.\,\?\!\)\:\;\-\]]/;
var s = ''; var s = '';
for (var i = 0; i < ts.length; i++) { for (var i = 0; i < ts.length; i++) {
var t = ts[i]; var t = ts[i].s;
var after = i < ts.length - 1 ? ts[i + 1] : null; var after = i < ts.length - 1 ? ts[i + 1].s : null;
s += t; s += t;
if (after != null if (after != null
&& !t.match(noSpaceAfter) && !t.match(noSpaceAfter)
@@ -520,11 +520,13 @@ var GFConcrete = (function () {
}; };
return GFConcrete; return GFConcrete;
}()); }());
String.prototype.tagWith = function (tag) { var TaggedString = (function () {
var s2 = this; function TaggedString(s, tag) {
s2.tag = tag; this.s = s;
return s2; this.tag = tag;
}; }
return TaggedString;
}());
var Apply = (function () { var Apply = (function () {
function Apply(fun, args) { function Apply(fun, args) {
this.id = 'Apply'; this.id = 'Apply';