@figtreejs/core 0.0.1-alpha.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/eslint.config.js +9 -0
  3. package/package.json +76 -0
  4. package/src/@custom-types/normalize-svg-path.d.ts +13 -0
  5. package/src/@custom-types/parse-svg-path.d.ts +8 -0
  6. package/src/@custom-types/svg-path-types.d.ts +37 -0
  7. package/src/bauble-makers/makers.ts +112 -0
  8. package/src/bauble-makers/set-up-baubles.ts +197 -0
  9. package/src/bauble-makers/utils.ts +61 -0
  10. package/src/components/baubles/bauble.tsx +61 -0
  11. package/src/components/baubles/branches.tsx +13 -0
  12. package/src/components/baubles/clades/cartoon.tsx +68 -0
  13. package/src/components/baubles/clades/highlight.tsx +96 -0
  14. package/src/components/baubles/clades/index.ts +1 -0
  15. package/src/components/baubles/clades.tsx +45 -0
  16. package/src/components/baubles/helpers.tsx +62 -0
  17. package/src/components/baubles/index.ts +16 -0
  18. package/src/components/baubles/labels.tsx +38 -0
  19. package/src/components/baubles/nodes.tsx +51 -0
  20. package/src/components/baubles/shapes/branch.tsx +53 -0
  21. package/src/components/baubles/shapes/circle.tsx +64 -0
  22. package/src/components/baubles/shapes/index.ts +9 -0
  23. package/src/components/baubles/shapes/label.tsx +104 -0
  24. package/src/components/baubles/shapes/rectangle.tsx +83 -0
  25. package/src/components/baubles/types.ts +99 -0
  26. package/src/components/decorations/axis/axis-types.ts +123 -0
  27. package/src/components/decorations/axis/axis.tsx +21 -0
  28. package/src/components/decorations/axis/index.ts +2 -0
  29. package/src/components/decorations/axis/polar-axis-bars.tsx +102 -0
  30. package/src/components/decorations/axis/polar-axis.tsx +175 -0
  31. package/src/components/decorations/axis/rectangular-axis-bars.tsx +53 -0
  32. package/src/components/decorations/axis/rectangular-axis.tsx +151 -0
  33. package/src/components/decorations/index.ts +2 -0
  34. package/src/components/decorations/legend/discrete-legend.tsx +93 -0
  35. package/src/components/decorations/legend/index.ts +1 -0
  36. package/src/components/decorations/legend/legend.tsx +1 -0
  37. package/src/components/figtree/figtree-types.ts +69 -0
  38. package/src/components/figtree/figtree.tsx +136 -0
  39. package/src/components/figtree/index.ts +3 -0
  40. package/src/components/hoc/index.ts +7 -0
  41. package/src/components/hoc/with-branch.tsx +148 -0
  42. package/src/components/hoc/with-branches.tsx +54 -0
  43. package/src/components/hoc/with-clades.tsx +47 -0
  44. package/src/components/hoc/with-node.tsx +183 -0
  45. package/src/components/hoc/with-nodes.tsx +45 -0
  46. package/src/components/index.ts +4 -0
  47. package/src/context/aminated-context.ts +3 -0
  48. package/src/context/dimension-context.ts +22 -0
  49. package/src/context/layout-context.ts +20 -0
  50. package/src/context/scale-context.ts +12 -0
  51. package/src/evo/index.ts +1 -0
  52. package/src/evo/tree/index.ts +5 -0
  53. package/src/evo/tree/mcc-tree.ts +0 -0
  54. package/src/evo/tree/normalized-tree/immutable-tree-helpers.ts +136 -0
  55. package/src/evo/tree/normalized-tree/immutable-tree.test.ts +158 -0
  56. package/src/evo/tree/normalized-tree/immutable-tree.ts +1365 -0
  57. package/src/evo/tree/normalized-tree/index.ts +3 -0
  58. package/src/evo/tree/parsers/annotation-parser.ts +276 -0
  59. package/src/evo/tree/parsers/index.ts +3 -0
  60. package/src/evo/tree/parsers/newick-character-parser.ts +246 -0
  61. package/src/evo/tree/parsers/newick-parsing.ts +22 -0
  62. package/src/evo/tree/parsers/nexus-parser.ts +12 -0
  63. package/src/evo/tree/parsers/nexus-parsing.ts +68 -0
  64. package/src/evo/tree/parsers/parsing.test.ts +289 -0
  65. package/src/evo/tree/parsers/stream-reader/index.ts +1 -0
  66. package/src/evo/tree/parsers/stream-reader/newick-importer.txt +395 -0
  67. package/src/evo/tree/parsers/stream-reader/nexus-importer.test.ts +99 -0
  68. package/src/evo/tree/parsers/stream-reader/nexus-importer.ts +293 -0
  69. package/src/evo/tree/parsers/stream-reader/nexus-tokenizer.ts +77 -0
  70. package/src/evo/tree/parsers/stream-reader/nexus-transform-stream.txt +109 -0
  71. package/src/evo/tree/taxa/helper-functions.ts +46 -0
  72. package/src/evo/tree/taxa/index.ts +1 -0
  73. package/src/evo/tree/taxa/taxon.ts +116 -0
  74. package/src/evo/tree/traversals/index.ts +1 -0
  75. package/src/evo/tree/traversals/preorder-traversal.ts +89 -0
  76. package/src/evo/tree/traversals/traversal-types.ts +6 -0
  77. package/src/evo/tree/tree-types.ts +197 -0
  78. package/src/evo/tree/utilities.ts +44 -0
  79. package/src/index.ts +6 -0
  80. package/src/layouts/functional/index.ts +2 -0
  81. package/src/layouts/functional/radial-layout.ts +150 -0
  82. package/src/layouts/functional/rectangular-layout.ts +71 -0
  83. package/src/layouts/index.ts +3 -0
  84. package/src/layouts/layout-interface.ts +90 -0
  85. package/src/layouts/types.ts +32 -0
  86. package/src/path.helpers.ts +81 -0
  87. package/src/store/polar-scale.ts +145 -0
  88. package/src/store/store.ts +144 -0
  89. package/src/tests/baubles/__snapshots__/branch-labels.test.tsx.snap +901 -0
  90. package/src/tests/baubles/__snapshots__/node-labels.test.tsx.snap +1516 -0
  91. package/src/tests/baubles/branch-labels.test.tsx +103 -0
  92. package/src/tests/baubles/label.svg +131 -0
  93. package/src/tests/baubles/node-labels.test.tsx +126 -0
  94. package/src/tests/clades/__snapshots__/cartoon.test.tsx.snap +327 -0
  95. package/src/tests/clades/__snapshots__/highlight.test.tsx.snap +337 -0
  96. package/src/tests/clades/cartoon.test.tsx +65 -0
  97. package/src/tests/clades/highlight.test.tsx +66 -0
  98. package/src/tests/figtree/__snapshots__/figtree.test.tsx.snap +761 -0
  99. package/src/tests/figtree/figtree.test.tsx +123 -0
  100. package/src/tests/figtree/simple.svg +47 -0
  101. package/src/tests/layouts/radiallayout.test.ts +23 -0
  102. package/src/tests/layouts/rectangularlayout.test.ts +65 -0
  103. package/src/tests/shapes/branch.test.tsx +40 -0
  104. package/src/tests/shapes/circle.test.tsx +47 -0
  105. package/src/tests/shapes/label.test.tsx +101 -0
  106. package/src/tests/shapes/rectangle.test.tsx +67 -0
  107. package/src/tests/shapes/types.ts +1 -0
  108. package/src/utils.ts +57 -0
  109. package/tsconfig.json +12 -0
  110. package/vite.config.ts +34 -0
  111. package/vitetest.config.ts +11 -0
