22 <div :class =" $style.playgroundRoot" >
33 <div :class =" $style.playgroundHeader" >
44 <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 >
713 </div >
814 </div >
915 <div :class =" [$style.playgroundPaneRoot, $style.playgroundEditorPane]" >
8389</template >
8490
8591<script setup lang="ts">
86- import { AISCRIPT_VERSION , Parser , Interpreter , utils , errors , type Ast } from ' @syuilo/aiscript' ;
8792import { inBrowser } from ' vitepress' ;
8893import { ref , computed , useTemplateRef , nextTick , onMounted , watch , onUnmounted } from ' vue' ;
8994import { createHighlighterCore } from ' shiki/core' ;
9095import type { HighlighterCore , LanguageRegistration } from ' shiki/core' ;
9196import { createOnigurumaEngine } from ' shiki/engine/oniguruma' ;
9297import lzString from ' lz-string' ;
9398import { useThrottle } from ' ../scripts/throttle' ;
99+ import type { Runner } from ' ../scripts/runner' ;
100+ import { latestVersion , versionModules } from ' ../scripts/versions' ;
94101
95102// lz-stringがCommonJSモジュールだったみたいなので
96103const { compressToEncodedURIComponent, decompressFromEncodedURIComponent } = lzString ;
@@ -166,8 +173,10 @@ function replaceWithFizzbuzz() {
166173// #endregion
167174
168175// #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 );
171180
172181const isRunning = ref (false );
173182
@@ -179,7 +188,7 @@ const logEl = useTemplateRef('logEl');
179188
180189const isSyntaxError = ref (false );
181190
182- const ast = ref <Ast . Node [] | null >(null );
191+ const ast = ref <unknown >(null );
183192const astHtml = ref (' ' );
184193
185194const metadata = ref <unknown >(null );
@@ -188,16 +197,16 @@ const metadataHtml = ref('');
188197function parse() {
189198 isSyntaxError .value = false ;
190199
191- if (parser != null ) {
200+ if (runner .value == null ) {
201+ ast .value = null ;
202+ } else {
192203 try {
193- const _ast = parser .parse (code .value );
204+ const [ast_, metadata_] = runner . value .parse (code .value );
194205 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 ;
199208 } catch (err ) {
200- if (err instanceof errors . AiScriptError ) {
209+ if (runner . value . isAiScriptError ( err ) ) {
201210 logs .value = [{
202211 text: ` [SyntaxError] ${err .name }: ${err .message } ` ,
203212 type: ' error' ,
@@ -207,30 +216,15 @@ function parse() {
207216 ast .value = null ;
208217 metadata .value = null ;
209218 }
210- } else {
211- ast .value = null ;
212219 }
213220}
214221
215222function 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 });
234228 },
235229 });
236230}
@@ -244,10 +238,10 @@ async function run() {
244238 isRunning .value = true ;
245239
246240 parse ();
247- if (ast .value != null && interpreter != = null ) {
241+ if (ast .value != null && runner . value ! = null ) {
248242 try {
249243 const execStartTime = performance .now ();
250- await interpreter .exec (ast .value );
244+ await runner . value .exec (ast .value );
251245 const execEndTime = performance .now ();
252246 logs .value .push ({
253247 text: ` [Playground] Execution Completed in ${Math .round (execEndTime - execStartTime )}ms ` ,
@@ -259,21 +253,8 @@ async function run() {
259253 });
260254 }
261255 } 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 );
277258 logs .value .push ({
278259 text: ` [${errorName }] ${err .name }: ${err .message } ` ,
279260 type: ' error' ,
@@ -291,8 +272,8 @@ async function run() {
291272}
292273
293274function abort() {
294- if (interpreter != null ) {
295- interpreter . abort ();
275+ if (runner . value != null ) {
276+ runner . value . dispose ();
296277 logs .value .push ({
297278 text: ' [Playground] Execution Aborted' ,
298279 type: ' info' ,
@@ -309,7 +290,7 @@ function clearLog() {
309290// #region Permalink with hash
310291type HashData = {
311292 code: string ;
312- // TODO: バージョン情報(マルチバージョン対応の際に必要。なければ最新にフォールバック)
293+ version ? : string ;
313294};
314295const hash = ref <string | null >(inBrowser ? window .location .hash .slice (1 ) || localStorage .getItem (' ais:playground' ) : null );
315296const hashData = computed <HashData | null >(() => {
@@ -332,16 +313,38 @@ onMounted(async () => {
332313 const loadStartedAt = Date .now ();
333314
334315 await init ();
335- initAiScriptEnv ();
336316
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+ }
339324 }
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 ;
342338 }, { immediate: true });
343339
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 ]) => {
345348 parse ();
346349 if (highlighter ) {
347350 editorHtml .value = highlighter .codeToHtml (newCode , {
@@ -399,9 +402,7 @@ onMounted(async () => {
399402});
400403
401404onUnmounted (() => {
402- if (interpreter != null ) {
403- interpreter .abort ();
404- }
405+ runner .value ?.dispose ();
405406});
406407 </script >
407408
@@ -420,8 +421,10 @@ onUnmounted(() => {
420421
421422.playgroundHeaderInner {
422423 margin : 0 auto ;
423- padding : 0.5em 36px ;
424+ padding : 0 36px ;
425+ min-height : 40px ;
424426 display : flex ;
427+ align-items : center ;
425428}
426429
427430.playgroundOptions {
@@ -629,14 +632,36 @@ onUnmounted(() => {
629632 background-color : var (--vp-button-brand-hover-bg );
630633}
631634
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+
632657@media (max-width : 768px ) {
633658 .playgroundEditorScroller ,
634659 .playgroundEditorTextarea {
635660 padding : 24px 24px ;
636661 }
637662
638663 .playgroundHeaderInner {
639- padding : 0.5 em 24px ;
664+ padding : 0 24px ;
640665 }
641666
642667 .playgroundResultActionsLeft {
0 commit comments