Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Services can trace programs. #48

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 105 additions & 22 deletions src/app/lib/solveProblem.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CharacterVariable, History, SolveProblemResult, Variable } from '../../types';

import { Board as BoardClass } from './Board';
import type { Character } from './Character';
import { Character as CharacterClass } from './Character';

export function parseProgram(program: string): string[] {
Expand All @@ -10,6 +11,90 @@ export function parseProgram(program: string): string[] {
.filter((line) => line !== '');
}

export function instrumentCode(code: string): string {
// generateCodeからコードを受け取って、トレースできるコードを返す関数
const sidRegex = /\/\* (\d+) \*\//;
const assignmentRegex = /([\w.]+)(?: = |\+\+|--)/;
const modifiedCodeLines: string[] = [];
const lines = code.split('\n');

for (const line of lines) {
const trimmedLine = line.trimStart();
let modifiedCodeLine = trimmedLine;
const statementMatch = trimmedLine.match(sidRegex);
if (statementMatch) {
const sid = statementMatch[1];

// ' = 'を含むline
const assignmentMatch = line.match(assignmentRegex);
if (assignmentMatch) {
const variableName = assignmentMatch[1].trim();

modifiedCodeLine = modifiedCodeLine.replace(sidRegex, `log(${sid}, '${variableName}', ${variableName});`);
}
}
modifiedCodeLines.push(modifiedCodeLine);
}
return modifiedCodeLines.join('\n');
}

type Trace = {
sid: number;
variableName: string;
variableValue: Character | string;
};

export function traceCode(code: string): Trace[] {
const Character = CharacterClass; // eslint-disable-line
const Board = BoardClass; // eslint-disable-line
const codeForTracing = `
const traceList = [];

${code}

function log(sid, variableName, variableValue) {
if (typeof value === 'number' && (!isFinite(value) || isNaN(value) || (modulo && value < 0))) {
process.exit(1);
}
traceList.push({
sid,
variableName,
variableValue
});
}

JSON.stringify(traceList);
`.replaceAll(/\s+/g, ' ');
const ret: Trace[] = JSON.parse(eval(codeForTracing));
Comment on lines +66 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここのタイミングでJSON化とパースを経由しているため、variableValue はクラスのインスタンスではなく、ただのオブジェクトになっていますね。

return ret;
}

function traceToVariablesList(traceList: Trace[]): (CharacterVariable | Variable)[][] {
const lineCount = traceList.at(-1)?.sid ?? 0;
const variablesList: (CharacterVariable | Variable)[][] = [];
let index = 0;
for (let i = 0; i < lineCount; i++) {
const variables = variablesList.at(-1) ?? ([] as (CharacterVariable | Variable)[]);
while (traceList[index].sid <= i) {
const { variableName, variableValue } = traceList[index];
const variablesIndex = variables.findIndex((variable) => variable.name === variableName);
if (variablesIndex === -1) {
if (variableValue instanceof CharacterClass) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variableValue.constructor.name -> Object になるので True にならなさそうです。
オブジェクトのまま検証する場合、ユーザー定義型ガード?のようなもので型を使って検証すればうまくいくかもしれません。

e.g.

const isCharacter = (value: unknown): value is Character => {
  const characterValue = value as Character;
  return typeof characterValue.name === 'string' &&
    typeof characterValue.x === 'number' &&
    typeof characterValue.y === 'number' &&
    typeof characterValue.direction === 'string' &&
    typeof characterValue.color === 'string' &&
    typeof characterValue.penDown === 'boolean';
}

console.log('character');
variables.push({ name: variableName, value: variableValue } as CharacterVariable);
} else {
variables.push({ name: variableName, value: variableValue } as Variable);
}
} else {
variables[variablesIndex].value = variableValue;
}
index++;
}
variablesList.push([...variables]);
}
return variablesList;
}

export function executeEval(command: string): (CharacterVariable | Variable)[] {
const Character = CharacterClass; // eslint-disable-line
const Board = BoardClass; // eslint-disable-line
Expand Down Expand Up @@ -52,38 +137,36 @@ export function extractVariableNames(command: string): string[] {
}

export function solveProblem(program: string): SolveProblemResult {
const commands = parseProgram(program);
const board = new BoardClass();
const histories: History[] = [{ step: 0, characterVariables: [], board, otherVariables: [] }];

for (let i = 0; i < commands.length; i++) {
if (i < commands.length) {
let mergedCommand = '';
const traceList = traceCode(instrumentCode(program));
const variablesList = traceToVariablesList(traceList);

for (let j = 0; j <= i; j++) {
mergedCommand += commands[j];
}
for (const variables of variablesList) {
console.log('variables', variables);
const characterVariables = selectCharacterVariables(variables);
const otherVariables = selectOtherVariables(variables);
console.log('characterVariables', characterVariables);
console.log('otherVariables', otherVariables);

const variables = executeEval(mergedCommand);
const characterVariables = selectCharacterVariables(variables);
const otherVariables = selectOtherVariables(variables);
const board = new BoardClass();
for (const history of histories) {
if (!history.characterVariables) continue;

const board = new BoardClass();
for (const history of histories) {
if (!history.characterVariables) continue;

for (const character of history.characterVariables) {
board.updateGrid(character.value);
}
for (const character of history.characterVariables) {
board.updateGrid(character.value);
}
for (const character of characterVariables) {
board.updateGrid(character.value as CharacterClass);
}

histories.push({ step: histories.length + 1, characterVariables, board, otherVariables });
}
for (const character of characterVariables) {
board.updateGrid(character.value as CharacterClass);
}

histories.push({ step: histories.length + 1, characterVariables, board, otherVariables });
}

console.log(histories);

const result: SolveProblemResult = {
characterVariables: histories?.at(-1)?.characterVariables,
otherVariables: histories?.at(-1)?.otherVariables,
Expand Down
26 changes: 13 additions & 13 deletions src/problems/problemData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ export const courseIdToProgramIdLists: Record<CourseId, ProgramId[][]> = {
export function generateProgram(programId: ProgramId, languageId: LanguageId): string {
// TODO(exKAZUu): 問題IDに紐づくプログラム(テンプレート)を取得して、乱数を使って具体的なプログラムを生成する。
return (
`const bear = new Character();
bear.moveForward();
bear.turnLeft();
bear.upPen();
let i = 0;
bear.moveForward();
const turtle = new Character({x: 3, y: 1, color: 'green'});
turtle.moveForward();
const foo = 'あいうえお';
var bar = 123;
i = i + 1;
turtle.moveForward();
turtle.moveForward();` || programIdToLanguageIdToProgram[programId][languageId]
`const bear = new Character(); /* 1 */
bear.moveForward(); /* 2 */
bear.turnLeft(); /* 3 */
bear.upPen(); /* 4 */
let i = 0; /* 5 */
bear.moveForward(); /* 6 */
const turtle = new Character({x: 3, y: 1, color: 'green'}); /* 7 */
turtle.moveForward(); /* 8 */
const foo = 'あいうえお'; /* 9 */
var bar = 123; /* 10 */
i = i + 1; /* 11 */
turtle.moveForward(); /* 12 */
turtle.moveForward(); /* 13 */` || programIdToLanguageIdToProgram[programId][languageId]
);
}

Expand Down
Loading