@temperlang/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/regex.js CHANGED
@@ -1,71 +1,68 @@
1
- export function regexCompiledFound(_, compiled, text) {
1
+ // @ts-check
2
+
3
+ export const regexCompiledFound = (_, compiled, text) => {
4
+ compiled.lastIndex = 0;
2
5
  return compiled.test(text);
3
- }
6
+ };
4
7
 
5
- export function regexCompiledFind(_, compiled, text, regexRefs) {
6
- const match = regexCompiledFindEx(_, compiled, text, regexRefs);
8
+ /**
9
+ * @param {RegExp} compiled
10
+ * @param {string} text
11
+ * @param {number} begin
12
+ */
13
+ export const regexCompiledFind = (_, compiled, text, begin, regexRefs) => {
14
+ const match = regexCompiledFindEx(_, compiled, text, begin, regexRefs);
7
15
  if (match === undefined) {
8
16
  // We could just fail on `undefined.groups`, but that seems accidental.
9
17
  throw Error();
10
18
  }
11
- return match.groups;
12
- }
19
+ return match;
20
+ };
13
21
 
14
22
  /**
15
23
  * @param {RegExp} compiled
16
24
  * @param {string} text
25
+ * @param {number} begin
17
26
  */
18
- function regexCompiledFindEx(_, compiled, text, regexRefs) {
27
+ function regexCompiledFindEx(_, compiled, text, begin, regexRefs) {
28
+ compiled.lastIndex = begin;
19
29
  const match = compiled.exec(text);
20
30
  if (match === null) {
21
31
  return undefined;
22
32
  }
23
- const { groups: groupsMaybe, indices: { groups: indexGroupsMaybe } } = match;
33
+ const {
34
+ // @ts-ignore
35
+ groups: groupsMaybe,
36
+ // @ts-ignore
37
+ indices: { groups: indexGroupsMaybe },
38
+ } = match;
24
39
  const groups = groupsMaybe || {};
25
- const indexGroups = indexGroupsMaybe || [];
40
+ const indexGroups = indexGroupsMaybe || {};
26
41
  // Find the begin indices in code points for all matched groups.
27
- const rawBegins = [];
28
- const defaultFull = !("full" in groups);
29
- if (defaultFull) {
30
- rawBegins.push({ name: "full", index: match.index });
31
- }
32
- for (const entry of Object.entries(indexGroups)) {
33
- const [name, indices] = entry;
34
- if (indices !== undefined) {
35
- rawBegins.push({ name, index: indices[0] });
36
- }
37
- }
38
- const begins = codePointIndices(text, rawBegins);
39
- // Form the matches.
40
- const Group = regexRefs.group.constructor;
42
+ const Match = regexRefs.match.constructor;
43
+ const Group = regexRefs.match.full.constructor;
41
44
  const resultGroups = new Map();
42
- if (defaultFull) {
43
- resultGroups.set("full", new Group("full", match[0], begins.full));
44
- }
45
- for (const entry of Object.entries(groups)) {
46
- const [name, value] = entry;
47
- resultGroups.set(name, new Group(name, value ?? "", begins[name] ?? -1));
48
- }
49
- return { groups: resultGroups, index: match.index, length: match[0].length };
50
- }
51
-
52
- function codePointIndices(text, unitNameIndexArray) {
53
- // Follows logic from CodePointsStringSlice but simpler.
54
- // Sort first for single pass O(m log m) + O(n) rather than O(m * n).
55
- // TODO(tjp, regex): Can we presume we often receive them in sorted order?
56
- unitNameIndexArray.sort((a, b) => a.index - b.index);
57
- let unitCount = 0;
58
- let codeCount = 0;
59
- const codeIndices = {};
60
- for (const unitNameIndex of unitNameIndexArray) {
61
- const { name, index: unitIndex } = unitNameIndex;
62
- while (unitCount < unitIndex) {
63
- unitCount += text.codePointAt(unitCount) > 0xFFFF ? 2 : 1;
64
- codeCount += 1;
45
+ const fullText = match[0];
46
+ const fullBegin = match.index;
47
+ const full = new Group(
48
+ "full",
49
+ fullText,
50
+ fullBegin,
51
+ fullBegin + fullText.length
52
+ );
53
+ for (const name of Object.keys(indexGroups)) {
54
+ const indices = indexGroups[name];
55
+ const text = groups[name];
56
+ if (text === undefined) {
57
+ continue;
65
58
  }
66
- codeIndices[name] = codeCount;
59
+ const groupBegin = indices[0];
60
+ resultGroups.set(
61
+ name,
62
+ new Group(name, text, groupBegin, groupBegin + text.length)
63
+ );
67
64
  }
68
- return codeIndices;
65
+ return new Match(full, resultGroups);
69
66
  }
70
67
 
71
68
  /**
@@ -74,36 +71,56 @@ function codePointIndices(text, unitNameIndexArray) {
74
71
  * @param {(groups: Map<string, any>) => string} format
75
72
  * @returns {string}
76
73
  */
77
- export function regexCompiledReplace(
78
- _,
79
- compiled,
80
- text,
81
- format,
82
- regexRefs
83
- ) {
74
+ export const regexCompiledReplace = (_, compiled, text, format, regexRefs) => {
84
75
  // Simple string replace doesn't provide all match group details if we want to
85
76
  // make our interface consistent, so we have to do this manually here.
86
77
  // The hope is that we can optimize a bunch out when we have compile-time
87
78
  // contant patterns and customized match result types.
88
- let match = regexCompiledFindEx(_, compiled, text, regexRefs);
89
- if (match === undefined) {
90
- // Manually handle no match case for our manual replace logic.
91
- return text;
92
- }
93
- // Index and length in js space should save some processing.
94
- const { groups, index, length } = match;
95
- const content = format(groups);
96
- return text.slice(0, index) + content + text.slice(index + length);
97
- }
79
+ let result = "";
80
+ let begin = 0;
81
+ let keepBegin = begin;
82
+ do {
83
+ let match = regexCompiledFindEx(_, compiled, text, begin, regexRefs);
84
+ if (match === undefined) {
85
+ if (result == "") {
86
+ // Manually handle no match case for our manual replace logic.
87
+ return text;
88
+ } else {
89
+ result += text.substring(keepBegin);
90
+ break;
91
+ }
92
+ }
93
+ result += text.slice(keepBegin, match.full.begin);
94
+ result += format(match);
95
+ keepBegin = match.full.end;
96
+ begin = Math.max(keepBegin, begin + 1);
97
+ } while (begin <= text.length); // `<=` to see string end
98
+ return result;
99
+ };
98
100
 
99
- export function regexCompileFormatted(_, formatted) {
100
- return new RegExp(formatted, "du"); // d:hasIndices, u:unicode
101
- }
101
+ /**
102
+ * @param {RegExp} compiled
103
+ * @param {string} text
104
+ * @returns {Readonly<string[]>}
105
+ */
106
+ export const regexCompiledSplit = (_, compiled, text) => {
107
+ return Object.freeze(text.split(compiled));
108
+ };
102
109
 
103
- export function regexFormatterAdjustCodeSet(self, codeSet, regexRefs) {
110
+ /**
111
+ * @param {RegExp} _
112
+ * @param {string} formatted
113
+ * @returns {RegExp}
114
+ */
115
+ export const regexCompileFormatted = (_, formatted) => {
116
+ return new RegExp(formatted, "dgu"); // d:hasIndices, g:global, u:unicode
117
+ };
118
+
119
+ export const regexFormatterAdjustCodeSet = (self, codeSet, regexRefs) => {
104
120
  if (codeSet.negated) {
105
121
  let maxCode = codeSet.items.reduce(
106
- (maxCode, item) => Math.max(maxCode, self.maxCode(item)) ?? 0, 0
122
+ (maxCode, item) => Math.max(maxCode, self.maxCode(item)) ?? 0,
123
+ 0
107
124
  );
108
125
  if (maxCode < MIN_SUPPLEMENTAL_CP) {
109
126
  // Add a bonus explicit surrogate pair to encourage js code points.
@@ -115,20 +132,18 @@ export function regexFormatterAdjustCodeSet(self, codeSet, regexRefs) {
115
132
  }
116
133
  return new regexRefs.orObject.constructor([
117
134
  codeSetBonus,
118
- new codeSet.constructor(
119
- [codeSetBonus].concat(codeSet.items), true
120
- ),
135
+ new codeSet.constructor([codeSetBonus].concat(codeSet.items), true),
121
136
  ]);
122
137
  }
123
138
  }
124
139
  return codeSet;
125
- }
140
+ };
126
141
 
127
- export function regexFormatterPushCodeTo(_, out, code, insideCodeSet) {
142
+ export const regexFormatterPushCodeTo = (_, out, code, insideCodeSet) => {
128
143
  // Ignore insideCodeSet for now.
129
144
  // TODO(tjp, regex): Get fancier, including with work in Temper.
130
145
  out.push(`\\u{${code.toString(16)}}`);
131
- }
146
+ };
132
147
 
133
148
  // Cached later for some approximate efficiency.
134
149
  let codeSetBonus = null;
package/string.js ADDED
@@ -0,0 +1,198 @@
1
+ import { requireIsSafeInteger } from "./check-type.js";
2
+ import {bubble} from "./core.js";
3
+
4
+ /**
5
+ * Implements extension method String::fromCodePoints
6
+ * @param {number[]} codePoints
7
+ * @returns {string}
8
+ */
9
+ export const stringFromCodePoints = (codePoints) => {
10
+ // TODO Append in batches if codePoints is long?
11
+ return String.fromCodePoint(...codePoints);
12
+ };
13
+
14
+ /**
15
+ * Implements extension method String::isEmpty
16
+ * @param {string} s
17
+ * @returns {boolean}
18
+ */
19
+ export const stringIsEmpty = (s) => {
20
+ return s === "";
21
+ };
22
+
23
+ /**
24
+ * Implements extension method String::split
25
+ * @param {string} s
26
+ * @param {string | undefined} separator
27
+ * @returns {string[]}
28
+ */
29
+ export const stringSplit = (s, separator) => {
30
+ return separator ? s.split(separator).map((s) => s) : Array.from(s);
31
+ };
32
+
33
+ /**
34
+ * Implements extension method String::toFloat64
35
+ * @param {string} s
36
+ * @returns {number}
37
+ */
38
+ export const stringToFloat64 = (s) => {
39
+ // TODO Consider JSON.parse + bonus constants instead? Faster or not?
40
+ if (!/^\s*-?(?:\d+(?:\.\d+)?(?:[eE][-+]?\d+)?|NaN|Infinity)\s*$/.test(s)) {
41
+ bubble();
42
+ }
43
+ return Number(s);
44
+ };
45
+
46
+ /**
47
+ * Implements extension method String::toInt
48
+ * @param {string} s
49
+ * @param {number?} radix
50
+ * @returns {number}
51
+ */
52
+ export const stringToInt = (s, radix) => {
53
+ // This currently maybe allocates for trim and then also for check.
54
+ // TODO Avoid that with manual char checks? Arbitrary base makes regex harder.
55
+ s = s.trim();
56
+ radix = radix ?? 10;
57
+ const result = parseInt(s, radix);
58
+ requireIsSafeInteger(result);
59
+ const trimmed = s.slice(0, s.length - 1);
60
+ if (parseInt(trimmed, radix) === result) {
61
+ bubble();
62
+ }
63
+ return result;
64
+ };
65
+
66
+ /**
67
+ * @param {string} s
68
+ * @param {number} begin
69
+ * @param {number} end
70
+ * @returns {number}
71
+ */
72
+ export const stringCountBetween = (s, begin, end) => {
73
+ let count = 0;
74
+ for (let i = begin; i < end; ++i) {
75
+ let cp = s.codePointAt(i);
76
+ if (cp !== undefined) {
77
+ count += 1;
78
+ i += !!(cp >>> 16); // skip over trailing surrogate
79
+ }
80
+ }
81
+ return count;
82
+ };
83
+
84
+ /**
85
+ * @param {string} s
86
+ * @param {number} i
87
+ * @returns {number}
88
+ */
89
+ export const stringGet = (s, i) => {
90
+ const c = s.codePointAt(i);
91
+ if (c === undefined) {
92
+ throw new Error();
93
+ }
94
+ return c;
95
+ };
96
+
97
+ /**
98
+ * @param {string} s
99
+ * @param {number} begin
100
+ * @param {number} end
101
+ * @param {number} minCount
102
+ * @returns {boolean}
103
+ */
104
+ export const stringHasAtLeast = (s, begin, end, minCount) => {
105
+ let { length } = s;
106
+ begin = Math.min(begin, length);
107
+ end = Math.max(begin, Math.min(end, length));
108
+ let nUtf16 = end - begin;
109
+ if (nUtf16 < minCount) { return false; }
110
+ if (nUtf16 >= minCount * 2) { return true; }
111
+ // Fall back to an early-outing version of countBetween.
112
+ let count = 0;
113
+ for (let i = begin; i < end; ++i) {
114
+ let cp = s.codePointAt(i);
115
+ if (cp !== undefined) {
116
+ count += 1;
117
+ i += !!(cp >>> 16); // skip over trailing surrogate
118
+ if (count >= minCount) { return true; }
119
+ }
120
+ }
121
+ return count >= minCount;
122
+ };
123
+
124
+ /**
125
+ * @param {string} s
126
+ * @param {number} i
127
+ * @returns {number}
128
+ */
129
+ export const stringNext = (s, i) => {
130
+ let iNext = Math.min(s.length, i);
131
+ let cp = s.codePointAt(i);
132
+ if (cp !== undefined) {
133
+ iNext += 1 + !!(cp >>> 16);
134
+ }
135
+ return iNext;
136
+ };
137
+
138
+ /**
139
+ * @param {string} s
140
+ * @param {number} i
141
+ * @returns {number}
142
+ */
143
+ export const stringPrev = (s, i) => {
144
+ let iPrev = Math.min(s.length, i);
145
+ if (iPrev) {
146
+ iPrev -= 1;
147
+ if (iPrev && s.codePointAt(iPrev - 1) >>> 16) {
148
+ iPrev -= 1;
149
+ }
150
+ }
151
+ return iPrev;
152
+ };
153
+
154
+ /**
155
+ * @param {string} s
156
+ * @param {(number) => void} f
157
+ */
158
+ export const stringForEach = (s, f) => {
159
+ let { length } = s;
160
+ for (let i = 0; i < length; ++i) {
161
+ let cp = s.codePointAt(i);
162
+ f(cp);
163
+ if (cp >>> 16) {
164
+ ++i; // Skip both surrogates
165
+ }
166
+ }
167
+ };
168
+
169
+ export const stringIndexNone = -1;
170
+
171
+ /**
172
+ * Casts a *StringIndexOption* to a *StringIndex*
173
+ *
174
+ * @param {number} i a string index or no string index
175
+ * @returns {number} i if it's a valid StringIndex.
176
+ */
177
+ export const requireStringIndex = (i) => {
178
+ if (i >= 0) { return i; }
179
+ throw new TypeError(`Expected StringIndex, not ${i}`);
180
+ };
181
+
182
+ /**
183
+ * Casts a *StringIndexOption* to a *NoStringIndex*.
184
+ * @param {number} i a string index or no string index
185
+ * @returns {number} i if it's valid NoStringIndex.
186
+ */
187
+ export const requireNoStringIndex = (i) => {
188
+ if (i < 0) { return i; }
189
+ throw new TypeError(`Expected NoStringIndex, not ${i}`);
190
+ };
191
+
192
+ /**
193
+ * @param {string[]} s
194
+ * @param {number} c
195
+ */
196
+ export const stringBuilderAppendCodePoint = (s, c) => {
197
+ s[0] += String.fromCodePoint(c);
198
+ }
@@ -1,121 +0,0 @@
1
- /**
2
- * @fileoverview
3
- * Declares *InterfaceType* which allows exposing a Temper `interface` type
4
- * as a type that user types can extend and which inter-operates with
5
- * `instanceof`.
6
- *
7
- * User code may do
8
- *
9
- * MyClass.implementedBy(AnInterface);
10
- *
11
- * to declare that `class MyClass implements AnInterface { ... }`.
12
- *
13
- * After that, members defined on *AnInterface* will be available via
14
- * *MyClass*'s prototype and `new MyClass(...) instanceof AnInterface` will
15
- * be true.
16
- */
17
-
18
- export class InterfaceType {
19
- constructor(name, members, supers, inheritanceDepth) {
20
- /** Diagnostic string. The name of the interface. */
21
- this.name = name;
22
- /**
23
- * A list of [kind, propertyKey, value] triples that should be added to
24
- * classes that implement this modulo overriding.
25
- * Kind has type ("m" | "g" | "s") where "m" indicates a normal method
26
- * whose name is key, "g" a getter for the property named key,
27
- * and "s" a setter.
28
- */
29
- this.members = [...members];
30
- /** interface types that are super-types of this */
31
- this.supers = [...supers];
32
- /**
33
- * Computed by Temper compiler so that sub-type members can clobber
34
- * super-type members but not otherwise.
35
- */
36
- this.inheritanceDepth = inheritanceDepth;
37
- /** Presence `in` a value indicates sub-type. */
38
- this.interfaceTag = Symbol(this.toString());
39
- }
40
-
41
- toString() { return `interface ${this.name}`; }
42
-
43
- /** Invoked when user code does `value instanceof anInterfaceType`. */
44
- [Symbol.hasInstance](x) {
45
- return x && typeof x === 'object' && this.interfaceTag in x;
46
- }
47
-
48
- /** Called to declare a class implements this InterfaceType. */
49
- implementedBy(classType) {
50
- const memberMaps = InterfaceType.#memberMapsFor(classType);
51
- classType.prototype[this.interfaceTag] = void 0;
52
- const inheritanceDepth = this.inheritanceDepth;
53
- for (const [kind, key, value] of this.members) {
54
- const memberMap = memberMaps[kind];
55
- const depth = memberMap.get(key);
56
- if (typeof depth === 'undefined' || inheritanceDepth > depth) {
57
- memberMap.set(key, inheritanceDepth);
58
- const proto = classType.prototype;
59
- const descriptor = Object.getOwnPropertyDescriptor(proto, key)
60
- || { configurable: true };
61
- switch (kind) {
62
- case 'm':
63
- descriptor.value = value;
64
- break;
65
- case 'g':
66
- descriptor.get = value;
67
- break;
68
- case 's':
69
- descriptor.set = value;
70
- break;
71
- }
72
- Object.defineProperty(proto, key, descriptor);
73
- }
74
- }
75
- for (const superType of this.supers) {
76
- superType.implementedBy(classType);
77
- }
78
- }
79
-
80
- static #memberMapsKey = Symbol('memberMaps');
81
-
82
- static #memberMapsFor(classType) {
83
- const memberMapsKey = InterfaceType.#memberMapsKey;
84
- // Do not reuse super-class's member map
85
- let maps = Object.hasOwnProperty.call(classType, memberMapsKey)
86
- ? classType[memberMapsKey] : null;
87
- if (!maps) {
88
- maps = { m: new Map(), g: new Map(), s: new Map() };
89
- classType[memberMapsKey] = maps;
90
- // Walk prototypes. Even though Temper does not allow class
91
- // inheritance, this code may be used by JavaScript types that
92
- // do, so flatten class members so that interfaces don't clobber
93
- // class members.
94
- // Except we treat interfaces as sub-types of Object, so stop
95
- // before Object.prototype. This allows an interface to override
96
- // Object members like toString, valueOf, etc.
97
- let prototype = classType.prototype;
98
- while (prototype && prototype !== Object.prototype) {
99
- for (const keys of [
100
- Object.getOwnPropertyNames(prototype),
101
- Object.getOwnPropertySymbols(prototype),
102
- ]) {
103
- for (const key of keys) {
104
- const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
105
- if ('value' in descriptor) {
106
- maps.m.set(key, Number.MAX_SAFE_INTEGER);
107
- }
108
- if (descriptor.get) {
109
- maps.g.set(key, Number.MAX_SAFE_INTEGER);
110
- }
111
- if (descriptor.set) {
112
- maps.s.set(key, Number.MAX_SAFE_INTEGER);
113
- }
114
- }
115
- }
116
- prototype = Object.getPrototypeOf(prototype);
117
- }
118
- }
119
- return maps;
120
- }
121
- }