@@ -0,0 +1,99 @@
1
+ import { ImmutableTree } from "../../normalized-tree";
2
+ import { NexusImporter } from "./nexus-importer";
3
+ import { describe, it, expect } from "vitest";
4
+ function stringToReadableStream(str: string) {
5
+ return new ReadableStream({
6
+ start(controller: {
7
+ enqueue: (arg0: Uint8Array<ArrayBuffer>) => void;
8
+ close: () => void;
9
+ }) {
10
+ // Convert the string to a Uint8Array and enqueue it
11
+ const encoder = new TextEncoder();
12
+ const chunk = encoder.encode(str);
13
+ controller.enqueue(chunk);
14
+ controller.close();
15
+ },
16
+ });
17
+ }
18
+
19
+ describe("Testing nexus importer", () => {
20
+ it("parse Nexus", async function () {
21
+ const nexusString = `#NEXUS
22
+ Begin trees;
23
+ tree tree1 = (((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2;
24
+ End;`;
25
+
26
+ const nexusStream = stringToReadableStream(nexusString);
27
+
28
+ const trees = new NexusImporter(nexusStream).getTrees();
29
+ let i = 0;
30
+ for await (const tree of trees) {
31
+ expect(tree).toBeInstanceOf(ImmutableTree);
32
+ expect(tree.getExternalNodeCount()).toBe(5);
33
+ expect(tree.getInternalNodeCount()).toBe(4);
34
+ i++;
35
+ }
36
+ expect(i).toBe(1);
37
+ });
38
+ it("parse Nexus multiple trees", async function () {
39
+ const nexusString = `#NEXUS
40
+ Begin trees;
41
+ tree tree1 = (((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2;
42
+ tree tree2 = (((Tip7:0.04873764378778993,(Tip8:0.6532000878000133,Tip6:0.1321456687110038):0.042190933211748174):0.6632395745455513,Tip5:0.07721466870767527):0.023945361546555622,Tip0:0.013537352040642556,(Tip3:0.13083726861861736,(((Tip9:0.048159442284509385,Tip4:0.0727658295569952):0.03481552263500266,Tip2:0.0821610870291415):0.13059030569905986,Tip1:0.03707788908419223):0.04920278810640517):0.0011230070183312782);
43
+ End;`;
44
+
45
+ const nexusStream = stringToReadableStream(nexusString);
46
+
47
+ const trees = new NexusImporter(nexusStream).getTrees();
48
+ let i = 0;
49
+ let lastTree;
50
+ for await (const tree of trees) {
51
+ expect(tree).toBeInstanceOf(ImmutableTree);
52
+ lastTree = tree;
53
+ i++;
54
+ }
55
+ expect(i).toBe(2);
56
+ expect(lastTree?.getTaxonCount()).toBe(15); //has taxa available from all trees
57
+ });
58
+ it("parse Nexus - treeannotations", async function () {
59
+ const nexusString = `#NEXUS
60
+ Begin trees;
61
+ tree tree1 = (((virus1[&test="A",height=90]:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2;
62
+ End;`;
63
+
64
+ const nexusStream = stringToReadableStream(nexusString);
65
+
66
+ const trees = new NexusImporter(nexusStream).getTrees();
67
+ let i = 0;
68
+ for await (const tree of trees) {
69
+ expect(tree).toBeInstanceOf(ImmutableTree);
70
+ i++;
71
+ }
72
+ expect(i).toBe(1);
73
+ });
74
+ it("parse Nexus multiple trees, taxa", async function () {
75
+ const nexusString = `#NEXUS
76
+ Begin taxa;
77
+ Dimensions ntax=5;
78
+ TaxLabels Tip0 Tip1 Tip2 Tip3 Tip4;
79
+ End;
80
+ Begin trees;
81
+ tree 1 = ((Tip3:0.005683055909367637,(Tip4:0.028897909268250865,Tip2:0.17725578701486128):0.15953222892967397):0.0015945469810341964,Tip0:0.16482157194489672,Tip1:0.06558413486573018);
82
+ tree 2 = (Tip3:0.07895643336574805,Tip0:0.09039621608818542,(Tip2:0.12447554684923097,(Tip4:0.016569217859539333,Tip1:0.04529906914098861):0.4270260158852053):0.5512862407921115);
83
+ tree 3 = ((Tip4:0.15573112107230094,Tip2:0.10434839467828654):0.10910110483363722,Tip0:0.009239519001625887,(Tip3:0.4614463596422172,Tip1:0.087294319045952):0.22603324870135144);
84
+ End;`;
85
+
86
+ const nexusStream = stringToReadableStream(nexusString);
87
+
88
+ const trees = new NexusImporter(nexusStream).getTrees();
89
+ let i = 0;
90
+ let lastTree;
91
+ for await (const tree of trees) {
92
+ expect(tree).toBeInstanceOf(ImmutableTree);
93
+ lastTree = tree;
94
+ i++;
95
+ }
96
+ expect(i).toBe(3);
97
+ expect(lastTree?.getTaxonCount()).toBe(5); //has taxa available from all trees
98
+ });
99
+ });
@@ -0,0 +1,293 @@
1
+ import { notNull } from "../../../../utils";
2
+ import type { ImmutableTree } from "../../normalized-tree";
3
+ import { TaxonSet } from "../../taxa/taxon";
4
+
5
+ import { NewickCharacterParser } from "../newick-character-parser";
6
+ import { newickDeliminators, nexusTokenizer } from "./nexus-tokenizer";
7
+
8
+ export class NexusImporter {
9
+ reader: ReadableStreamDefaultReader<string>;
10
+ taxonSet: TaxonSet;
11
+ currentBlock: string | undefined;
12
+ hasTree: boolean | undefined;
13
+ options: { labelName?: string };
14
+ translateTaxonMap: Map<string, string> | undefined;
15
+ constructor(
16
+ stream: ReadableStream<BufferSource>,
17
+ options: { labelName?: string } = {},
18
+ ) {
19
+ const tokenizer = new TransformStream(nexusTokenizer());
20
+
21
+ this.reader = stream
22
+ .pipeThrough(new TextDecoderStream())
23
+ .pipeThrough(tokenizer)
24
+ .getReader();
25
+
26
+ this.taxonSet = new TaxonSet();
27
+ this.currentBlock = undefined;
28
+ this.options = options;
29
+ }
30
+
31
+ async *getTrees(): AsyncIterableIterator<ImmutableTree> {
32
+ while (this.currentBlock !== "trees") {
33
+ await this.parseNextBlock();
34
+ this.hasTree = true;
35
+ }
36
+ yield* this.parseTreesBlock();
37
+ }
38
+
39
+ private async parseNextBlock() {
40
+ const blockName = await this.getNextBlockName();
41
+ switch (blockName) {
42
+ case "taxa":
43
+ this.currentBlock = "taxa";
44
+ await this.parseTaxaBlock();
45
+ break;
46
+ case "trees":
47
+ this.currentBlock = "trees";
48
+ break;
49
+ default:
50
+ console.log(
51
+ `skipping block ${blockName}. Only parsing blocks are taxa and trees for now.`,
52
+ );
53
+ await this.readToEndOfBlock();
54
+ }
55
+ }
56
+
57
+ private async nextToken() {
58
+ const { done, value } = await this.reader.read();
59
+ if (done) {
60
+ throw new Error("unexpectedly hit the end of the stream");
61
+ }
62
+ return value;
63
+ }
64
+ //make skip and then a list of regex to skip
65
+ private async skipSemiColon() {
66
+ const { done, value } = await this.reader.read();
67
+ if (done) {
68
+ throw new Error("unexpectedly hit the end of the stream");
69
+ }
70
+ if (!value.match(/;$/)) {
71
+ throw new Error(`expected ";" got ${value}`);
72
+ }
73
+ }
74
+
75
+ private async getNextBlockName() {
76
+ let keepGoing = true;
77
+ let token;
78
+ while (keepGoing) {
79
+ const value = await this.nextToken();
80
+ if (value.match(/\bbegin/i)) {
81
+ token = await this.nextToken();
82
+ await this.skipSemiColon();
83
+ keepGoing = false;
84
+ }
85
+ }
86
+ return token;
87
+ }
88
+
89
+ private async readToEndOfBlock() {
90
+ let keepGoing = true;
91
+ while (keepGoing) {
92
+ const value = await this.nextToken();
93
+ if (value.match(/\bend;/i)) {
94
+ keepGoing = false;
95
+ }
96
+ }
97
+ }
98
+
99
+ // private async getNextCommand(command: RegExp) {
100
+ // let value;
101
+ // let keepGoing = true
102
+ // while (keepGoing) {
103
+ // value = await this.nextToken()
104
+ // if (value === ";") {
105
+ // throw `Hit ; looking for ${command}`
106
+ // }
107
+ // if (command.test(value)) {
108
+ // keepGoing=false
109
+ // }
110
+ // }
111
+ // return value
112
+ // }
113
+ // skip until match and return match
114
+ private async skipUntil(stopper: RegExp): Promise<string> {
115
+ let value;
116
+ let keepGoing = true;
117
+ while (keepGoing) {
118
+ value = await this.nextToken();
119
+ if (stopper.test(value)) {
120
+ keepGoing = false;
121
+ }
122
+ }
123
+ if (value == undefined)
124
+ throw new Error(`Internal parsing error: ${stopper.source} not found `);
125
+ return value;
126
+ }
127
+ // read up to match return everything up to including the match
128
+ private async readUntil(stopper: RegExp) {
129
+ let buffer = "";
130
+ let keepGoing = true;
131
+ while (keepGoing) {
132
+ const value = await this.nextToken();
133
+ if (stopper.test(value)) {
134
+ buffer += value;
135
+ keepGoing = false;
136
+ }
137
+ buffer += value;
138
+ }
139
+ return buffer;
140
+ }
141
+ private async parseTaxaBlock() {
142
+ let ntax;
143
+ let keepGoing = true;
144
+ while (keepGoing) {
145
+ const command = await this.skipUntil(/dimensions|taxlabels|end/i);
146
+ switch (true) {
147
+ case /dimensions/i.test(command): {
148
+ const taxaLine = await this.readUntil(/;/);
149
+ const ntaxa = taxaLine.match(/ntax=(\d+);/);
150
+ if (ntaxa) {
151
+ notNull(ntaxa[1], `No number of taxa found despite matching regex`);
152
+ ntax = parseInt(ntaxa[1]);
153
+ } else {
154
+ throw new Error(
155
+ `Expected dimension in form of ntax=(\\d+);. Got ${taxaLine}`,
156
+ );
157
+ }
158
+ break;
159
+ }
160
+ case /taxlabels/i.test(command): {
161
+ let token = await this.nextToken();
162
+ while (token !== ";") {
163
+ this.taxonSet.addTaxon(token);
164
+ token = await this.nextToken();
165
+ }
166
+ if (ntax) {
167
+ if (ntax != this.taxonSet.getTaxonCount()) {
168
+ throw new Error(
169
+ `found ${this.taxonSet.getTaxonCount()} taxa. Expected: ${ntax}}`,
170
+ );
171
+ }
172
+ }
173
+ break;
174
+ }
175
+ case /end/i.test(command): {
176
+ if (this.taxonSet.getTaxonCount() === 0) {
177
+ throw new Error("hit end of taxa section but didn't find any taxa");
178
+ }
179
+ this.taxonSet.lockTaxa(); // no more taxa can be added since we parsed a block;
180
+ await this.skipSemiColon();
181
+ keepGoing = false;
182
+ break;
183
+ }
184
+ default:
185
+ throw new Error(
186
+ `Reached impossible code looking for dimensions or taxlabels or end in taxa block "${command}"`,
187
+ );
188
+ }
189
+ }
190
+ }
191
+
192
+ private async *parseTreesBlock() {
193
+ let token;
194
+ let keepGoing = true;
195
+ while (keepGoing) {
196
+ const command = await this.skipUntil(/translate|tree|end/i);
197
+ switch (true) {
198
+ case /translate/i.test(command): {
199
+ // all white space removed by tranformStream so will be
200
+ // ['key','taxon,'] but may be ['key','taxon',','] if space tween taxa and ,
201
+ this.translateTaxonMap = new Map();
202
+ let i = 0;
203
+ let key;
204
+ token = await this.nextToken();
205
+ while (token !== ";") {
206
+ if (i % 2 == 0) {
207
+ key = token;
208
+ } else {
209
+ if (token[token.length - 1] === ",") {
210
+ token = token.slice(0, -1);
211
+ }
212
+ // todo get taxa to add here.
213
+ if (this.taxonSet.isFinalized) {
214
+ if (!this.taxonSet.hasTaxon(token)) {
215
+ throw new Error(
216
+ `Taxon ${token} not found in taxa block - but found in translate block`,
217
+ );
218
+ }
219
+ } else {
220
+ //new taxon set so add it;
221
+ this.taxonSet.addTaxon(token);
222
+ }
223
+ // const taxon = this.taxonSet.getTaxonByName(token)
224
+ notNull(
225
+ key,
226
+ "Error parsing nexus. Expected key for taxa but found nothing",
227
+ );
228
+ this.translateTaxonMap.set(key, token);
229
+ }
230
+ token = await this.nextToken();
231
+ while (token === ",") {
232
+ token = await this.nextToken();
233
+ } //incase some white space in there
234
+ i++;
235
+ }
236
+ this.taxonSet.lockTaxa();
237
+ break;
238
+ }
239
+ case /tree/i.test(command): {
240
+ //parse tree
241
+ // put this in loop so the next call parses the next tree;
242
+
243
+ // first token will be tree id
244
+ // then possible annotation
245
+ // Then =
246
+ // then possible annotations
247
+ // then tree
248
+ //tree id =
249
+ await this.nextToken(); //todo - read to'=' not just next token
250
+ const parser = new NewickCharacterParser(this.taxonSet, {
251
+ translateTaxonNames: this.translateTaxonMap,
252
+ });
253
+ // read to first '(';
254
+ token = await this.skipUntil(/\(/);
255
+ let buffer = token
256
+ .split(newickDeliminators)
257
+ .filter((n) => n.length > 0)
258
+ .reverse();
259
+ while (!parser.isDone()) {
260
+ while (buffer.length > 0) {
261
+ const c = buffer.pop();
262
+ notNull(c, `Unexpectedly hit the end of the buffer`);
263
+ parser.parseCharacter(c);
264
+ }
265
+ if (!parser.isDone()) {
266
+ // get next token
267
+ token = await this.nextToken();
268
+ buffer = token
269
+ .split(newickDeliminators)
270
+ .filter((n) => n.length > 0)
271
+ .reverse();
272
+ }
273
+ }
274
+ const tree = parser.getTree();
275
+ yield tree;
276
+ break;
277
+ }
278
+ case /end/i.test(command):
279
+ await this.skipSemiColon();
280
+ this.hasTree = false;
281
+ // Give up the ghost.
282
+ //TODO read to the end of the file.
283
+ this.reader.releaseLock();
284
+ keepGoing = false;
285
+ break;
286
+ default:
287
+ throw new Error(
288
+ `Reached impossible code in treeblock block "${command}"`,
289
+ );
290
+ }
291
+ }
292
+ }
293
+ }
@@ -0,0 +1,77 @@
1
+ import { notNull } from "../../../../utils";
2
+
3
+ export enum STATUS {
4
+ PARSING = "parsing",
5
+ IN_SINGLE_QUOTE = "in single quote",
6
+ IN_DOUBLE_QUOTE = "in double quote",
7
+ IN_COMMENT = "in comment",
8
+ }
9
+
10
+ export function nexusTokenizer() {
11
+ return {
12
+ lastChunk: "",
13
+ status: STATUS.PARSING,
14
+ end: "",
15
+ start() {},
16
+ transform(chunk: string, controller: { enqueue: (arg0: string) => void }) {
17
+ // not really any but we'll see
18
+ const data = this.lastChunk + chunk;
19
+ let buffer = "";
20
+ for (let i = 0; i < data.length; i++) {
21
+ const char = data[i];
22
+ notNull(char, `Internal Error. Hit empty character in array`);
23
+ if (this.status === STATUS.PARSING) {
24
+ // on the look out for quotes and comments
25
+ [this.status, this.end] = getStatusAndEnd(char);
26
+ if (this.status === STATUS.IN_COMMENT) {
27
+ // clear the buffer and send the comment separate
28
+ if (buffer.length > 0) {
29
+ controller.enqueue(buffer); //pass it on
30
+ }
31
+ buffer = "";
32
+ }
33
+ } else if (char === this.end) {
34
+ this.status = STATUS.PARSING;
35
+ this.end = "";
36
+ }
37
+ // if not in quote and hit a space then send it on.
38
+ if (this.status === STATUS.PARSING && /\s|;|\]/.test(char)) {
39
+ if (buffer.length > 0) {
40
+ if (/\]/.test(char)) {
41
+ buffer += char;
42
+ } // close the comment and pass
43
+ controller.enqueue(buffer); //pass it on
44
+ buffer = "";
45
+ }
46
+ if (/;/.test(char)) {
47
+ controller.enqueue(char); // sent the ';'
48
+ }
49
+ } else {
50
+ buffer += char;
51
+ }
52
+ }
53
+ this.lastChunk = buffer;
54
+ },
55
+ flush(controller: { enqueue: (arg0: string) => void }) {
56
+ if (this.lastChunk) {
57
+ controller.enqueue(this.lastChunk);
58
+ }
59
+ },
60
+ };
61
+ }
62
+
63
+ function getStatusAndEnd(char: string): [STATUS, string] {
64
+ if (char === "'") {
65
+ return [STATUS.IN_SINGLE_QUOTE, "'"];
66
+ }
67
+ if (char === '"') {
68
+ return [STATUS.IN_DOUBLE_QUOTE, '"'];
69
+ }
70
+ if (char === "[") {
71
+ return [STATUS.IN_COMMENT, "]"];
72
+ }
73
+ return [STATUS.PARSING, ""];
74
+ }
75
+
76
+ export const newickDeliminators =
77
+ /\s*('[^']+'|"[^"]+"|\[&[^[]+]|,|:|\)|\(|;)\s*/;
@@ -0,0 +1,109 @@
1
+ import { TaxonSet, Tree } from "../..";
2
+
3
+ enum NexusBlock {
4
+ Taxa = "taxa",
5
+ Trees = "trees",
6
+ Other = "other"
7
+ }
8
+
9
+ enum STATUS {
10
+ BLOCK_NAME_NEXT,
11
+ SKIP_SEMICOLON,
12
+ PARSING,
13
+ SKIPPING,
14
+ IN_BLOCK,
15
+ SKIP_UNTIL,
16
+ }
17
+
18
+ export function NexusTransformStream(){
19
+ // local parameters
20
+ const taxonSet = new TaxonSet();
21
+ let currentBlock :NexusBlock | undefined= undefined;
22
+ let status = STATUS.PARSING;
23
+ let command = undefined;
24
+
25
+ function processChunk(chunk:string):Tree|undefined{
26
+ if(status === STATUS.SKIP_SEMICOLON){
27
+ if(!chunk.match(/;$/)) {
28
+ throw `expected ";" got ${chunk}`;
29
+ }
30
+ status = STATUS.PARSING;
31
+ return;
32
+ }else if(status === STATUS.SKIPPING){
33
+ if(chunk.match(/\bend;/i)){
34
+ status = STATUS.PARSING;
35
+ currentBlock = undefined;
36
+ return;
37
+ }else{
38
+ return;
39
+ }
40
+ } else if(status === STATUS.BLOCK_NAME_NEXT){
41
+ if(chunk.match(/taxa/i)){
42
+ currentBlock = NexusBlock.Taxa;
43
+ status = STATUS.SKIP_SEMICOLON;
44
+ }else if(chunk.match(/trees/i)){
45
+ currentBlock = NexusBlock.Trees;
46
+ status = STATUS.SKIP_SEMICOLON;
47
+ }else{
48
+ console.log(
49
+ `skipping block ${chunk}. Only parsing blocks are taxa and trees for now.`,
50
+ )
51
+ }
52
+ status = STATUS.SKIPPING;
53
+ }else if(chunk.match(/\bbegin/i)){
54
+ status = STATUS.BLOCK_NAME_NEXT;
55
+ return;
56
+ }else if(status === STATUS.PARSING && currentBlock === NexusBlock.Taxa){
57
+ if(command)
58
+ }
59
+
60
+
61
+ return parseTree(chunk);
62
+
63
+ }
64
+
65
+ function parseNextBlock(chunk:string){
66
+ const blockName = getNextBlockName(chunk);
67
+ switch (blockName) {
68
+ case "taxa":
69
+ this.currentBlock = "taxa"
70
+ await this.parseTaxaBlock()
71
+ break
72
+ case "trees":
73
+ this.currentBlock = "trees"
74
+ break
75
+ default:
76
+ console.log(
77
+ `skipping block ${blockName}. Only parsing blocks are taxa and trees for now.`,
78
+ )
79
+ await this.readToEndOfBlock()
80
+ }
81
+ }
82
+
83
+ function getNextBlockName(chunck ) {
84
+ while (true) {
85
+ const value = await this.nextToken()
86
+
87
+ if (value.match(/\bbegin/i)) {
88
+ const token = await this.nextToken()
89
+ this.skipSemiColon()
90
+ return token
91
+ }
92
+ }
93
+ }
94
+
95
+ return {
96
+ start(){},
97
+ transform(chunk: string, controller: { enqueue: (arg0: Tree) => void }) {
98
+
99
+ const tree = processChunk(chunk);
100
+
101
+ if(tree){
102
+ controller.enqueue(tree);
103
+ }
104
+
105
+ },
106
+ flush(controller: any) { }
107
+ }
108
+
109
+ }
@@ -0,0 +1,46 @@
1
+ import type { Maybe, Undefinable } from "@figtreejs/maybe/maybe";
2
+ import { Nothing, Some, MaybeType } from "@figtreejs/maybe/maybe";
3
+ import type { Taxon, TaxonSetData } from "./taxon";
4
+
5
+ export function maybeGetNameFromIndex(
6
+ data: TaxonSetData,
7
+ id: Maybe<number> | number,
8
+ ): Maybe<string> {
9
+ let n: number;
10
+ if (id instanceof Object) {
11
+ if (id.type === MaybeType.Nothing) {
12
+ return id;
13
+ } else {
14
+ n = id.value;
15
+ }
16
+ } else {
17
+ n = id;
18
+ }
19
+ const name = data.allNames[n] as Undefinable<string>;
20
+ if (name === undefined) {
21
+ return Nothing();
22
+ }
23
+ return Some(name);
24
+ }
25
+
26
+ export function maybeGetTaxonByName(
27
+ data: TaxonSetData,
28
+ id: Maybe<string> | string,
29
+ ): Maybe<Taxon> {
30
+ let n: string;
31
+ if (id instanceof Object) {
32
+ if (id.type === MaybeType.Nothing) {
33
+ return id;
34
+ } else {
35
+ n = id.value;
36
+ }
37
+ } else {
38
+ n = id;
39
+ }
40
+ const taxon = data.byName[n] as Undefinable<Taxon>;
41
+ if (taxon === undefined) {
42
+ return Nothing();
43
+ } else {
44
+ return Some(taxon);
45
+ }
46
+ }
@@ -0,0 +1 @@
1
+ export * from "./taxon";