2
2
<div :class =" $style.playgroundRoot" >
3
3
<div :class =" $style.playgroundHeader" >
4
4
<div :class =" $style.playgroundHeaderInner" >
5
- <div >Playground <small >(v{{ AISCRIPT_VERSION }})</small ></div >
6
- <div :class =" $style.playgroundOptions" ></div >
5
+ <div >Playground <small >(v{{ runner?.version ?? "???" }})</small ></div >
6
+ <div :class =" $style.playgroundOptions" >
7
+ <select :class =" $style.playgroundSelect" v-model =" version" >
8
+ <option v-for =" version in versionModules.keys()" :value =" version" >
9
+ {{ version === latestVersion ? `${version} (latest)` : version }}
10
+ </option >
11
+ </select >
12
+ </div >
7
13
</div >
8
14
</div >
9
15
<div :class =" [$style.playgroundPaneRoot, $style.playgroundEditorPane]" >
83
89
</template >
84
90
85
91
<script setup lang="ts">
86
- import { AISCRIPT_VERSION , Parser , Interpreter , utils , errors , type Ast } from ' @syuilo/aiscript' ;
87
92
import { inBrowser } from ' vitepress' ;
88
93
import { ref , computed , useTemplateRef , nextTick , onMounted , watch , onUnmounted } from ' vue' ;
89
94
import { createHighlighterCore } from ' shiki/core' ;
90
95
import type { HighlighterCore , LanguageRegistration } from ' shiki/core' ;
91
96
import { createOnigurumaEngine } from ' shiki/engine/oniguruma' ;
92
97
import lzString from ' lz-string' ;
93
98
import { useThrottle } from ' ../scripts/throttle' ;
99
+ import type { Runner } from ' ../scripts/runner' ;
100
+ import { latestVersion , versionModules } from ' ../scripts/versions' ;
94
101
95
102
// lz-stringがCommonJSモジュールだったみたいなので
96
103
const { compressToEncodedURIComponent, decompressFromEncodedURIComponent } = lzString ;
@@ -166,8 +173,10 @@ function replaceWithFizzbuzz() {
166
173
// #endregion
167
174
168
175
// #region Runner
169
- let parser: Parser | null = null ;
170
- let interpreter: Interpreter | null = null ;
176
+ let RunnerConstructor: new (... args : ConstructorParameters <typeof Runner >) => Runner ;
177
+ const runner = ref <Runner >();
178
+
179
+ const version = ref (latestVersion );
171
180
172
181
const isRunning = ref (false );
173
182
@@ -179,7 +188,7 @@ const logEl = useTemplateRef('logEl');
179
188
180
189
const isSyntaxError = ref (false );
181
190
182
- const ast = ref <Ast . Node [] | null >(null );
191
+ const ast = ref <unknown >(null );
183
192
const astHtml = ref (' ' );
184
193
185
194
const metadata = ref <unknown >(null );
@@ -188,16 +197,16 @@ const metadataHtml = ref('');
188
197
function parse() {
189
198
isSyntaxError .value = false ;
190
199
191
- if (parser != null ) {
200
+ if (runner .value == null ) {
201
+ ast .value = null ;
202
+ } else {
192
203
try {
193
- const _ast = parser .parse (code .value );
204
+ const [ast_, metadata_] = runner . value .parse (code .value );
194
205
logs .value = [];
195
- ast .value = _ast ;
196
-
197
- const meta = Interpreter .collectMetadata (_ast );
198
- metadata .value = meta ?.get (null ) ?? null ;
206
+ ast .value = ast_ ;
207
+ metadata .value = metadata_ ?.get (null ) ?? null ;
199
208
} catch (err ) {
200
- if (err instanceof errors . AiScriptError ) {
209
+ if (runner . value . isAiScriptError ( err ) ) {
201
210
logs .value = [{
202
211
text: ` [SyntaxError] ${err .name }: ${err .message } ` ,
203
212
type: ' error' ,
@@ -207,30 +216,15 @@ function parse() {
207
216
ast .value = null ;
208
217
metadata .value = null ;
209
218
}
210
- } else {
211
- ast .value = null ;
212
219
}
213
220
}
214
221
215
222
function initAiScriptEnv() {
216
- if (parser == null ) {
217
- parser = new Parser ();
218
- }
219
- if (interpreter != null ) {
220
- interpreter .abort ();
221
- }
222
- interpreter = new Interpreter ({}, {
223
- out : (value ) => {
224
- logs .value .push ({
225
- text: value .type === ' num' ? value .value .toString () : value .type === ' str' ? ` "${value .value }" ` : JSON .stringify (utils .valToJs (value ), null , 2 ) ?? ' ' ,
226
- });
227
- },
228
- log : (type , params ) => {
229
- if (type === ' end' && params .val != null && ' type' in params .val ) {
230
- logs .value .push ({
231
- text: utils .valToString (params .val , true ),
232
- });
233
- }
223
+ runner .value ?.dispose ();
224
+
225
+ runner .value = new RunnerConstructor ({
226
+ print(text ) {
227
+ logs .value .push ({ text });
234
228
},
235
229
});
236
230
}
@@ -244,10 +238,10 @@ async function run() {
244
238
isRunning .value = true ;
245
239
246
240
parse ();
247
- if (ast .value != null && interpreter != = null ) {
241
+ if (ast .value != null && runner . value ! = null ) {
248
242
try {
249
243
const execStartTime = performance .now ();
250
- await interpreter .exec (ast .value );
244
+ await runner . value .exec (ast .value );
251
245
const execEndTime = performance .now ();
252
246
logs .value .push ({
253
247
text: ` [Playground] Execution Completed in ${Math .round (execEndTime - execStartTime )}ms ` ,
@@ -259,21 +253,8 @@ async function run() {
259
253
});
260
254
}
261
255
} catch (err ) {
262
- if (err instanceof errors .AiScriptError ) {
263
- let errorName = ' AiScriptError' ;
264
-
265
- if (err instanceof errors .AiScriptSyntaxError ) {
266
- errorName = ' SyntaxError' ;
267
- } else if (err instanceof errors .AiScriptTypeError ) {
268
- errorName = ' TypeError' ;
269
- } else if (err instanceof errors .AiScriptRuntimeError ) {
270
- errorName = ' RuntimeError' ;
271
- } else if (err instanceof errors .AiScriptIndexOutOfRangeError ) {
272
- errorName = ' IndexOutOfRangeError' ;
273
- } else if (err instanceof errors .AiScriptUserError ) {
274
- errorName = ' UserError' ;
275
- }
276
-
256
+ if (runner .value .isAiScriptError (err )) {
257
+ const errorName = runner .value .getErrorName (err );
277
258
logs .value .push ({
278
259
text: ` [${errorName }] ${err .name }: ${err .message } ` ,
279
260
type: ' error' ,
@@ -291,8 +272,8 @@ async function run() {
291
272
}
292
273
293
274
function abort() {
294
- if (interpreter != null ) {
295
- interpreter . abort ();
275
+ if (runner . value != null ) {
276
+ runner . value . dispose ();
296
277
logs .value .push ({
297
278
text: ' [Playground] Execution Aborted' ,
298
279
type: ' info' ,
@@ -309,7 +290,7 @@ function clearLog() {
309
290
// #region Permalink with hash
310
291
type HashData = {
311
292
code: string ;
312
- // TODO: バージョン情報(マルチバージョン対応の際に必要。なければ最新にフォールバック)
293
+ version ? : string ;
313
294
};
314
295
const hash = ref <string | null >(inBrowser ? window .location .hash .slice (1 ) || localStorage .getItem (' ais:playground' ) : null );
315
296
const hashData = computed <HashData | null >(() => {
@@ -332,16 +313,38 @@ onMounted(async () => {
332
313
const loadStartedAt = Date .now ();
333
314
334
315
await init ();
335
- initAiScriptEnv ();
336
316
337
- if (hashData .value != null && hashData .value .code != null ) {
338
- code .value = hashData .value .code ;
317
+ if (hashData .value != null ) {
318
+ if (hashData .value .code != null ) {
319
+ code .value = hashData .value .code ;
320
+ }
321
+ if (hashData .value .version != null ) {
322
+ version .value = hashData .value .version ;
323
+ }
339
324
}
340
- watch ([code ], () => {
341
- updateHash ({ code: code .value });
325
+
326
+ watch (version , async () => {
327
+ editorLoading .value = true ;
328
+
329
+ const import_ = versionModules .get (version .value );
330
+ if (import_ == null ) return ;
331
+
332
+ const module = await import_ ();
333
+ RunnerConstructor = module .default ;
334
+
335
+ initAiScriptEnv ();
336
+
337
+ editorLoading .value = false ;
342
338
}, { immediate: true });
343
339
344
- watch (code , async (newCode ) => {
340
+ watch ([code , version ], () => {
341
+ updateHash ({
342
+ code: code .value ,
343
+ version: version .value ,
344
+ });
345
+ }, { immediate: true });
346
+
347
+ watch ([code , runner ], ([newCode ]) => {
345
348
parse ();
346
349
if (highlighter ) {
347
350
editorHtml .value = highlighter .codeToHtml (newCode , {
@@ -399,9 +402,7 @@ onMounted(async () => {
399
402
});
400
403
401
404
onUnmounted (() => {
402
- if (interpreter != null ) {
403
- interpreter .abort ();
404
- }
405
+ runner .value ?.dispose ();
405
406
});
406
407
</script >
407
408
@@ -420,8 +421,10 @@ onUnmounted(() => {
420
421
421
422
.playgroundHeaderInner {
422
423
margin : 0 auto ;
423
- padding : 0.5em 36px ;
424
+ padding : 0 36px ;
425
+ min-height : 40px ;
424
426
display : flex ;
427
+ align-items : center ;
425
428
}
426
429
427
430
.playgroundOptions {
@@ -629,14 +632,36 @@ onUnmounted(() => {
629
632
background-color : var (--vp-button-brand-hover-bg );
630
633
}
631
634
635
+ .playgroundSelect {
636
+ background-color : var (--vp-button-alt-bg );
637
+ transition : background-color 0.25s ;
638
+ padding : 3px 36px 3px 16px ;
639
+ border-radius : 8px ;
640
+ font-family : var (--vp-font-family-base );
641
+ font-size : 80% ;
642
+
643
+ background-image : url (" data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e" );
644
+ background-repeat : no-repeat ;
645
+ background-position : right .75em center ;
646
+ background-size : 16px 12px ;
647
+ }
648
+
649
+ :global(html .dark ) .playgroundSelect {
650
+ background-image : url (" data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fffff5db' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e" );
651
+ }
652
+
653
+ .playgroundSelect :hover {
654
+ background-color : var (--vp-button-alt-hover-bg );
655
+ }
656
+
632
657
@media (max-width : 768px ) {
633
658
.playgroundEditorScroller ,
634
659
.playgroundEditorTextarea {
635
660
padding : 24px 24px ;
636
661
}
637
662
638
663
.playgroundHeaderInner {
639
- padding : 0.5 em 24px ;
664
+ padding : 0 24px ;
640
665
}
641
666
642
667
.playgroundResultActionsLeft {
0 commit comments