From c4641aee19d5858eeb8ecdc6cf90d6c04325cfea Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 17 Sep 2026 17:04:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../competitio_login/assets/gongan-beian.png | Bin 0 -> 11143 bytes scratch-gui/competitio_login/index.html | 2 +- scratch-gui/scripts/test-cpp-regressions.cjs | 78 +++--------------- .../src/components/stage-cpp/cpp-parser.js | 46 ++++------- .../components/stage-cpp/stage-cpp-clang.jsx | 22 ++--- scratch-gui/src/playground/aboutus.ejs | 2 +- scratch-gui/src/playground/ai.ejs | 2 +- scratch-gui/src/playground/competition.ejs | 2 +- .../src/playground/competition_list.ejs | 2 +- .../src/playground/competition_sum.ejs | 2 +- scratch-gui/src/playground/config.js | 40 ++++----- scratch-gui/src/playground/home.ejs | 2 +- scratch-gui/src/playground/login-new.ejs | 2 +- scratch-gui/src/playground/login.ejs | 2 +- .../src/playground/programming-learning.ejs | 3 +- scratch-gui/src/playground/signup-new.ejs | 2 +- scratch-gui/src/playground/welcome.ejs | 2 +- scratch-gui/static/images/gongan-beian.png | Bin 0 -> 11143 bytes 18 files changed, 65 insertions(+), 146 deletions(-) create mode 100644 scratch-gui/competitio_login/assets/gongan-beian.png create mode 100644 scratch-gui/static/images/gongan-beian.png diff --git a/scratch-gui/competitio_login/assets/gongan-beian.png b/scratch-gui/competitio_login/assets/gongan-beian.png new file mode 100644 index 0000000000000000000000000000000000000000..6fe667f73fa1ae8c37d33b5bece6f602a861f7bf GIT binary patch literal 11143 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 literal 0 HcmV?d00001 diff --git a/scratch-gui/competitio_login/index.html b/scratch-gui/competitio_login/index.html index 02d883f4..c6ae702e 100644 --- a/scratch-gui/competitio_login/index.html +++ b/scratch-gui/competitio_login/index.html @@ -384,7 +384,7 @@ diff --git a/scratch-gui/scripts/test-cpp-regressions.cjs b/scratch-gui/scripts/test-cpp-regressions.cjs index fc5ed6e5..10d9f837 100644 --- a/scratch-gui/scripts/test-cpp-regressions.cjs +++ b/scratch-gui/scripts/test-cpp-regressions.cjs @@ -1,4 +1,4 @@ -// Regression tests for the editor's real interpreter and output callback. +// Regression tests for variable scopes in the editor interpreter. // Run: node scripts/test-cpp-regressions.cjs const assert = require('assert'); const fs = require('fs'); @@ -32,19 +32,13 @@ int main() { } return 0; }`; -// The third screenshot reports cout without supplying a cout example. -// Add observable output to the supplied program to check both fixes together. -const customerEmailWithOutput = customerEmailCode.replace(' playerMove(i);', - ' cout << "i=" << i << endl;\n playerMove(i);'); - async function run(code, expected, configure) { const moves = []; const turns = []; - const output = []; context.window = {isScriptRunOver: false, cppGameAPI: { playerMove: async n => moves.push(n), turnLeft: async n => turns.push(n), - cmdPrint: text => output.push(text) + cmdPrint: () => {} }}; const executor = new CppCodeExecutor(code, null); if (configure) configure(executor, context.window); @@ -53,7 +47,6 @@ async function run(code, expected, configure) { await executor.run(); if (expected.moves) assert.deepStrictEqual(moves, expected.moves); if (expected.turns) assert.deepStrictEqual(turns, expected.turns); - if (expected.output) assert.deepStrictEqual(output, expected.output); assert.strictEqual(executor.variables, executor.globalVariables, 'scope must unwind after execution'); assert.strictEqual(executor.callStack.length, 0); return executor; @@ -61,12 +54,10 @@ async function run(code, expected, configure) { const cases = [ ['customer email complete program (screenshots 1 and 2)', customerEmailCode, {moves: [0], turns: [-1]}], - ['customer email with variable output (screenshot 3)', customerEmailWithOutput, {moves: [0], turns: [-1], output: ['i=0\n']}], ['nested loops', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}], ['loop initializer restores shadowed value', main('int i = 9;\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [9]}], ['loop assignment updates existing value', main('int i = 9;\nfor (i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [2]}], ['block declaration versus assignment', main('int n = 1;\nif (true) {\nint n = 7;\nplayerMove(n);\n}\nif (true) {\nn = 3;\n}\nplayerMove(n);'), {moves: [7, 3]}], - ['string block shadowing', main('string msg = "outer";\nif (true) {\nstring msg = "C++";\ncout << msg;\n}\ncout << msg;'), {output: ['C++', 'outer']}], ['for body scope per iteration', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nplayerMove(x);\nint x = 2;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}], ['while body scope per iteration', main('int x = 9;\nint i = 0;\nwhile (i < 2) {\nplayerMove(x);\nint x = 2;\ni++;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}], ['function locals', 'void f() {\nint n = 7;\nplayerMove(n);\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [7, 2]}], @@ -75,14 +66,6 @@ const cases = [ ['early void return unwinds scope', 'void f() {\nint n = 8;\nreturn;\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [2]}], ['continue unwinds body scope', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nint x = 2;\ncontinue;\n}\nplayerMove(x);'), {moves: [9]}], ['break does not increment', main('int i = 0;\nfor (i = 0; i < 2; i++) {\nbreak;\n}\nplayerMove(i);'), {moves: [0]}], - ['cout with equals', main('int i = 7;\ncout << "i=" << i << endl;'), {output: ['i=7\n']}], - ['cout with operator text', main('cout << "value=42 C++ -- +=" << endl;'), {output: ['value=42 C++ -- +=\n']}], - ['cout with game API text', main('cout << "playerMove(2)" << endl;'), {moves: [], output: ['playerMove(2)\n']}], - ['qualified cout', main('std::cout << "Hello" << std::endl;'), {output: ['Hello\n']}], - ['cout postfix and prefix', main('int i = 0;\ncout << i++ << endl;\ncout << ++i << endl;\ncout << i-- << endl;\ncout << --i << endl;'), {output: ['0\n', '2\n', '2\n', '0\n']}], - ['cout arithmetic', main('int i = 3;\ncout << "result=" << (i * 2 + 1) << endl;'), {output: ['result=7\n']}], - ['escaped quotes and stream delimiters', main(String.raw`cout << "a\"< { @@ -90,6 +73,15 @@ const cases = [ try { await run(code, expected); } catch (error) {error.message = `${name}: ${error.message}`; throw error;} console.log(`PASS ${name}`); } + const strings = new CppCodeExecutor('', null); + await strings.executeSimpleStatement('string msg = "outer";'); + await strings.withVariableScope(async () => { + await strings.executeSimpleStatement('string msg = "inner";'); + assert.strictEqual(strings.variables.get('msg'), 'inner'); + }); + assert.strictEqual(strings.variables.get('msg'), 'outer'); + console.log('PASS string block shadowing'); + // Seed the interpreter's global environment to verify lexical lookup and writes. const globalExecutor = await run('void f() {\ng += 1;\nplayerMove(g);\n}\n' + main('int g = 9;\nf();\nplayerMove(g);'), {moves: [2, 9]}, executor => executor.variables.set('g', 1)); assert.strictEqual(globalExecutor.variables.get('g'), 2); @@ -112,51 +104,5 @@ const cases = [ console.log(`PASS scope cleanup on ${stop ? 'stop' : 'error'}`); } - // Extract the actual React component callback, avoiding a game-engine dependency. - const uiPath = path.resolve(__dirname, '../src/components/stage-cpp/stage-cpp-clang.jsx'); - const uiSource = fs.readFileSync(uiPath, 'utf8'); - const ast = babel.parseSync(uiSource, {configFile: false, babelrc: false, parserOpts: {plugins: ['jsx']}}); - let callback; - function visit(node) { - if (!node || typeof node !== 'object') return; - if (node.type === 'ObjectProperty' && node.key.name === 'cmdPrint') callback = node.value; - for (const value of Object.values(node)) { - if (Array.isArray(value)) value.forEach(visit); - else if (value && typeof value === 'object') visit(value); - } - } - visit(ast); - assert(callback, 'cmdPrint callback exists'); - for (const engineState of ['absent', 'ready', 'throwing']) { - let displayed = ''; - const sent = []; - const window = {}; - if (engineState !== 'absent') window.unityInstance = {SendMessage: (...args) => { - if (engineState === 'throwing') throw new Error('engine unavailable'); - sent.push(args); - }}; - const cmdPrint = vm.runInNewContext(`(${uiSource.slice(callback.start, callback.end)})`, { - window, console: silentConsole, setCompilationOutput: update => {displayed = update(displayed);} - }); - cmdPrint('A'); - cmdPrint('B\n'); - assert.strictEqual(displayed, 'AB\n'); - assert.strictEqual(window.cmd_print_text, 'AB\n'); - if (engineState === 'ready') assert.deepStrictEqual(sent[1], ['UIMain', 'SetText', 'AB\n']); - console.log(`PASS output callback with ${engineState} engine`); - - // Connect the real interpreter to the real UI callback, including repeated runs. - for (let attempt = 0; attempt < 2; attempt++) { - displayed = ''; - window.cmd_print_text = ''; - await run(customerEmailWithOutput, {moves: [0], turns: [-1]}, (executor, runtimeWindow) => { - runtimeWindow.cppGameAPI.cmdPrint = cmdPrint; - }); - assert.strictEqual(displayed, 'i=0\n'); - assert.strictEqual(window.cmd_print_text, 'i=0\n'); - if (engineState === 'ready') assert.deepStrictEqual(sent[sent.length - 1], ['UIMain', 'SetText', 'i=0\n']); - } - console.log(`PASS customer program through output callback with ${engineState} engine`); - } - console.log(`${cases.length + 9} regression checks passed`); + console.log(`${cases.length + 4} regression checks passed`); })().catch(error => {console.error(error); process.exitCode = 1;}); diff --git a/scratch-gui/src/components/stage-cpp/cpp-parser.js b/scratch-gui/src/components/stage-cpp/cpp-parser.js index c4b95f5c..9b10e683 100644 --- a/scratch-gui/src/components/stage-cpp/cpp-parser.js +++ b/scratch-gui/src/components/stage-cpp/cpp-parser.js @@ -1373,12 +1373,6 @@ export class CppCodeExecutor { async executeSimpleStatement(content) { debugLog(`🔍 执行简单语句: ${content}`); - // 先识别输出语句,避免把字符串里的 =、++、游戏函数名当作代码执行。 - if (/^\s*(?:std\s*::\s*)?cout\s*< escapes[char]); - } - - // 按字符串和括号边界分割输出流。 + // 智能分割cout的各个部分 splitCoutParts(content) { const parts = []; let current = ''; @@ -2275,12 +2266,6 @@ export class CppCodeExecutor { for (let i = 0; i < content.length; i++) { const char = content[i]; const nextChar = content[i + 1]; - - if (inString && char === '\\' && nextChar !== undefined) { - current += char + nextChar; - i++; - continue; - } if (!inString && inParens === 0 && char === '<' && nextChar === '<') { // 遇到 << 分隔符 @@ -2321,11 +2306,6 @@ export class CppCodeExecutor { expr = expr.trim().replace(/;$/, ''); debugLog(`🔍 计算表达式: ${expr}`); - // 字符串中的 ++、-- 和函数名都是文本,不参与表达式运算。 - if (/^("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')$/.test(expr)) { - return this.decodeStringLiteral(expr); - } - // 布尔常量 if (expr === 'true') { debugLog(`🔍 布尔常量: true = 1`); @@ -2380,7 +2360,7 @@ export class CppCodeExecutor { const newValue = currentValue + 1; this.variables.set(varName, newValue); console.log(`🔄 自增操作: ${expr} (${currentValue} -> ${newValue})`); - return expr.startsWith('++') ? newValue : currentValue; + return newValue; } if (expr.includes('--')) { @@ -2398,7 +2378,7 @@ export class CppCodeExecutor { const newValue = currentValue - 1; this.variables.set(varName, newValue); console.log(`🔄 自减操作: ${expr} (${currentValue} -> ${newValue})`); - return expr.startsWith('--') ? newValue : currentValue; + return newValue; } // 游戏API函数调用 @@ -2428,6 +2408,12 @@ export class CppCodeExecutor { return position; } + // 整个表达式是字符串字面量时才当字符串;含引号的 API 调用走后面的算术解析 + if ((expr.startsWith('"') && expr.endsWith('"')) || + (expr.startsWith("'") && expr.endsWith("'"))) { + return expr.slice(1, -1); + } + return this.parseComplexExpression(expr); } diff --git a/scratch-gui/src/components/stage-cpp/stage-cpp-clang.jsx b/scratch-gui/src/components/stage-cpp/stage-cpp-clang.jsx index 835398d8..4aa02668 100644 --- a/scratch-gui/src/components/stage-cpp/stage-cpp-clang.jsx +++ b/scratch-gui/src/components/stage-cpp/stage-cpp-clang.jsx @@ -645,21 +645,17 @@ const StageWrapperCppClang = () => { textStr = String(text); } - // cout 自己决定何时换行,连续输出应保持在同一行。 - window.cmd_print_text += textStr; + window.cmd_print_text += textStr + '\n'; const lines = window.cmd_print_text.split('\n'); if (lines.length > 20) { window.cmd_print_text = lines.slice(lines.length - 20).join('\n'); } - setCompilationOutput(prev => prev + textStr); if (window.unityInstance) { - try { - window.unityInstance.SendMessage("UIMain", "SetText", window.cmd_print_text); - } catch (error) { - console.warn('游戏画面输出失败,文本仍可在运行输出中查看', error); - } + window.unityInstance.SendMessage("UIMain", "SetText", window.cmd_print_text); } + + setCompilationOutput(prev => prev + textStr + "\n"); }, player_data: (direction) => { @@ -799,7 +795,6 @@ const StageWrapperCppClang = () => { setCompilationOutput(prev => prev + "语法检查通过!\n"); // 重置停止标志 - window.cmd_print_text = ''; window.isScriptRunOver = false; setIsRunning(true); setOverlayActive(true); @@ -974,7 +969,7 @@ const StageWrapperCppClang = () => {
-

运行输出

+

编译信息

diff --git a/scratch-gui/src/playground/aboutus.ejs b/scratch-gui/src/playground/aboutus.ejs index cac2ff1b..4d291b16 100644 --- a/scratch-gui/src/playground/aboutus.ejs +++ b/scratch-gui/src/playground/aboutus.ejs @@ -1358,7 +1358,7 @@