diff --git a/.env b/.env index f13f7deb..f62418b8 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ DATABASE_URL="file:./dev.sqlite3?connection_limit=1" NEXT_PUBLIC_BASE_URL="http://localhost:3000" -NEXT_PUBLIC_COURSE_ID_TO_LECTURE_IDS_JSON={"tuBeginner1":["8d692b48-8c19-4679-8d8f-3f27a051d44d","d4de75e2-758b-4500-b38e-96213c360527","99045fdf-6cb5-4947-b934-8b1bc5831bbd","37632776-e3ab-4cc5-ae08-934caf2ada53","8fbc94d3-d20c-4457-8997-61e85b3516d9","35957643-c106-4a97-8073-6705c39ab9a6","045094ae-1f5c-4caf-bc33-a86af985f13b","84805179-12cf-4871-969e-fb39e6ad767a"],"tuBeginner2":["5ba06885-2044-4c1e-bd65-2a9c5e9c9e39"],"test":["6481cd2e-5143-4a99-9c86-dd93b7254211"]} +NEXT_PUBLIC_COURSE_ID_TO_LECTURE_IDS_JSON={"tuBeginner1":["8d692b48-8c19-4679-8d8f-3f27a051d44d","d4de75e2-758b-4500-b38e-96213c360527","99045fdf-6cb5-4947-b934-8b1bc5831bbd","37632776-e3ab-4cc5-ae08-934caf2ada53","8fbc94d3-d20c-4457-8997-61e85b3516d9","35957643-c106-4a97-8073-6705c39ab9a6","045094ae-1f5c-4caf-bc33-a86af985f13b","84805179-12cf-4871-969e-fb39e6ad767a"],"tuBeginner2":["8d692b48-8c19-4679-8d8f-3f27a051d44d","d4de75e2-758b-4500-b38e-96213c360527","99045fdf-6cb5-4947-b934-8b1bc5831bbd","37632776-e3ab-4cc5-ae08-934caf2ada53","8fbc94d3-d20c-4457-8997-61e85b3516d9","35957643-c106-4a97-8073-6705c39ab9a6","045094ae-1f5c-4caf-bc33-a86af985f13b","84805179-12cf-4871-969e-fb39e6ad767a"],"test":["8d692b48-8c19-4679-8d8f-3f27a051d44d"]} SUPERTOKENS_URI="https://st-dev-9ecbdd10-5f8e-11ef-a204-f3d2d26d900f.aws.supertokens.io" SUPERTOKENS_API_KEY="J-1qlZUxj8wo1xO3DZBu9rUwZc" diff --git a/README.md b/README.md index 49f2cb6d..4e008dad 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ An educational web app for training program tracing skills. ``` 2. Open lectures via the following URLs: + - 初級プログラミングⅠ 1. http://localhost:3000/courses/tuBeginner1/lectures/8d692b48-8c19-4679-8d8f-3f27a051d44d 2. http://localhost:3000/courses/tuBeginner1/lectures/d4de75e2-758b-4500-b38e-96213c360527 @@ -51,7 +52,9 @@ An educational web app for training program tracing skills. 7. http://localhost:3000/courses/tuBeginner1/lectures/045094ae-1f5c-4caf-bc33-a86af985f13b 8. http://localhost:3000/courses/tuBeginner1/lectures/84805179-12cf-4871-969e-fb39e6ad767a - 初級プログラミングⅡ - 1. http://localhost:3000/courses/tuBeginner2/lectures/5ba06885-2044-4c1e-bd65-2a9c5e9c9e39 + 1. http://localhost:3000/courses/tuBeginner2/lectures/8d692b48-8c19-4679-8d8f-3f27a051d44d + - 動作確認用 + 1. http://localhost:3000/courses/test/lectures/8d692b48-8c19-4679-8d8f-3f27a051d44d The URL format is as follows. You can find parameter values from `NEXT_PUBLIC_COURSE_ID_TO_LECTURE_IDS_JSON` in the `.env` file. diff --git a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx index 67f377ba..1fb741c8 100644 --- a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx +++ b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/BoardEditor.tsx @@ -75,7 +75,7 @@ export const BoardEditor = forwardRef updateVariables(props.initialVariables); if (!keepSelectedCell) setSelectedCell(undefined); }, - [previousTraceItem, props.initialVariables, updateBoard, updateTurtles] + [previousTraceItem, props.initialVariables, updateBoard, updateTurtles, updateVariables] ); useImperativeHandle(ref, () => ({ @@ -84,8 +84,7 @@ export const BoardEditor = forwardRef useEffect(() => { initialize(props.previousTraceItemIndex >= 1); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.previousTraceItemIndex, props.problem]); + }, [props.previousTraceItemIndex, initialize]); // We should include initialize because it depends on props.initialVariables const updateTurtle = (currentTurtle: TurtleTrace, newTurtle: Partial): void => { updateTurtles((draft) => { @@ -116,7 +115,8 @@ export const BoardEditor = forwardRef zenkakuAlphanumericalsToHankaku(value) !== zenkakuAlphanumericalsToHankaku(props.currentVariables[name].toString()) ) { - locations.push(`変数${name}`); + const isExpression = /[+\-*/%()[\]\\.]/.test(name); + locations.push(isExpression ? `式「${name}」` : `変数${name}`); if (props.initialVariables[name].toString() === props.currentVariables[name].toString()) { hintText += hintText ? '\n\n' : '\n\nヒント: '; hintText += @@ -234,7 +234,7 @@ export const BoardEditor = forwardRef - + diff --git a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx index 31e97a32..b4e6170f 100644 --- a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx +++ b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/ProblmBody.tsx @@ -317,11 +317,10 @@ function getInitialVariables( let adjustedPreviousTraceItemIndex = previousTraceItemIndex; if (problemType === 'step') { - while ( - adjustedPreviousTraceItemIndex > 0 && - traceItems[currentTraceItemIndex].depth !== traceItems[adjustedPreviousTraceItemIndex].depth - ) { - if (traceItems[currentTraceItemIndex].depth > traceItems[adjustedPreviousTraceItemIndex].depth) { + const currentDepth = traceItems[currentTraceItemIndex].depth; + while (adjustedPreviousTraceItemIndex > 0 && currentDepth !== traceItems[adjustedPreviousTraceItemIndex].depth) { + if (currentDepth > traceItems[adjustedPreviousTraceItemIndex].depth) { + // 過去のトレースの方が現在のトレースよりも深いため。 return getEmptyVariables(currentVariables); } adjustedPreviousTraceItemIndex--; @@ -330,6 +329,7 @@ function getInitialVariables( if ( traceItems[currentTraceItemIndex].callStack.at(-1) !== traceItems[adjustedPreviousTraceItemIndex].callStack.at(-1) ) { + // 過去のトレースと現在のトレースのスタックトレースが別であるため。 return getEmptyVariables(currentVariables); } } diff --git a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/Variables.tsx b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/Variables.tsx index 8b2a4c0d..b99c7caf 100644 --- a/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/Variables.tsx +++ b/src/app/(withAuth)/courses/[courseId]/lectures/[lectureId]/problems/[problemId]/Variables.tsx @@ -25,7 +25,7 @@ export const Variables: React.FC = ({ traceItemVars }) => {
変数名変数/式 値(=や+=などの代入がないと変化しない)
- + diff --git a/src/app/(withAuth)/usage/page.tsx b/src/app/(withAuth)/usage/page.tsx index 187ab774..0b77201d 100644 --- a/src/app/(withAuth)/usage/page.tsx +++ b/src/app/(withAuth)/usage/page.tsx @@ -40,7 +40,7 @@ const CoursesPage: NextPage = async () => { - ④盤面の下に「変数名」「値」と書かれたフォームがある場合は、プログラムを実行した時の最終的な変数の値を入力してください。 + ④盤面の下に「変数/式」「値」と書かれたフォームがある場合は、プログラムを実行した時の最終的な変数の値を入力してください。 diff --git a/src/problems/instantiateProblem.ts b/src/problems/instantiateProblem.ts index 7ca51e3b..a96b3804 100644 --- a/src/problems/instantiateProblem.ts +++ b/src/problems/instantiateProblem.ts @@ -15,6 +15,11 @@ export type InstantiatedProblem = { */ displayProgram: string; + /** + * The program to be executable via `eval()`. For debugging. + */ + executableCode: string; + /** * The trace items of the program. */ diff --git a/src/problems/problemData.ts b/src/problems/problemData.ts index 3433ec4b..096ca5ca 100644 --- a/src/problems/problemData.ts +++ b/src/problems/problemData.ts @@ -90,7 +90,8 @@ export const problemIds = [ 'string5', 'oop1', 'oop2', - 'oop3', + 'static2', + 'polymorphism1', 'test1', 'test2', 'test3', @@ -197,7 +198,8 @@ export const problemIdToName: Record = { string5: '文字列を使おう(5)', oop1: 'オブジェクト指向プログラミング(1)', oop2: 'オブジェクト指向プログラミング(2)', - oop3: 'オブジェクト指向プログラミング(3)', + static2: '静的フィールド(2)', + polymorphism1: 'ポリモルフィズム(1)', test1: 'ステップ実行のテスト用問題(1)', test2: 'ステップ実行のテスト用問題(2)', test3: 'ステップ実行のテスト用問題(3)', @@ -242,8 +244,8 @@ export const courseIdToLectureIndexToProblemIds: Record ['method1', 'method2', 'method3', 'method4', 'method5', 'return1', 'return2', 'return3', 'return4', 'return5'], ['array1', 'array2', 'array3', 'array4', 'array5', 'string1', 'string2', 'string3', 'string4', 'string5'], ], - tuBeginner2: [['oop1', 'oop2', 'oop3']], - test: [['test1', 'test2', 'test3', 'test4', 'test5']], + tuBeginner2: [], + test: [['test1', 'test2', 'test3', 'test4', 'test5', 'oop1', 'oop2', 'static2', 'polymorphism1']], }; export const courseIdToLectureIds: Record = JSON.parse( @@ -253,7 +255,7 @@ export const courseIdToLectureIds: Record = JSON.parse( export const problemIdToLanguageIdToProgram: Record> = { straight: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); `.trim(), @@ -269,7 +271,7 @@ public class Main { }, straight2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.forward(); @@ -287,7 +289,7 @@ public class Main { }, stepBack: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.backward(); `.trim(), @@ -303,7 +305,7 @@ public class Main { }, stepBack2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.backward(); @@ -325,7 +327,7 @@ public class Main { }, turnRight: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.turnRight(); t.forward(); @@ -343,7 +345,7 @@ public class Main { }, turnRight2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.turnRight(); @@ -365,7 +367,7 @@ public class Main { }, turnLeftAndRight: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.turnRight(); t.forward(); t.turnLeft(); @@ -385,7 +387,7 @@ public class Main { }, turnLeftAndRight2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.turnRight(); t.forward(); @@ -407,7 +409,7 @@ public class Main { }, turnLeftAndRight3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.turnRight(); t.forward(); t.turnLeft(); @@ -431,7 +433,7 @@ public class Main { }, turnLeftAndRight4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.turnRight(); @@ -459,7 +461,7 @@ public class Main { }, square1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.turnRight(); t.forward(); @@ -481,7 +483,7 @@ public class Main { }, square2: { instrumented: ` -const t = new Turtle(<1-5>, <1-4>); +const t = new Turtle(<1-5>, <1-4>); // trace t.forward(); t.turnRight(); t.forward(); @@ -503,7 +505,7 @@ public class Main { }, square3: { instrumented: ` -const t = new Turtle(<2-5>, <2-5>); +const t = new Turtle(<2-5>, <2-5>); // trace t.forward(); t.turnLeft(); t.forward(); @@ -529,7 +531,7 @@ public class Main { }, square4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.turnRight(); @@ -565,7 +567,7 @@ public class Main { variable: { instrumented: ` s.set('x', <1-5>); -const t = new Turtle(s.get('x'), <1-5>); +const t = new Turtle(s.get('x'), <1-5>); // trace t.forward(); `.trim(), java: ` @@ -582,7 +584,7 @@ public class Main { instrumented: ` s.set('a', <2-6>); s.set('a', s.get('a') - 1); -const t = new Turtle(<1-5>, s.get('a')); +const t = new Turtle(<1-5>, s.get('a')); // trace t.forward(); `.trim(), java: ` @@ -601,7 +603,7 @@ public class Main { s.set('x', <1-4>); s.set('x', s.get('x') + 1); s.set('y', s.get('x') + 1); -const t = new Turtle(s.get('x'), s.get('y')); +const t = new Turtle(s.get('x'), s.get('y')); // trace t.forward(); `.trim(), java: ` @@ -621,7 +623,7 @@ public class Main { s.set('b', <1-4>); s.set('b', s.get('b') + 1); s.set('a', s.get('b') - 2); -const t = new Turtle(s.get('a') + 1, s.get('b')); +const t = new Turtle(s.get('a') + 1, s.get('b')); // trace t.forward(); `.trim(), java: ` @@ -642,7 +644,7 @@ s.set('x', <1-5>); s.set('x', s.get('x') - 1); s.set('y', s.get('x') * 2); s.set('y', s.get('y') / 3); -const t = new Turtle(s.get('x') + 1, s.get('y') + 1); +const t = new Turtle(s.get('x') + 1, s.get('y') + 1); // trace t.forward(); `.trim(), java: ` @@ -663,7 +665,7 @@ public class Main { s.set('a', <1-3>); s.set('b', s.get('a') * 2); s.set('c', s.get('b') - 2); -const t = new Turtle(s.get('c'), s.get('b')); +const t = new Turtle(s.get('c'), s.get('b')); // trace t.forward(); `.trim(), java: ` @@ -684,7 +686,7 @@ s.set('x', <0-2>); s.set('y', (s.get('x') * 2) + 1); s.set('z', (s.get('y') * 2) + (s.get('x') / 2)); s.set('x', s.get('z') / 3); -const t = new Turtle(s.get('x'), s.get('y')); +const t = new Turtle(s.get('x'), s.get('y')); // trace t.forward(); `.trim(), java: ` @@ -707,7 +709,7 @@ s.set('y', (s.get('x') * 3) + 2); s.set('z', (s.get('y') * 2) - (s.get('x') * 3)); s.set('x', (s.get('z') / 4) % 7); s.set('y', (s.get('x') + s.get('y')) % 7); -const t = new Turtle(s.get('x'), s.get('y')); +const t = new Turtle(s.get('x'), s.get('y')); // trace t.backward(); `.trim(), java: ` @@ -731,7 +733,7 @@ s.set('y', (s.get('x') * 4) + 3); s.set('z', (s.get('y') * 3) - (s.get('x') * 2)); s.set('x', ((s.get('z') / 5) + s.get('x')) % 7); s.set('y', ((s.get('x') * 2) + s.get('y')) % 7); -const t = new Turtle(s.get('x'), s.get('y')); +const t = new Turtle(s.get('x'), s.get('y')); // trace t.forward(); `.trim(), java: ` @@ -756,7 +758,7 @@ s.set('z', (s.get('y') * 2) - (s.get('x') * 3)); s.set('w', (s.get('z') + s.get('x')) % 5); s.set('x', ((s.get('z') / 6) + s.get('w')) % 7); s.set('y', ((s.get('x') * 3) + s.get('y')) % 7); -const t = new Turtle(s.get('x'), s.get('y')); +const t = new Turtle(s.get('x'), s.get('y')); // trace t.forward(); `.trim(), java: ` @@ -776,7 +778,7 @@ public class Main { }, while1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('i', 0); while (s.get('i') < <3-5>) { t.forward(); @@ -798,7 +800,7 @@ public class Main { }, while2: { instrumented: ` -const t = new Turtle(<0-1>, <0-1>); +const t = new Turtle(<0-1>, <0-1>); // trace s.set('i', <1-2>); while (s.get('i') < <4-6>) { t.forward(); @@ -820,7 +822,7 @@ public class Main { }, while3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('i', 0); while (s.get('i') < <2-3>) { s.set('i', s.get('i') + 1); @@ -844,7 +846,7 @@ public class Main { }, while4: { instrumented: ` -const t = new Turtle(<0-1>, <0-1>); +const t = new Turtle(<0-1>, <0-1>); // trace t.turnRight(); s.set('i', <1-2>); while (s.get('i') < <4-5>) { @@ -872,7 +874,7 @@ public class Main { }, while5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('i', <1-2>); while (s.get('i') < <3-4>) { t.forward(); @@ -900,7 +902,7 @@ public class Main { }, for1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <3-5>; s.set('i', s.get('i') + 1)) { t.forward(); } @@ -919,7 +921,7 @@ public class Main { }, for2: { instrumented: ` -const t = new Turtle(<1-2>, <1-2>); +const t = new Turtle(<1-2>, <1-2>); // trace for (s.set('i', <1-2>); s.get('i') < <4-6>; s.set('i', s.get('i') + 1)) { t.forward(); } @@ -938,7 +940,7 @@ public class Main { }, for3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('i', 0); for (; s.get('i') < <2-3>;) { t.forward(); @@ -962,7 +964,7 @@ public class Main { }, for4: { instrumented: ` -const t = new Turtle(<1-2>, <1-2>); +const t = new Turtle(<1-2>, <1-2>); // trace s.set('i', 1); for (; s.get('i') < <3-4>;) { t.forward(); @@ -994,7 +996,7 @@ for (s.set('i', 2); s.get('i') <= <4-5>; s.set('i', s.get('i') + 1)) { } delete s.vars['i']; s.set('x', s.get('x') / 3); -const t = new Turtle(s.get('x') + 1, 0); +const t = new Turtle(s.get('x') + 1, 0); // trace t.forward(); `.trim(), java: ` @@ -1022,7 +1024,7 @@ for (s.set('i', <4-5>); s.get('i') > 0; s.set('i', s.get('i') - 1)) { delete s.vars['i']; s.set('a', s.get('a') / 4); s.set('b', s.get('b') / 5); -const t = new Turtle(s.get('a') % 6, s.get('b') % 6); +const t = new Turtle(s.get('a') % 6, s.get('b') % 6); // trace t.forward(); `.trim(), java: ` @@ -1044,7 +1046,7 @@ public class Main { }, for7: { instrumented: ` -const t = new Turtle(3, 3); +const t = new Turtle(3, 3); // trace s.set('sum', 0); for (s.set('i', 1); s.get('i') <= <4-6>; s.set('i', s.get('i') + 1)) { s.set('sum', s.get('sum') + s.get('i')); @@ -1080,7 +1082,7 @@ public class Main { }, doubleLoop1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <2-3>; s.set('i', s.get('i') + 1)) { for (s.set('j', 0); s.get('j') < <2-3>; s.set('j', s.get('j') + 1)) { t.forward(); @@ -1106,7 +1108,7 @@ public class Main { }, doubleLoop2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.turnRight(); for (s.set('i', 0); s.get('i') < <2-3>; s.set('i', s.get('i') + 1)) { for (s.set('j', 0); s.get('j') < <2-3>; s.set('j', s.get('j') + 1)) { @@ -1134,7 +1136,7 @@ public class Main { }, doubleLoop3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', <3-4>); s.get('i') > 0; s.set('i', s.get('i') - 1)) { for (s.set('j', 0); s.get('j') < s.get('i'); s.set('j', s.get('j') + 1)) { t.forward(); @@ -1160,7 +1162,7 @@ public class Main { }, doubleLoop4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', <3-4>); s.get('i') > 0; s.set('i', s.get('i') - 1)) { s.set('j', s.get('i')); while (s.get('j') >= 0) { @@ -1190,7 +1192,7 @@ public class Main { }, doubleLoop5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <3-4>; s.set('i', s.get('i') + 1)) { for (s.set('j', <0-1>); s.get('j') <= s.get('i'); s.set('j', s.get('j') + 1)) { t.forward(); @@ -1225,7 +1227,7 @@ public class Main { }, if1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <7-9>; s.set('i', s.get('i') + 1)) { t.forward(); if (s.get('i') % 3 === 2) { @@ -1250,7 +1252,7 @@ public class Main { }, if2: { instrumented: ` -const t = new Turtle(<1-2>, <1-2>); +const t = new Turtle(<1-2>, <1-2>); // trace t.turnRight(); for (s.set('i', 0); s.get('i') < <6-8>; s.set('i', s.get('i') + 1)) { t.forward(); @@ -1277,7 +1279,7 @@ public class Main { }, if3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <4-5>; s.set('i', s.get('i') + 1)) { t.forward(); if (s.get('i') % 2 === 0) { @@ -1306,7 +1308,7 @@ public class Main { }, if4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <4-5>; s.set('i', s.get('i') + 1)) { t.forward(); if (s.get('i') % 3 === 0) { @@ -1335,7 +1337,7 @@ public class Main { }, if5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <3-4>; s.set('i', s.get('i') + 1)) { for (s.set('j', 0); s.get('j') < <2-3>; s.set('j', s.get('j') + 1)) { if (s.get('j') % 2 === 0) { @@ -1371,7 +1373,7 @@ public class Main { }, elseIf1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <4-6>; s.set('i', s.get('i') + 1)) { if (s.get('i') < 2) { t.forward(); @@ -1401,7 +1403,7 @@ public class Main { }, elseIf2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <5-7>; s.set('i', s.get('i') + 1)) { if (s.get('i') % 4 === 0) { t.forward(); @@ -1435,7 +1437,7 @@ public class Main { }, elseIf3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 7; s.set('i', s.get('i') + 1)) { if (s.get('i') < 2) { t.forward(); @@ -1469,7 +1471,7 @@ public class Main { }, elseIf4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <6-7>; s.set('i', s.get('i') + 1)) { if (s.get('i') % 5 === 0) { t.forward(); @@ -1503,7 +1505,7 @@ public class Main { }, elseIf5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <7-8>; s.set('i', s.get('i') + 1)) { if (s.get('i') % 5 === 1) { t.turnRight(); @@ -1533,7 +1535,7 @@ public class Main { }, switch1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <5-7>; s.set('i', s.get('i') + 1)) { switch (s.get('i')) { case 0: case 1: @@ -1566,7 +1568,7 @@ public class Main { }, switch2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <5-7>; s.set('i', s.get('i') + 1)) { switch (s.get('i') % 4) { case 1: @@ -1599,7 +1601,7 @@ public class Main { }, switch3: { instrumented: ` -const t = new Turtle(2, 2); +const t = new Turtle(2, 2); // trace for (s.set('i', 0); s.get('i') < <6-7>; s.set('i', s.get('i') + 1)) { switch (s.get('i')) { case 0: @@ -1646,7 +1648,7 @@ public class Main { }, switch4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <7-8>; s.set('i', s.get('i') + 1)) { switch (s.get('i') % 5) { case 0: @@ -1689,7 +1691,7 @@ public class Main { }, switch5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <9-10>; s.set('i', s.get('i') + 1)) { switch (s.get('i') % 6) { case 0: @@ -1734,7 +1736,7 @@ public class Main { }, break1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace while (true) { if (!t.canMoveForward()) break; t.forward(); @@ -1754,7 +1756,7 @@ public class Main { }, break2: { instrumented: ` -const t = new Turtle(<3-4>,<3-4>); +const t = new Turtle(<3-4>,<3-4>); // trace while (true) { if (!t.canMoveForward()) break; t.forward(); @@ -1784,7 +1786,7 @@ public class Main { }, break3: { instrumented: ` -const t = new Turtle(<4-6>, <4-6>); +const t = new Turtle(<4-6>, <4-6>); // trace for (s.set('i', 0); s.get('i') < 4; s.set('i', s.get('i') + 1)) { while (true) { t.forward(); @@ -1811,7 +1813,7 @@ public class Main { }, break4: { instrumented: ` -const t = new Turtle(<3-5>, <3-5>); +const t = new Turtle(<3-5>, <3-5>); // trace for (s.set('i', 0); s.get('i') < 3; s.set('i', s.get('i') + 1)) { while (true) { if (!t.canMoveForward()) break; @@ -1838,7 +1840,7 @@ public class Main { }, break5: { instrumented: ` -const t = new Turtle(<4-6>, <4-6>); +const t = new Turtle(<4-6>, <4-6>); // trace for (s.set('i', 0); s.get('i') < 3; s.set('i', s.get('i') + 1)) { for (s.set('j', 0); s.get('j') < 6; s.set('j', s.get('j') + 1)) { if (!t.canMoveForward()) break; @@ -1876,7 +1878,7 @@ public class Main { }, continue1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <3-5>; s.set('i', s.get('i') + 1)) { if (s.get('i') == 0) { continue; @@ -1901,7 +1903,7 @@ public class Main { }, continue2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <5-7>; s.set('i', s.get('i') + 1)) { if (s.get('i') % <2-3> == 1) { t.turnRight(); @@ -1928,7 +1930,7 @@ public class Main { }, continue3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 2; s.set('i', s.get('i') + 1)) { for (s.set('j', s.get('i') * 4); s.get('j') < 8; s.set('j', s.get('j') + 1)) { if (s.get('j') % 4 == 1) { @@ -1964,7 +1966,7 @@ public class Main { }, continue4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 3; s.set('i', s.get('i') + 1)) { for (s.set('j', s.get('i')); s.get('j') < 6; s.set('j', s.get('j') + 1)) { if (s.get('j') % 2 == 0) { @@ -2000,7 +2002,7 @@ public class Main { }, continue5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 3; s.set('i', s.get('i') + 1)) { for (s.set('j', s.get('i') + 1); s.get('j') < 6; s.set('j', s.get('j') + 1)) { if (s.get('j') % 4 == 0) { @@ -2038,7 +2040,7 @@ public class Main { }, method1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(forwardTwoSteps, 't')(t); t.turnRight(); call(threeStepsForward, 't')(t); @@ -2076,7 +2078,7 @@ public class Main { }, method2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(forwardTwoSteps, 't')(t); call(turnAround, 't')(t); t.forward(); @@ -2095,7 +2097,7 @@ function turnAround(t) { java: ` public class Main { public static void main(String[] args) { - Turtle t = new Turtle(); // sid: + Turtle t = new Turtle(); // sid 二歩前に進める(t); // caller 後ろを向く(t); // caller t.前に進む(); // sid @@ -2113,7 +2115,7 @@ public class Main { }, method3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(forwardGivenSteps, 't', 'n')(t, <3-4>); t.turnRight(); call(forwardGivenSteps, 't', 'n')(t, 2); @@ -2144,7 +2146,7 @@ public class Main { }, method4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(forwardTwoSteps, 't')(t); t.turnRight(); call(forwardFourSteps, 't')(t); @@ -2180,7 +2182,7 @@ public class Main { }, method5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(drawSquare, 't')(t); t.backward(); t.backward(); @@ -2214,7 +2216,7 @@ public class Main { }, return1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('x', call(double, 'a')(<2-3>)); call(forwardGivenSteps, 't', 'n')(t, s.get('x')); @@ -2249,7 +2251,7 @@ public class Main { }, return2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('x', call(double, 'a')(2)); call(forwardGivenSteps, 't', 'n')(t, s.get('x')); t.turnRight(); @@ -2290,7 +2292,7 @@ public class Main { }, return3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace call(forwardGivenSteps, 't', 'n')(t, call(add, 'a', 'b')(1, 1)); call(forwardGivenSteps, 't', 'n')(t, call(add, 'a', 'b')(1, 2)); @@ -2325,7 +2327,7 @@ public class Main { }, return4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < <3-4>; s.set('i', s.get('i') + 1)) { if (call(isEven, 'a')(s.get('i'))) { t.turnRight(); @@ -2376,7 +2378,7 @@ public class Main { }, return5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 3; s.set('i', s.get('i') + 1)) { for (s.set('j', 0); s.get('j') < 3; s.set('j', s.get('j') + 1)) { if (call(isEqual, 'a', 'b')(s.get('i'), s.get('j'))) @@ -2425,7 +2427,7 @@ public class Main { array1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('arr', [2, <1-2>, <1-2>]); for (s.set('i', 0); s.get('i') < s.get('arr').length; s.set('i', s.get('i') + 1)) { call(forwardGivenSteps, 't', 'n')(t, s.get('arr')[s.get('i')]); @@ -2460,7 +2462,7 @@ public class Main { }, array2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('arr', [<4-5>, <3-4>, <3-4>]); for (s.set('i', 0); s.get('i') < s.get('arr').length; s.set('i', s.get('i') + 1)) { call(forwardGivenSteps, 't', 'n')(t, s.get('arr')[s.get('i')]); @@ -2495,7 +2497,7 @@ public class Main { }, array3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('arr', [0, 1, 0, 2, 0]); for (s.set('i', 0); s.get('i') < s.get('arr').length; s.set('i', s.get('i') + 1)) { switch (s.get('arr')[s.get('i')]) { @@ -2530,7 +2532,7 @@ public class Main { }, array4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('arr', [0, 1, 0, 2, 0]); for (const cmd of [0, 1, 0, 2, 0]) { s.set('cmd', cmd); @@ -2566,7 +2568,7 @@ public class Main { }, array5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('arr', [0, 1, 0, 2, 0, 3, 0]); s.set('steps', 1); for (const cmd of [0, 1, 0, 2, 0, 3, 0]) { @@ -2615,7 +2617,7 @@ public class Main { }, string1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('s', 'frflf'); for (s.set('i', 0); s.get('i') < s.get('s').length; s.set('i', s.get('i') + 1)) { switch (s.get('s').charAt(s.get('i'))) { @@ -2650,7 +2652,7 @@ public class Main { }, string2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('s', 'ffbrfl'); for (s.set('i', 0); s.get('i') < s.get('s').length; s.set('i', s.get('i') + 1)) { switch (s.get('s').charAt(s.get('i'))) { @@ -2689,7 +2691,7 @@ public class Main { }, string3: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('s', 'frflf'); for (const ch of 'frflf') { s.set('ch', ch); @@ -2725,7 +2727,7 @@ public class Main { }, string4: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('cmds', ['ri', 'aa', 'fo']); for (const cmd of ['ri', 'aa', 'fo']) { s.set('cmd', cmd); @@ -2760,7 +2762,7 @@ public class Main { }, string5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace s.set('cmds', ['ri', 'add', 'fo', 'add', 'le', 'fo', 'fo']); s.set('x', 0); for (const cmd of ['ri', 'add', 'fo', 'add', 'le', 'fo', 'fo']) { @@ -2816,8 +2818,8 @@ public class Main { }, oop1: { instrumented: ` -const t1 = new Turtle(1, 1); -const t2 = new Turtle(3, 3); +const t1 = new Turtle(1, 1); // trace +const t2 = new Turtle(3, 3); // trace t1.forward(); t2.forward(); t1.forward(); @@ -2837,21 +2839,23 @@ public class Main { `.trim(), }, oop2: { + // 上のコードから下にあるクラスを参照するためには、main()関数を定義しないといけない。 instrumented: ` function main() { - const m = new MyTurtle(0, 0, 2); - m.forward(); + const m = call(MyTurtle, 'x', 'y', 'speed')(0, 0, 2); + call(m.forward.bind(m))(); } class MyTurtle { constructor(x, y, speed) { - this.speed = speed; - this.c = new Turtle(x, y); + this.speed = speed; // trace + this.c = new Turtle(x, y); // trace } forward() { - for (let i = 0; i < this.speed; i++) { + for (s.set('i', 0); s.get('i') < this.speed; s.set('i', s.get('i') + 1)) { this.c.forward(); } + delete s.vars['i']; } } @@ -2860,8 +2864,8 @@ main(); java: ` public class Main { public static void main(String[] args) { - MyTurtle m = new MyTurtle(0, 0, 2); // sid - m.forward(); // sid + MyTurtle m = new MyTurtle(0, 0, 2); // caller + m.forward(); // caller } } @@ -2880,21 +2884,160 @@ class MyTurtle { } `.trim(), }, - oop3: { + // ----------- 初級プログラミングⅡ 第1回 ここから ----------- + // ----------- 初級プログラミングⅡ 第1回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第2回 ここから ----------- + // ----------- 初級プログラミングⅡ 第2回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第3回 ここから ----------- + // ----------- 初級プログラミングⅡ 第3回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第4回 ここから ----------- + static2: { + // グローバル変数を扱う際は、 `myGlobal` という名前のオブジェクトを使うこと。 + // 上のコードから下にあるクラスを参照するためには、main()関数を定義しないといけない。 instrumented: ` -const t = new Turtle(); -`, +myGlobal.Settings = { speed: 3 }; + +function main() { + const t1 = call(MyTurtle)(); // trace + call(t1.moveForward.bind(t1))(); + myGlobal.Settings.speed = 2; // trace + const t2 = call(MyTurtle)(); // trace + call(t1.moveForward.bind(t1))(); + call(t2.moveForward.bind(t2))(); +} + +class MyTurtle { + constructor() { + this.t = new Turtle(); + } + moveForward() { + for (s.set('i', 0); s.get('i') < myGlobal.Settings.speed; s.set('i', s.get('i') + 1)) { + this.t.forward(); + } + delete s.vars['i']; + } +} + +main(); +`.trim(), java: ` public class Main { public static void main(String[] args) { - Turtle c = new Turtle(); // sid + MyTurtle t1 = new MyTurtle(); // caller // sid + t1.moveForward(); // caller + Settings.speed = 2; // sid + MyTurtle t2 = new MyTurtle(); // caller // sid + t1.moveForward(); // caller + t2.moveForward(); // caller } } -`, + +class Settings { + static public int speed = 3; +} + +class MyTurtle { + private Turtle t = new Turtle(); + + void moveForward(Turtle t) { + for (int i = 0; i < Settings.speed; i++) { // sid + t.前に進む(); // sid + } + } +} +`.trim(), + }, + // ----------- 初級プログラミングⅡ 第4回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第5回 ここから ----------- + polymorphism1: { + instrumented: ` +function main() { + const ts = [call(MyTurtle, 'x', 'y')(0, 0), call(FastTurtle, 'p')(1)]; // trace + for (s.set('i', 0); s.get('i') < ts.length; s.set('i', s.get('i') + 1)) { + call(ts[s.get('i')].drawLine.bind(ts[s.get('i')]))(); + } +} + +class MyTurtle { + constructor(x, y) { + this.t = new Turtle(x, y); + } + drawLine() { + for (s.set('i', 0); s.get('i') < this.length(); s.set('i', s.get('i') + 1)) { + this.t.forward(); + } + delete s.vars['i']; + } + length() { + return 2; + } +} + +class FastTurtle extends MyTurtle { + constructor(p) { + super(p, p); + } + length() { + return 3; + } +} + +main(); +`.trim(), + java: ` +public class Main { + public static void main(String[] args) { + MyTurtle[] ts = { + new MyTurtle(0, 0), new FastTurtle(1) }; // caller // sid + for (int i = 0; i < ts.length; i++) { + ts[i].drawLine(); // caller + } + } +} + +class MyTurtle { + Turtle t; + + MyTurtle(int x, int y) { + this.t = new Turtle(x, y); + } + void drawLine() { + for (int i = 0; i < this.length(); i++) { // sid + this.t.前に進む(); // sid + } + } + int length() { + return 2; + } +} + +class FastTurtle extends MyTurtle { + FastTurtle(int p) { + super(p, p); + } + @Override int length() { + return 3; + } +} +`.trim(), }, + // ----------- 初級プログラミングⅡ 第5回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第6回 ここから ----------- + // ----------- 初級プログラミングⅡ 第6回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第7回 ここから ----------- + // ----------- 初級プログラミングⅡ 第7回 ここまで ----------- + + // ----------- 初級プログラミングⅡ 第8回 ここから ----------- + // ----------- 初級プログラミングⅡ 第8回 ここまで ----------- test1: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.forward(); @@ -2913,7 +3056,7 @@ public class Main { }, test2: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace for (s.set('i', 0); s.get('i') < 2; s.set('i', s.get('i') + 1)) { t.forward(); t.forward(); @@ -2968,13 +3111,13 @@ public class Main { }, test4: { instrumented: ` -const t1 = new Turtle(); +const t1 = new Turtle(); // trace t1.forward(); t1.turnRight(); s.set('i', 0); t1.forward(); -const t2 = new Turtle(2, 3, 'G'); +const t2 = new Turtle(2, 3, 'G'); // trace t2.forward(); s.set('foo', 'あいうえお'); s.set('bar', <1-100>); @@ -3004,7 +3147,7 @@ public class Main { }, test5: { instrumented: ` -const t = new Turtle(); +const t = new Turtle(); // trace t.forward(); t.forward(); t.turnRight(); diff --git a/src/problems/traceProgram.ts b/src/problems/traceProgram.ts index 784699ac..d785da31 100644 --- a/src/problems/traceProgram.ts +++ b/src/problems/traceProgram.ts @@ -4,11 +4,12 @@ import { TURTLE_GRAPHICS_DEFAULT_COLOR as DEFAULT_COLOR, TURTLE_GRAPHICS_EMPTY_COLOR as EMPTY_COLOR, } from '../constants'; -import type { CellColor, ColorChar } from '../types'; import type { InstantiatedProblem } from './instantiateProblem'; import type { LanguageId } from './problemData'; +import type { CellColor, ColorChar } from '@/types'; + export interface TurtleTrace { x: number; y: number; @@ -21,6 +22,7 @@ export interface TurtleTrace { export interface TraceItem { depth: number; sid: number; + /** caller id のスタック。 `// caller` のある行に caller id が付与される。 */ callStack: number[]; vars: TraceItemVariable; turtles: TurtleTrace[]; @@ -47,6 +49,7 @@ export const colorToChar = Object.fromEntries( ) as Record; export function traceProgram( + this: unknown, instrumented: string, rawDisplayProgram: string, languageId: LanguageId @@ -59,37 +62,17 @@ export function traceProgram( throw new Error('Instrumented program MUST NOT contain variable declarations.'); } } - - let statementId = 1; - let callerId = 1; - const modifiedCodeLines = []; - for (const line of instrumented.split('\n')) { - let statementReplaced = false; - let callReplaced = false; - const newLine = line - .replace(/for\s*\(([^;]*);\s*([^;]*);/, (_, init, cond) => `for (${init}; checkForCond(${cond}, ${statementId});`) - .replaceAll( - /(new\s+Turtle|\.set|\.forward|\.backward|\.turnRight|\.turnLeft)\(([^\n;]*)\)(;|\)\s*{)/g, - (_, newOrMethod, args, tail) => { - statementReplaced = true; - const delimiter = args === '' ? '' : ', '; - return `${newOrMethod}(${statementId}${delimiter}${args})${tail}`; - } - ) - .replaceAll('call(', (_) => { - callReplaced = true; - return `call(${callerId}, `; - }); - if (statementReplaced) statementId++; - if (callReplaced) callerId++; - modifiedCodeLines.push(newLine); - } + const modifiedCodeLines = modifyCode(instrumented); const modifiedCode = modifiedCodeLines.join('\n'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const thisPropNames = Object.keys((this as any) || {}); // 無理に難読化する必要はないが、コードの文量を減らす意識を持つ。 const executableCode = ` +let myGlobal = {}; const trace = []; const turtles = []; const callStack = []; +const thisPropNames = ${JSON.stringify(thisPropNames)}; let s; class Scope { constructor(parent) { @@ -102,9 +85,9 @@ class Scope { } throw new Error(\`\${varName} is not defined: \${JSON.stringify(s)}\`); } - set(sid, varName, value) { + set(sid, self, varName, value) { this.vars[varName] = typeof value === 'number' ? Math.floor(value) : value; - addTrace(sid); + addTrace(sid, self); } enterNewScope(params) { s = new Scope(this); @@ -131,16 +114,15 @@ const dx = [0, 1, 0, -1]; const dy = [1, 0, -1, 0]; const board = Array.from({length: ${GRID_ROWS}}, () => Array.from({length: ${GRID_COLUMNS}}, () => '${EMPTY_COLOR}')); class Turtle { - constructor(sid, x = 0, y = 0, color = '${DEFAULT_COLOR}') { + constructor(x = 0, y = 0, color = '${DEFAULT_COLOR}') { this.x = x; this.y = y; this.color = color; this.dir = 'N'; board[this.y][this.x] = this.color; turtles.push(this); - addTrace(sid); } - forward(sid) { + forward(sid, self) { const index = dirs.indexOf(this.dir); this.x += dx[index]; this.y += dy[index]; @@ -148,9 +130,9 @@ class Turtle { throw new Error(\`Out of bounds: (\${this.x}, \${this.y})\`); } board[this.y][this.x] = this.color; - addTrace(sid); + addTrace(sid, self); } - backward(sid) { + backward(sid, self) { const index = dirs.indexOf(this.dir); this.x -= dx[index]; this.y -= dy[index]; @@ -158,7 +140,7 @@ class Turtle { throw new Error(\`Out of bounds: (\${this.x}, \${this.y})\`); } board[this.y][this.x] = this.color; - addTrace(sid); + addTrace(sid, self); } canMoveForward() { const index = dirs.indexOf(this.dir); @@ -166,17 +148,34 @@ class Turtle { const ny = this.y + dy[index]; return nx >= 0 && nx < ${GRID_COLUMNS} && ny >= 0 && ny < ${GRID_ROWS}; } - turnRight(sid) { + turnRight(sid, self) { this.dir = dirs[(dirs.indexOf(this.dir) + 1) % 4]; - addTrace(sid); + addTrace(sid, self); } - turnLeft(sid) { + turnLeft(sid, self) { this.dir = dirs[(dirs.indexOf(this.dir) + 3) % 4]; - addTrace(sid); + addTrace(sid, self); } } -function addTrace(sid) { - trace.push({depth: s.getDepth(), sid, callStack: [...callStack], turtles: turtles.map(t => ({...t})), vars: {...s.vars}, board: board.map(r => r.join('')).join('\\n')}); +function addTrace(sid, self) { + const vars = {...s.vars, ...myGlobal}; + if (self && self !== globalThis) { + vars['this'] = {...self}; + for (const name of thisPropNames) delete vars['this'][name]; + } + flattenObjects(vars); + trace.push({depth: s.getDepth(), sid, callStack: [...callStack], turtles: turtles.map(t => ({...t})), vars, board: board.map(r => r.join('')).join('\\n')}); +} +function flattenObjects(obj) { + for (const [key, value] of Object.entries(obj)) { + if (value instanceof Turtle) delete obj[key]; + else if (value && typeof value === 'object' && !Array.isArray(value)) { + delete obj[key]; + for (const [nestKey, nestValue] of Object.entries(value)) { + if (!(nestValue instanceof Turtle)) obj[\`\${key}.\${nestKey}\`] = nestValue; + } + } + } } function checkForCond(cond, sid) { if (!cond && trace.at(-1).sid === sid) { @@ -187,18 +186,21 @@ function checkForCond(cond, sid) { function call(cid, f, ...argNames) { return (...argValues) => { if (argNames.length !== argValues.length) { - throw new Error(\`Expected \${argNames.length} arguments, got \${argValues.length}.\`); + throw new Error(\`Expected \${argNames.length} arguments (\${argNames}), got \${argValues.length} (\${argValues}). Fix call() in instrumented code.\`); } try { callStack.push(cid); s.enterNewScope(argNames.map((n, i) => [n, argValues[i]]).filter(([n, v]) => !(v instanceof Turtle))); - return f(...argValues); + return isClass(f) ? new f(...argValues) : f(...argValues); } finally { callStack.pop(); s.leaveScope(); } }; } +function isClass(obj) { + return typeof obj === 'function' && /^class\\s/.test(obj.toString()); +} trace.push({depth: 0, sid: 0, callStack: [], turtles: [], vars: {}, board: board.map(r => r.join('')).join('\\n')}); s = new Scope(); ${modifiedCode.trim()} @@ -236,9 +238,43 @@ ${modifiedCode.trim()} return { languageId, displayProgram: refinedLines.join('\n'), + executableCode, traceItems: trace, sidToLineIndex, callerIdToLineIndex, finalVars, }; } + +function modifyCode(instrumented: string): string[] { + const modifiedCodeLines = []; + let statementId = 1; + let callerId = 1; + for (const line of instrumented.split('\n')) { + let statementReplaced = false; + let callReplaced = false; + const newLine = line + // Python向けに最後のループの処理かどうかを判定するために checkForCond を挿入する。 + .replace(/for\s*\(([^;]*);\s*([^;]*);/, (_, init, cond) => `for (${init}; checkForCond(${cond}, ${statementId});`) + .replaceAll( + /(\.set|\.forward|\.backward|\.turnRight|\.turnLeft)\(([^\n;]*)\)(;|\)\s*{)/g, + (_, newOrMethod, args, tail) => { + statementReplaced = true; + const delimiter = args === '' ? '' : ', '; + return `${newOrMethod}(${statementId}, this${delimiter}${args})${tail}`; + } + ) + .replace(/\s*\/\/\s*trace\s*/, (_) => { + statementReplaced = true; + return `addTrace(${statementId}, this);`; + }) + .replaceAll('call(', (_) => { + callReplaced = true; + return `call(${callerId}, `; + }); + if (statementReplaced) statementId++; + if (callReplaced) callerId++; + modifiedCodeLines.push(newLine); + } + return modifiedCodeLines; +} diff --git a/test.js b/test.js new file mode 100644 index 00000000..632d0197 --- /dev/null +++ b/test.js @@ -0,0 +1,175 @@ +const trace = []; +const turtles = []; +const callStack = []; +const thisPropNames = ['charToColor', 'colorToChar', 'traceProgram']; +let s; +class Scope { + constructor(parent) { + this.parent = parent; + this.vars = {}; + } + get(varName) { + if (this.vars[varName] !== undefined) { + return this.vars[varName]; + } + throw new Error(`${varName} is not defined: ${JSON.stringify(s)}`); + } + set(sid, self, varName, value) { + this.vars[varName] = typeof value === 'number' ? Math.floor(value) : value; + addTrace(sid, self); + } + enterNewScope(params) { + s = new Scope(this); + for (const [k, v] of params) { + s.vars[k] = v; + } + } + leaveScope() { + if (!this.parent) throw new Error(); + s = this.parent; + } + getDepth() { + let depth = 0; + let currentScope = this; + while (currentScope.parent) { + depth++; + currentScope = currentScope.parent; + } + return depth; + } +} +const dirs = ['N', 'E', 'S', 'W']; +const dx = [0, 1, 0, -1]; +const dy = [1, 0, -1, 0]; +const board = Array.from({ length: 7 }, () => Array.from({ length: 7 }, () => '.')); +class Turtle { + constructor(x = 0, y = 0, color = '#') { + this.x = x; + this.y = y; + this.color = color; + this.dir = 'N'; + board[this.y][this.x] = this.color; + turtles.push(this); + } + forward(sid, self) { + const index = dirs.indexOf(this.dir); + this.x += dx[index]; + this.y += dy[index]; + if (this.x < 0 || 7 <= this.x || this.y < 0 || 7 <= this.y) { + throw new Error(`Out of bounds: (${this.x}, ${this.y})`); + } + board[this.y][this.x] = this.color; + addTrace(sid, self); + } + backward(sid, self) { + const index = dirs.indexOf(this.dir); + this.x -= dx[index]; + this.y -= dy[index]; + if (this.x < 0 || 7 <= this.x || this.y < 0 || 7 <= this.y) { + throw new Error(`Out of bounds: (${this.x}, ${this.y})`); + } + board[this.y][this.x] = this.color; + addTrace(sid, self); + } + canMoveForward() { + const index = dirs.indexOf(this.dir); + const nx = this.x + dx[index]; + const ny = this.y + dy[index]; + return nx >= 0 && nx < 7 && ny >= 0 && ny < 7; + } + turnRight(sid, self) { + this.dir = dirs[(dirs.indexOf(this.dir) + 1) % 4]; + addTrace(sid, self); + } + turnLeft(sid, self) { + this.dir = dirs[(dirs.indexOf(this.dir) + 3) % 4]; + addTrace(sid, self); + } +} +function addTrace(sid, self) { + const vars = { ...s.vars, ...(typeof myGlobal !== 'undefined' && myGlobal) }; + if (self && self !== globalThis) { + vars['this'] = { ...self }; + for (const name of thisPropNames) delete vars['this'][name]; + } + flattenObjects(vars); + trace.push({ + depth: s.getDepth(), + sid, + callStack: [...callStack], + turtles: turtles.map((t) => ({ ...t })), + vars, + board: board.map((r) => r.join('')).join('\n'), + }); +} +function flattenObjects(obj) { + for (const [key, value] of Object.entries(obj)) { + if (value instanceof Turtle) delete obj[key]; + else if (value && typeof value === 'object' && !Array.isArray(value)) { + delete obj[key]; + for (const [nestKey, nestValue] of Object.entries(value)) { + if (!(nestValue instanceof Turtle)) obj[`${key}.${nestKey}`] = nestValue; + } + } + } +} +function checkForCond(cond, sid) { + if (!cond && trace.at(-1).sid === sid) { + trace.at(-1).last = true; + } + return cond; +} +function call(cid, f, ...argNames) { + return (...argValues) => { + if (argNames.length !== argValues.length) { + throw new Error( + `Expected ${argNames.length} arguments (${argNames}), got ${argValues.length} (${argValues}). Fix call() in instrumented code.` + ); + } + try { + callStack.push(cid); + s.enterNewScope(argNames.map((n, i) => [n, argValues[i]]).filter(([n, v]) => !(v instanceof Turtle))); + return isClass(f) ? new f(...argValues) : f(...argValues); + } finally { + callStack.pop(); + s.leaveScope(); + } + }; +} +function isClass(obj) { + return typeof obj === 'function' && /^class\s/.test(obj.toString()); +} +trace.push({ depth: 0, sid: 0, callStack: [], turtles: [], vars: {}, board: board.map((r) => r.join('')).join('\n') }); +s = new Scope(); +myGlobal = { Settings: { speed: 3 } }; + +function main() { + const t1 = call(1, MyTurtle)(); + addTrace(1, this); + call(2, t1.moveForward.bind(t1))(); + myGlobal.Settings.speed = 2; + addTrace(2, this); + const t2 = call(3, MyTurtle)(); + addTrace(3, this); + call(4, t1.moveForward.bind(t1))(); + call(5, t2.moveForward.bind(t2))(); +} + +class MyTurtle { + constructor() { + this.t = new Turtle(); + } + moveForward() { + for ( + s.set(4, this, 'i', 0); + checkForCond(s.get('i') < myGlobal.Settings.speed, 4); + s.set(4, this, 'i', s.get('i') + 1) + ) { + this.t.forward(5, this); + } + delete s.vars['i']; + } +} + +main(); +console.log({ trace, finalVars: { ...s.vars } }); diff --git a/tests/unit/traceProgram.test.ts b/tests/unit/traceProgram.test.ts index 316412d2..48513805 100644 --- a/tests/unit/traceProgram.test.ts +++ b/tests/unit/traceProgram.test.ts @@ -595,6 +595,19 @@ public class Straight { } ); +test('Trace a specific program', () => { + const problem = instantiateProblem('static2', 'java', ''); + if (!problem) throw new Error('Failed to generate problem.'); + + const { displayProgram, executableCode, sidToLineIndex, traceItems } = problem; + console.info('---------------------- displayProgram ----------------------\n', displayProgram); + console.info('------------------------------------------------------------\n'); + console.info('---------------------- executableCode ----------------------\n', executableCode); + console.info('------------------------------------------------------------\n'); + console.info('sidToLineIndex:', JSON.stringify(Object.fromEntries(sidToLineIndex), undefined, 2)); + console.info('traceItems:', JSON.stringify(traceItems, undefined, 2)); +}); + /** * テストに失敗した際に、WebStorm上で期待値との差異を確認しやすくするために、文字列化しておく。 */
変数名変数/式