{ }codelper
変数の宣言スコープリストのコピーP0360

値を入れる前に使った変数

小さな言語で書かれた n 行のプログラムを、実行せずに検査する。
この言語の変数は宣言しただけでは値がなく、値を入れた後でなければ使えない。
命令は8種類で、1行に1つずつある。
- var x: 変数 x を宣言する。
まだ値はない。
- set x: x に値を入れる。
- use x: x の値を使う。
- if: ブロックを開く。
このブロックは実行されることも、飛ばされることもある。
- else: if 部分を閉じて else 部分を開く。
else のある if は、2つの部分のうちちょうど1つだけが実行される。
- loop: ブロックを開く。
このブロックは 0 回以上繰り返される。
- end: いちばん内側で開いている if または loop を閉じる。
- stop: プログラムをその場で終える。
if 部分、else 部分、loop ブロックの中で宣言した変数は、その部分が閉じるときに消える。
set x や use x で x が宣言されていなければ undeclared エラーである。
まだ宣言していないか、すでに消えた場合である。
use x で x が宣言されていても、その行まで来られる道のうち、x を宣言してから一度も値を入れずに来る道が1つでもあれば unassigned エラーである。
道を考えるとき、if と loop の条件は分からないものとする。
if はどちらにも進めて、loop は何回でも回れる。
エラーになった行は何もしなかったものとして、検査を続ける。
エラーを上から順に出力し、最後の行にエラーの数を出力せよ。

入力
1行目に n (1 ≤ n ≤ 1,000) が与えられる。
続く n 行に命令が1つずつ与えられる。
命令と名前は空白1つで区切る。
名前は英小文字 1 ~ 10 文字で、命令の単語とは異なる。
if と loop は end と対応がとれている。
else はいちばん内側で開いているものが if 部分のときだけ現れ、if 1つにつき多くても1つである。
今宣言されている名前をもう一度宣言することはない。
消えた名前はもう一度宣言できる。
stop は if 部分または else 部分の最後の命令としてだけ現れ、1つの if の2つの部分が両方とも stop で終わることはない
出力
エラーごとに1行で、行番号、エラー名(undeclared または unassigned)、変数名を空白1つずつで区切って出力する。
行番号は命令だけを数え、最初の命令が 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 言語の解答

解答は、今見えている変数ごとに「どの道から来ても値がある」という印を持ち、ブロックを開くときにその印のコピーを取っておきます。
罠その1:else のない if の中で入れた値は、ブロックの後では信用できません。
飛ばされることがあるので、ブロックを開く前の印に戻します。
2つ目の例の5行目がそうです。
罠その2:if と else があるなら、両方で入れなければなりません。
1つ目の例の a は if 部分でしか入れていないので、13行目はエラーです。
罠その3:else 部分は、if 部分が終わった状態ではなく、if に出会う前の状態から始まります。
1つ目の例の8行目がそうです。
罠その4:loop は1回も回らないことがあります。
2つ目の例の z は loop の中で入れていますが22行目はエラーで、1周目にはまだ値がないので19行目もエラーです。
罠その5:stop で終わった側はブロックの後に来られないので、除いて考えます。
2つ目の例の y は if 部分でしか入れていませんが、else 部分が stop で終わるので16行目はエラーではありません。
罠その6:消えた変数と同じ名前でも、宣言し直せば値のない新しい変数です。
1つ目の例の11行目がそうです。
Java・C#・C の解答は配列の場所を使い回すので、宣言のときに必ず印を消します。
罠その7:ブロックが閉じたら、その中の変数を消さなければなりません。
消さないと、1つ目の例の14行目が undeclared ではなく unassigned になります。
罠その8:印はコピーで取っておく必要があります。
Python・JS・クレイコンで = で入れると同じリストを指すので、ブロックの中で変えた内容が取っておいた側にも見えます。
コピーは Python filled[:]、JS・TS slice()、Java Arrays.copyOf、C# Array.Copy、C memcpy、クレイコン 部分リスト で取ります。
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 になります。
クレイコンにも宣言は別になく、最初に値を入れる行が宣言なので、値のない変数はできません。
ブロックの中で初めて入れた名前をブロックの外で使うと、実行する前に、まだ作っていない名前だと知らせてくれます。
見えている名前を内側のブロックでもう一度宣言するのは、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]; // 두 길 모두에서 넣었어야 한다            }        }        블록종류.位置で取り除く(끝);        블록표시.位置で取り除く(끝);        블록전.位置で取り除く(끝);        블록첫부분.位置で取り除く(끝);        블록첫멈춤.位置で取り除く(끝);    }    멈춤 = 지금멈춤;}コンソール.書く(오류);