检查一个用小语言写成的 n 行程序,但不运行它。 这种语言的变量只声明时没有值,放入值之后才能使用。 命令有八种,每行一条。 - var x:声明变量 x。 此时还没有值。 - set x:把值放入 x。 - use x:使用 x 的值。 - if:打开一个块。 这个块可能执行,也可能被跳过。 - else:关闭 if 部分并打开 else 部分。 带 else 的 if,两个部分中恰好执行一个。 - loop:打开一个块。 这个块重复 0 次或更多次。 - end:关闭最内层打开着的 if 或 loop。 - stop:让程序就地结束。 在 if 部分、else 部分、loop 块里声明的变量,在该部分关闭时消失。 在 set x 或 use x 处,如果 x 没有被声明,就是 undeclared 错误。 这是还没声明或已经消失的情况。 在 use x 处,即使 x 已声明,只要能到达这一行的路径中有一条在声明 x 之后一次也没有给它放入值,就是 unassigned 错误。 考虑路径时,把 if 和 loop 的条件当作未知。 if 可以走任意一边,loop 可以转任意多次。 出错的那一行当作什么也没做,继续检查。 从上到下依次输出错误,并在最后一行输出错误数。
输入
第一行给出 n (1 ≤ n ≤ 1,000)。 接下来的 n 行每行给出一条命令。 命令和名字之间用一个空格隔开。 名字是 1 ~ 10 个英文小写字母,且不与命令单词相同。 if 和 loop 都与 end 配对。 else 只在最内层打开着的是 if 部分时出现,每个 if 最多有一个 else。 不会再次声明当前已声明的名字。 已经消失的名字可以再次声明。 stop 只作为 if 部分或 else 部分的最后一条命令出现,同一个 if 的两个部分不会都以 stop 结束
14
var a
var b
if
set a
var t
set t
else
use a
set b
var t
use t
end
use a
use t
stdout
8 unassigned a
11 unassigned t
13 unassigned a
14 undeclared t
4
样例 2 · stdin
22
var c
if
set c
end
use c
var x
var y
if
set x
set y
else
set x
stop
end
use x
use y
var z
loop
use z
set z
end
use z
stdout
5 unassigned c
19 unassigned z
22 unassigned z
3
main.py
错误列表00F8 ↓ · ⇧F8 ↑
输入(stdin)
运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。
$ 就绪
请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。
其他语言怎么写?查看各语言题解 · 8 种语言题解
解答给当前可见的每个变量保存一个“无论从哪条路来都有值”的标记,并在打开块时把这些标记复制一份存起来。 陷阱一:在没有 else 的 if 里放入的值,在块之后不可信。 块可能被跳过,所以要恢复到打开块之前的标记。 第二个例子的第 5 行就是这样。 陷阱二:有 if 和 else 时,两边都必须放入。 第一个例子的 a 只在 if 部分放入,所以第 13 行是错误。 陷阱三:else 部分不是从 if 部分结束时的状态开始,而是从遇到 if 之前的状态开始。 第一个例子的第 8 行就是这样。 陷阱四:loop 可能一次也不转。 第二个例子的 z 是在 loop 里放入的,但第 22 行是错误;而第一圈时还没有值,所以第 19 行也是错误。 陷阱五:以 stop 结束的那一边到不了块的后面,所以要排除它。 第二个例子的 y 只在 if 部分放入,但 else 部分以 stop 结束,所以第 16 行不是错误。 陷阱六:即使和已消失的变量同名,重新声明后就是没有值的新变量。 第一个例子的第 11 行就是这样。 Java、C#、C 的解答会重复使用数组的位置,所以声明时一定要清掉标记。 陷阱七:块关闭时必须删掉其中的变量。 不删的话,第一个例子的第 14 行会变成 unassigned 而不是 undeclared。 陷阱八:标记必须复制后再保存。 在 Python、JS、Craykon 里用 = 存入的是同一个列表,块里的修改在保存的那一份里也看得到。 复制用 Python filled[:]、JS·TS slice()、Java Arrays.copyOf、C# Array.Copy、C memcpy、Craykon 子列表。 C++ 的 vector 只用 = 就会整个复制。 这种检查正是真正的编译器在做的事。 Java 在 int x; if (c) { x = 1; } 之后使用 x 会报错 variable x might not have been initialized,把 if 换成 while 也一样。 C# 报 CS0165 错误,TS 报 TS2454 错误。 Java 和 C# 在两边都放入值,或者 else 一边以 return 结束时会放行。 C 和 C++ 对同样的代码不报错就编译通过,加上 -Wall 之类的选项才给警告。 那个变量的值是不确定的,读取它在大多数情况下是未定义行为。 JS 在 let x; 之后使用 x 得到 undefined。 Python 没有单独的声明,在函数里 if 被跳过后使用那个变量,运行时会出现 UnboundLocalError。 Craykon 也没有单独的声明,第一次放入值的那一行就是声明,所以不会出现没有值的变量。 在块里第一次放入的名字如果在块外使用,运行之前就会提示这是还没创建的名字。 在内层块里再次声明可见的名字,在 Java 和 C# 里是编译错误。 这和本题的约定相同。 C 允许这样做,内层的会遮住外层的。
Pythonmain.py · 56 行
import sysdef main(): lines = sys.stdin.read().split("\n") n = int(lines[0]) names = [] # 지금 보이는 변수 이름 (선언한 차례대로) filled = [] # names와 짝: 어느 길로 와도 값이 들어 있는가 frames = [] # 열려 있는 블록: [종류, 열 때의 변수 수, 열 때의 filled 사본, if 부분의 결과, if 부분이 stop으로 끝났나] stopped = False # 바로 앞 명령이 stop인가 out = [] for i in range(1, n + 1): parts = lines[i].split() cmd = parts[0] if cmd == "var": names.append(parts[1]) filled.append(False) # 선언만으로는 값이 없다 elif cmd == "set" or cmd == "use": x = parts[1] if x not in names: out.append(f"{i} undeclared {x}") elif cmd == "set": filled[names.index(x)] = True elif not filled[names.index(x)]: out.append(f"{i} unassigned {x}") elif cmd == "if" or cmd == "loop": # filled[:] 로 사본을 뜬다. 그냥 filled를 넣으면 같은 리스트를 가리킨다 frames.append([cmd, len(names), filled[:], None, False]) elif cmd == "stop": stopped = True continue # stopped를 아래에서 지우지 않도록 elif cmd == "else": f = frames[-1] del names[f[1]:] # if 부분에서 선언한 변수는 여기서 사라진다 del filled[f[1]:] f[0] = "else" f[3] = filled f[4] = stopped filled = f[2][:] # else 부분은 if를 만나기 전의 상태에서 시작한다 else: # end f = frames.pop() del names[f[1]:] del filled[f[1]:] if f[0] != "else": filled = f[2] # 한 번도 안 돌 수 있으니 블록 전의 상태로 elif stopped: filled = f[3] # else 부분은 여기까지 오지 못한다: if 부분만 본다 elif not f[4]: filled = [a and b for a, b in zip(f[3], filled)] # 두 길 모두에서 넣었어야 한다 # if 부분이 stop으로 끝났으면 else 부분의 결과를 그대로 쓴다 stopped = False out.append(str(len(out))) print("\n".join(out))main()
JavaScriptmain.js · 47 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const names = []; // 지금 보이는 변수 이름 (선언한 차례대로)let filled = []; // names와 짝: 어느 길로 와도 값이 들어 있는가const frames = []; // 열려 있는 블록let stopped = false; // 바로 앞 명령이 stop인가const out = [];for (let i = 1; i <= n; i++) { const [cmd, x] = lines[i].trim().split(" "); let nowStopped = false; if (cmd === "var") { names.push(x); filled.push(false); // 선언만으로는 값이 없다 } else if (cmd === "set" || cmd === "use") { const at = names.indexOf(x); if (at === -1) out.push(`${i} undeclared ${x}`); else if (cmd === "set") filled[at] = true; else if (!filled[at]) out.push(`${i} unassigned ${x}`); } else if (cmd === "if" || cmd === "loop") { // slice()로 사본을 뜬다. filled를 그대로 넣으면 같은 배열을 가리킨다 frames.push({ kind: cmd, mark: names.length, before: filled.slice(), first: null, firstStopped: false }); } else if (cmd === "stop") { nowStopped = true; } else if (cmd === "else") { const f = frames[frames.length - 1]; names.length = f.mark; // if 부분에서 선언한 변수는 여기서 사라진다 filled.length = f.mark; f.kind = "else"; f.first = filled; f.firstStopped = stopped; filled = f.before.slice(); // else 부분은 if를 만나기 전의 상태에서 시작한다 } else { const f = frames.pop(); names.length = f.mark; filled.length = f.mark; if (f.kind !== "else") { filled = f.before; // 한 번도 안 돌 수 있으니 블록 전의 상태로 } else if (stopped) { filled = f.first; // else 부분은 여기까지 오지 못한다: if 부분만 본다 } else if (!f.firstStopped) { filled = filled.map((v, k) => v && f.first[k]); // 두 길 모두에서 넣었어야 한다 } } stopped = nowStopped;}out.push(String(out.length));console.log(out.join("\n"));
TypeScriptmain.ts · 56 行
const lines: string[] = require("fs").readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]);interface Frame { kind: string; mark: number; // 블록을 열 때 보이던 변수 수 before: boolean[]; // 블록을 열 때의 상태 사본 first: boolean[]; // if 부분이 끝났을 때의 상태 firstStopped: boolean;}const names: string[] = []; // 지금 보이는 변수 이름 (선언한 차례대로)let filled: boolean[] = []; // names와 짝: 어느 길로 와도 값이 들어 있는가const frames: Frame[] = [];let stopped = false; // 바로 앞 명령이 stop인가const out: string[] = [];for (let i = 1; i <= n; i++) { const [cmd, x] = lines[i].trim().split(" "); let nowStopped = false; if (cmd === "var") { names.push(x); filled.push(false); // 선언만으로는 값이 없다 } else if (cmd === "set" || cmd === "use") { const at = names.indexOf(x); if (at === -1) out.push(`${i} undeclared ${x}`); else if (cmd === "set") filled[at] = true; else if (!filled[at]) out.push(`${i} unassigned ${x}`); } else if (cmd === "if" || cmd === "loop") { // slice()로 사본을 뜬다. filled를 그대로 넣으면 같은 배열을 가리킨다 frames.push({ kind: cmd, mark: names.length, before: filled.slice(), first: [], firstStopped: false }); } else if (cmd === "stop") { nowStopped = true; } else if (cmd === "else") { const f = frames[frames.length - 1]; names.length = f.mark; // if 부분에서 선언한 변수는 여기서 사라진다 filled.length = f.mark; f.kind = "else"; f.first = filled; f.firstStopped = stopped; filled = f.before.slice(); // else 부분은 if를 만나기 전의 상태에서 시작한다 } else { const f = frames.pop()!; names.length = f.mark; filled.length = f.mark; if (f.kind !== "else") { filled = f.before; // 한 번도 안 돌 수 있으니 블록 전의 상태로 } else if (stopped) { filled = f.first; // else 부분은 여기까지 오지 못한다: if 부분만 본다 } else if (!f.firstStopped) { filled = filled.map((v, k) => v && f.first[k]); // 두 길 모두에서 넣었어야 한다 } } stopped = nowStopped;}out.push(String(out.length));console.log(out.join("\n"));
JavaMain.java · 80 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.Arrays;public class Main { static class Frame { String kind; int mark; // 블록을 열 때 보이던 변수 수 boolean[] before; // 블록을 열 때의 상태 사본 boolean[] first; // if 부분이 끝났을 때의 상태 boolean firstStopped; } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); String[] names = new String[n]; // 지금 보이는 변수 이름 (선언한 차례대로) boolean[] filled = new boolean[n]; // names와 짝: 어느 길로 와도 값이 들어 있는가 int count = 0; // 지금 보이는 변수 수 ArrayList<Frame> frames = new ArrayList<>(); boolean stopped = false; // 바로 앞 명령이 stop인가 StringBuilder out = new StringBuilder(); int errors = 0; for (int i = 1; i <= n; i++) { String[] parts = br.readLine().trim().split(" "); String cmd = parts[0]; boolean nowStopped = false; if (cmd.equals("var")) { names[count] = parts[1]; filled[count] = false; // 선언만으로는 값이 없다. 전에 쓰던 칸일 수 있으니 꼭 지운다 count++; } else if (cmd.equals("set") || cmd.equals("use")) { String x = parts[1]; int at = -1; for (int k = 0; k < count; k++) { if (names[k].equals(x)) at = k; // 문자열은 ==가 아니라 equals로 견준다 } if (at == -1) { out.append(i).append(" undeclared ").append(x).append('\n'); errors++; } else if (cmd.equals("set")) { filled[at] = true; } else if (!filled[at]) { out.append(i).append(" unassigned ").append(x).append('\n'); errors++; } } else if (cmd.equals("if") || cmd.equals("loop")) { Frame f = new Frame(); f.kind = cmd; f.mark = count; f.before = Arrays.copyOf(filled, count); // 사본. filled를 그대로 넣으면 같은 배열을 가리킨다 frames.add(f); } else if (cmd.equals("stop")) { nowStopped = true; } else if (cmd.equals("else")) { Frame f = frames.get(frames.size() - 1); count = f.mark; // if 부분에서 선언한 변수는 여기서 사라진다 f.kind = "else"; f.first = Arrays.copyOf(filled, count); f.firstStopped = stopped; System.arraycopy(f.before, 0, filled, 0, count); // else 부분은 if를 만나기 전의 상태에서 시작한다 } else { Frame f = frames.remove(frames.size() - 1); count = f.mark; if (!f.kind.equals("else")) { System.arraycopy(f.before, 0, filled, 0, count); // 한 번도 안 돌 수 있으니 블록 전의 상태로 } else if (stopped) { System.arraycopy(f.first, 0, filled, 0, count); // else 부분은 여기까지 오지 못한다 } else if (!f.firstStopped) { for (int k = 0; k < count; k++) filled[k] = filled[k] && f.first[k]; // 두 길 모두에서 넣었어야 한다 } } stopped = nowStopped; } out.append(errors).append('\n'); System.out.print(out); }}
C#Main.cs · 98 行
using System;using System.Collections.Generic;using System.Text;class Frame{ public string Kind; public int Mark; // 블록을 열 때 보이던 변수 수 public bool[] Before; // 블록을 열 때의 상태 사본 public bool[] First; // if 부분이 끝났을 때의 상태 public bool FirstStopped;}class Program{ static void Main() { int n = int.Parse(Console.ReadLine()); string[] names = new string[n]; // 지금 보이는 변수 이름 (선언한 차례대로) bool[] filled = new bool[n]; // names와 짝: 어느 길로 와도 값이 들어 있는가 int count = 0; // 지금 보이는 변수 수 var frames = new Stack<Frame>(); bool stopped = false; // 바로 앞 명령이 stop인가 var sb = new StringBuilder(); int errors = 0; for (int i = 1; i <= n; i++) { string[] parts = Console.ReadLine().Trim().Split(' '); string cmd = parts[0]; bool nowStopped = false; if (cmd == "var") { names[count] = parts[1]; filled[count] = false; // 선언만으로는 값이 없다. 전에 쓰던 칸일 수 있으니 꼭 지운다 count++; } else if (cmd == "set" || cmd == "use") { string x = parts[1]; int at = Array.IndexOf(names, x, 0, count); if (at == -1) { sb.Append(i).Append(" undeclared ").Append(x).Append('\n'); errors++; } else if (cmd == "set") { filled[at] = true; } else if (!filled[at]) { sb.Append(i).Append(" unassigned ").Append(x).Append('\n'); errors++; } } else if (cmd == "if" || cmd == "loop") { var f = new Frame { Kind = cmd, Mark = count, Before = new bool[count] }; Array.Copy(filled, f.Before, count); // 사본. filled를 그대로 넣으면 같은 배열을 가리킨다 frames.Push(f); } else if (cmd == "stop") { nowStopped = true; } else if (cmd == "else") { Frame f = frames.Peek(); count = f.Mark; // if 부분에서 선언한 변수는 여기서 사라진다 f.Kind = "else"; f.First = new bool[count]; Array.Copy(filled, f.First, count); f.FirstStopped = stopped; Array.Copy(f.Before, filled, count); // else 부분은 if를 만나기 전의 상태에서 시작한다 } else { Frame f = frames.Pop(); count = f.Mark; if (f.Kind != "else") { Array.Copy(f.Before, filled, count); // 한 번도 안 돌 수 있으니 블록 전의 상태로 } else if (stopped) { Array.Copy(f.First, filled, count); // else 부분은 여기까지 오지 못한다 } else if (!f.FirstStopped) { for (int k = 0; k < count; k++) filled[k] = filled[k] && f.First[k]; // 두 길 모두에서 넣었어야 한다 } } stopped = nowStopped; } sb.Append(errors).Append('\n'); Console.Write(sb); }}
C++main.cpp · 74 行
#include <algorithm>#include <iostream>#include <string>#include <vector>using namespace std;struct Frame { string kind; size_t mark; // 블록을 열 때 보이던 변수 수 vector<bool> before; // 블록을 열 때의 상태 사본 vector<bool> first; // if 부분이 끝났을 때의 상태 bool firstStopped;};int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<string> names; // 지금 보이는 변수 이름 (선언한 차례대로) vector<bool> filled; // names와 짝: 어느 길로 와도 값이 들어 있는가 vector<Frame> frames; bool stopped = false; // 바로 앞 명령이 stop인가 int errors = 0; for (int i = 1; i <= n; i++) { string cmd, x; cin >> cmd; bool nowStopped = false; if (cmd == "var") { cin >> x; names.push_back(x); filled.push_back(false); // 선언만으로는 값이 없다 } else if (cmd == "set" || cmd == "use") { cin >> x; auto it = find(names.begin(), names.end(), x); if (it == names.end()) { cout << i << " undeclared " << x << '\n'; errors++; } else if (cmd == "set") { filled[it - names.begin()] = true; } else if (!filled[it - names.begin()]) { cout << i << " unassigned " << x << '\n'; errors++; } } else if (cmd == "if" || cmd == "loop") { frames.push_back({cmd, names.size(), filled, {}, false}); // vector는 = 로 넣으면 통째로 베껴진다 } else if (cmd == "stop") { nowStopped = true; } else if (cmd == "else") { Frame& f = frames.back(); names.resize(f.mark); // if 부분에서 선언한 변수는 여기서 사라진다 filled.resize(f.mark); f.kind = "else"; f.first = filled; f.firstStopped = stopped; filled = f.before; // else 부분은 if를 만나기 전의 상태에서 시작한다 } else { Frame f = frames.back(); frames.pop_back(); names.resize(f.mark); filled.resize(f.mark); if (f.kind != "else") { filled = f.before; // 한 번도 안 돌 수 있으니 블록 전의 상태로 } else if (stopped) { filled = f.first; // else 부분은 여기까지 오지 못한다 } else if (!f.firstStopped) { for (size_t k = 0; k < f.mark; k++) filled[k] = filled[k] && f.first[k]; // 두 길 모두에서 넣었어야 한다 } } stopped = nowStopped; } cout << errors << '\n'; return 0;}
Cmain.c · 90 行
#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAXN 1000typedef struct { int is_else; /* else 부분까지 왔는가 */ int mark; /* 블록을 열 때 보이던 변수 수 */ char *before; /* 블록을 열 때의 상태 사본 */ char *first; /* if 부분이 끝났을 때의 상태 */ int first_stopped;} Frame;static char names[MAXN][12]; /* 지금 보이는 변수 이름 (선언한 차례대로) */static char filled[MAXN]; /* names와 짝: 어느 길로 와도 값이 들어 있는가 */static Frame frames[MAXN];/* 길이 len인 상태를 새 메모리에 베낀다. 배열은 = 로 베낄 수 없다 */static char *copy_of(const char *src, int len) { char *p = malloc((size_t)len + 1); if (p == NULL) exit(1); memcpy(p, src, (size_t)len); return p;}int main(void) { int n; if (scanf("%d", &n) != 1) return 0; int count = 0, depth = 0, stopped = 0, errors = 0; for (int i = 1; i <= n; i++) { char cmd[8], x[12] = ""; if (scanf("%7s", cmd) != 1) return 0; int now_stopped = 0; int is_var = strcmp(cmd, "var") == 0, is_set = strcmp(cmd, "set") == 0, is_use = strcmp(cmd, "use") == 0; if (is_var || is_set || is_use) { if (scanf("%11s", x) != 1) return 0; } if (is_var) { strcpy(names[count], x); filled[count] = 0; /* 선언만으로는 값이 없다. 전에 쓰던 칸일 수 있으니 꼭 지운다 */ count++; } else if (is_set || is_use) { int at = -1; for (int k = 0; k < count; k++) { if (strcmp(names[k], x) == 0) at = k; /* 문자열은 ==가 아니라 strcmp로 견준다 */ } if (at == -1) { printf("%d undeclared %s\n", i, x); errors++; } else if (is_set) { filled[at] = 1; } else if (!filled[at]) { printf("%d unassigned %s\n", i, x); errors++; } } else if (strcmp(cmd, "if") == 0 || strcmp(cmd, "loop") == 0) { Frame *f = &frames[depth++]; f->is_else = 0; f->mark = count; f->before = copy_of(filled, count); f->first = NULL; f->first_stopped = 0; } else if (strcmp(cmd, "stop") == 0) { now_stopped = 1; } else if (strcmp(cmd, "else") == 0) { Frame *f = &frames[depth - 1]; count = f->mark; /* if 부분에서 선언한 변수는 여기서 사라진다 */ f->is_else = 1; f->first = copy_of(filled, count); f->first_stopped = stopped; memcpy(filled, f->before, (size_t)count); /* else 부분은 if를 만나기 전의 상태에서 시작한다 */ } else { Frame *f = &frames[--depth]; count = f->mark; if (!f->is_else) { memcpy(filled, f->before, (size_t)count); /* 한 번도 안 돌 수 있으니 블록 전의 상태로 */ } else if (stopped) { memcpy(filled, f->first, (size_t)count); /* else 부분은 여기까지 오지 못한다 */ } else if (!f->first_stopped) { for (int k = 0; k < count; k++) filled[k] = filled[k] && f->first[k]; /* 두 길 모두에서 넣었어야 한다 */ } free(f->before); free(f->first); /* free(NULL)은 아무 일도 하지 않는다 */ } stopped = now_stopped; } printf("%d\n", errors); return 0;}
Craykonmain.cray · 107 行
// 목록을 통째로 베낀다. 그냥 = 로 넣으면 같은 목록을 가리킨다베끼기(원본){ 返回 원본.子列表(0, 원본.数量);}// 목록을 앞에서 '남길수'개만 남긴다줄이기(목록, 남길수){ 当 (목록.数量 > 남길수) { 목록.按位置移除(목록.数量 - 1); }}n = 控制台.读行().转数字();이름들 = 新列表(0, ""); // 지금 보이는 변수 이름 (선언한 차례대로)채움 = 新列表(0, 假); // 이름들과 짝: 어느 길로 와도 값이 들어 있는가// 열려 있는 블록마다 한 칸씩블록종류 = 新列表(0, "");블록표시 = 新列表(0, 0); // 블록을 열 때 보이던 변수 수블록전 = 新列表(0, 채움); // 블록을 열 때의 상태 사본블록첫부분 = 新列表(0, 채움); // if 부분이 끝났을 때의 상태블록첫멈춤 = 新列表(0, 假);멈춤 = 假; // 바로 앞 명령이 stop인가오류 = 0;循环 (i = 1; i <= n; i++){ 조각 = 控制台.读行().去空白().分割(" "); 명령 = 조각[0]; 지금멈춤 = 假; 如果 (명령 == "var") { 이름들.添加(조각[1]); 채움.添加(假); // 선언만으로는 값이 없다 } 否则 如果 (명령 == "set" || 명령 == "use") { x = 조각[1]; 자리 = 이름들.查找位置(x); 如果 (자리 == -1) { 控制台.输出(i + " undeclared " + x); 오류 = 오류 + 1; } 否则 如果 (명령 == "set") { 채움[자리] = 真; } 否则 如果 (!채움[자리]) { 控制台.输出(i + " unassigned " + x); 오류 = 오류 + 1; } } 否则 如果 (명령 == "if" || 명령 == "loop") { 블록종류.添加(명령); 블록표시.添加(이름들.数量); 블록전.添加(베끼기(채움)); 블록첫부분.添加(채움); 블록첫멈춤.添加(假); } 否则 如果 (명령 == "stop") { 지금멈춤 = 真; } 否则 如果 (명령 == "else") { 끝 = 블록종류.数量 - 1; 줄이기(이름들, 블록표시[끝]); // if 부분에서 선언한 변수는 여기서 사라진다 줄이기(채움, 블록표시[끝]); 블록종류[끝] = "else"; 블록첫부분[끝] = 채움; 블록첫멈춤[끝] = 멈춤; 채움 = 베끼기(블록전[끝]); // else 부분은 if를 만나기 전의 상태에서 시작한다 } 否则 { 끝 = 블록종류.数量 - 1; 줄이기(이름들, 블록표시[끝]); 줄이기(채움, 블록표시[끝]); 如果 (블록종류[끝] != "else") { 채움 = 블록전[끝]; // 한 번도 안 돌 수 있으니 블록 전의 상태로 } 否则 如果 (멈춤) { 채움 = 블록첫부분[끝]; // else 부분은 여기까지 오지 못한다 } 否则 如果 (!블록첫멈춤[끝]) { 첫부분 = 블록첫부분[끝]; 循环 (k = 0; k < 채움.数量; k++) { 채움[k] = 채움[k] && 첫부분[k]; // 두 길 모두에서 넣었어야 한다 } } 블록종류.按位置移除(끝); 블록표시.按位置移除(끝); 블록전.按位置移除(끝); 블록첫부분.按位置移除(끝); 블록첫멈춤.按位置移除(끝); } 멈춤 = 지금멈춤;}控制台.输出(오류);