{ }codelper
声明变量作用域复制列表P0360

还没放入值就使用的变量

检查一个用小语言写成的 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 结束
输出
每个错误输出一行,依次是行号、错误名(undeclared 或 unassigned)、变量名,用一个空格隔开。
行号只数命令,第一条命令是 1。
最后一行输出错误数
样例 1 · stdin
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
输入(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 sys  def 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 1000 typedef 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]; // 두 길 모두에서 넣었어야 한다            }        }        블록종류.按位置移除(끝);        블록표시.按位置移除(끝);        블록전.按位置移除(끝);        블록첫부분.按位置移除(끝);        블록첫멈춤.按位置移除(끝);    }    멈춤 = 지금멈춤;}控制台.输出(오류);