1+ // This code is Deno only (it uses the new `using` keyword)
2+
13import { extractResultError , ResultError } from "../compound.ts" ;
24import { Position , PositionedError } from "../parser/parser_lib.ts" ;
35import { HEADS } from "./parser.ts" ;
@@ -12,95 +14,115 @@ type WorkerError =
1214 } >
1315 | Readonly < { type : "other" ; error : unknown } > ;
1416
15- function buildOffloaded ( source : string ) : Promise < Dictionary > {
16- return new Promise ( ( resolve , reject ) => {
17- const worker = new Worker (
18- new URL ( "./worker.ts" , import . meta. url ) ,
19- { type : "module" } ,
20- ) ;
21- worker . postMessage ( source ) ;
22- worker . onmessage = ( event ) => {
23- resolve ( event . data as Dictionary ) ;
24- worker . terminate ( ) ;
25- } ;
26- worker . onerror = ( event ) => {
27- const error = event . error as WorkerError ;
28- switch ( error . type ) {
29- case "result error" :
30- reject (
31- new AggregateError (
32- error . errors . map ( ( error ) =>
33- new PositionedError (
34- error . message ,
35- { position : error . position ?? undefined } ,
36- )
17+ class ParserWorker {
18+ #worker = new Worker (
19+ new URL ( "./worker.ts" , import . meta. url ) ,
20+ { type : "module" } ,
21+ ) ;
22+ [ Symbol . dispose ] ( ) {
23+ this . #worker. terminate ( ) ;
24+ }
25+ parse ( source : string ) : Promise < Dictionary > {
26+ return new Promise ( ( resolve , reject ) => {
27+ const messageCallback = ( event : MessageEvent ) => {
28+ resolve ( event . data as Dictionary ) ;
29+ this . #worker. removeEventListener ( "message" , messageCallback ) ;
30+ } ;
31+ this . #worker. addEventListener ( "message" , messageCallback ) ;
32+ const errorCallback = ( event : ErrorEvent ) => {
33+ const error = event . error as WorkerError ;
34+ switch ( error . type ) {
35+ case "result error" :
36+ reject (
37+ new AggregateError (
38+ error . errors . map ( ( error ) =>
39+ new PositionedError (
40+ error . message ,
41+ { position : error . position ?? undefined } ,
42+ )
43+ ) ,
3744 ) ,
38- ) ,
39- ) ;
40- break ;
41- case "other" :
42- reject ( error . error ) ;
43- break ;
44- }
45- } ;
46- } ) ;
45+ ) ;
46+ break ;
47+ case "other" :
48+ reject ( error . error ) ;
49+ break ;
50+ }
51+ this . #worker. removeEventListener ( "error" , errorCallback ) ;
52+ } ;
53+ this . #worker. addEventListener ( "error" , errorCallback ) ;
54+ this . #worker. postMessage ( source ) ;
55+ } ) ;
56+ }
4757}
48- export async function parseDictionary ( source : string ) : Promise < Dictionary > {
49- const heads = [ ...source . matchAll ( HEADS ) ] . map ( ( match ) => match . index ) ;
50- const regionIndices = [ ...new Array ( navigator . hardwareConcurrency ) . keys ( ) ]
51- . map ( ( index ) => {
52- const start = index * source . length / navigator . hardwareConcurrency ;
53- for ( const head of heads ) {
54- if ( start <= head ) {
55- return head ;
58+ export class Parser {
59+ #workers = new Array ( navigator . hardwareConcurrency )
60+ . fill ( undefined )
61+ . map ( ( ) => new ParserWorker ( ) ) ;
62+ [ Symbol . dispose ] ( ) : void {
63+ using stack = new DisposableStack ( ) ;
64+ for ( const worker of this . #workers) {
65+ stack . use ( worker ) ;
66+ }
67+ }
68+ async parse ( source : string ) : Promise < Dictionary > {
69+ const heads = [ ...source . matchAll ( HEADS ) ] . map ( ( match ) => match . index ) ;
70+ const regionIndices = [ ...new Array ( this . #workers. length ) . keys ( ) ]
71+ . map ( ( index ) => {
72+ const start = index * source . length / this . #workers. length ;
73+ for ( const head of heads ) {
74+ if ( start <= head ) {
75+ return head ;
76+ }
77+ }
78+ return source . length ;
79+ } ) ;
80+ const jobs = regionIndices . map ( ( index , i ) => ( {
81+ index : index ,
82+ job : this . #workers[ i ] . parse (
83+ source . slice ( index , regionIndices [ i + 1 ] ?? source . length ) ,
84+ ) ,
85+ } ) ) ;
86+ const dictionary : Dictionary = new Map ( ) ;
87+ const errors : Array < ResultError > = [ ] ;
88+ for ( const job of jobs ) {
89+ let entries : Dictionary ;
90+ try {
91+ // deno-lint-ignore no-await-in-loop
92+ entries = await job . job ;
93+ } catch ( error ) {
94+ for ( const resultError of extractResultError ( error ) ) {
95+ if (
96+ resultError instanceof PositionedError &&
97+ resultError . position != null
98+ ) {
99+ errors . push (
100+ new PositionedError ( resultError . message , {
101+ position : {
102+ position : job . index + resultError . position . position ,
103+ length : resultError . position . length ,
104+ } ,
105+ cause : resultError ,
106+ } ) ,
107+ ) ;
108+ } else {
109+ errors . push ( resultError ) ;
110+ }
56111 }
112+ continue ;
57113 }
58- return source . length ;
59- } ) ;
60- const jobs = regionIndices . map ( ( index , i ) => ( {
61- index : index ,
62- job : buildOffloaded (
63- source . slice ( index , regionIndices [ i + 1 ] ?? source . length ) ,
64- ) ,
65- } ) ) ;
66- const dictionary : Dictionary = new Map ( ) ;
67- const errors : Array < ResultError > = [ ] ;
68- for ( const job of jobs ) {
69- let entries : Dictionary ;
70- try {
71- // deno-lint-ignore no-await-in-loop
72- entries = await job . job ;
73- } catch ( error ) {
74- for ( const resultError of extractResultError ( error ) ) {
75- if (
76- resultError instanceof PositionedError && resultError . position != null
77- ) {
78- errors . push (
79- new PositionedError ( resultError . message , {
80- position : {
81- position : job . index + resultError . position . position ,
82- length : resultError . position . length ,
83- } ,
84- cause : resultError ,
85- } ) ,
86- ) ;
114+ for ( const [ word , definition ] of entries . entries ( ) ) {
115+ if ( dictionary . has ( word ) ) {
116+ errors . push ( new ResultError ( `duplicate Toki Pona word "${ word } "` ) ) ;
87117 } else {
88- errors . push ( resultError ) ;
118+ dictionary . set ( word , definition ) ;
89119 }
90120 }
91- continue ;
92121 }
93- for ( const [ word , definition ] of entries . entries ( ) ) {
94- if ( dictionary . has ( word ) ) {
95- errors . push ( new ResultError ( `duplicate Toki Pona word "${ word } "` ) ) ;
96- } else {
97- dictionary . set ( word , definition ) ;
98- }
122+ if ( errors . length === 0 ) {
123+ return dictionary ;
124+ } else {
125+ throw new AggregateError ( errors ) ;
99126 }
100127 }
101- if ( errors . length === 0 ) {
102- return dictionary ;
103- } else {
104- throw new AggregateError ( errors ) ;
105- }
106128}
0 commit comments