1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

共通テスト用プログラミング表記(謎の言語)のインタプリタを使えるサイトを作った話v2

Last updated at Posted at 2024-11-05

本当は「「共通テスト用プログラミング表記」のインタプリタを作った話」という記事を出していたのですが、トラブルがあったので書き直します。

共通テスト用プログラミング表記とは?

作られた目的

共通テスト用プログラミング表記は、共通テストのために作られた擬似言語です(Pythonでいいじゃん)
元々DNCL(共通テスト手順記述標準言語)という別の謎言語が使われていましたが、令和7年度からこの共通テスト用プログラミング表記が使われるようになります。

文法

ざっくり説明すると(詳しくはこのサイトの下の方を参照)、

四則演算
3 + 2
5 ** 2
10 ÷ 3
変数定義
x = a + 5
関数
表示する(x)
整数(x) 
  etc...
外部からの入力
x = 【外部からの入力】
if文に相当
もしx==5ならば:
┃ 処理1
┃ 処理2
そうでなくもしx==1ならば:
┃ 処理3
そうでなければ:
┃ 処理4
┗ 処理5
for文に相当
i を 3 から 0 まで 1 ずつ減らしながら繰り返す:
┗ 処理1
while文に相当
i = 0
i < 3の間繰り返す:
┃ i = i + 1
┗ 処理1

こんな感じで、基本日本語で書く言語になっています。

x = 【外部からの入力】
もし 1 + x == 3 ならば:
┃ 表示する("1 + xは3です")
そうでなくもし 1 + x == 2 ならば:
┃ 表示する("1 + xは2です")
そうでなくもし 1 + x == 4 ならば:
┃ 表示する("1 + xは4です")
そうでなければ:
┗ 表示する("1 + xは2でも3でも4でもありません")

もちろん、例にはありませんが文の中に文を入れることもできます。

作ったもの

作った理由

DNCL(共通テスト手順記述標準言語)を実行するサイトはあったのですが自分が将来受ける共通テストに出るであろう共通テスト用プログラミング表記を実行するサイトは見当たらなかったので作りました。

見た目

最初の画面
スクリーンショット 2024-11-04 15.45.59.png

制御文もできます。
スクリーンショット 2024-11-04 15.47.32.png

プログラム

インタプリタのコードの説明だけします。適当に作ったのでParserのコードとかは汚いです。
読まなくていい人は「最後に」まで飛ばしてください。

Lexer

字句解析器です。

Lexer.py
import re
from typing import List, Tuple

# トークンの種類
TOKEN_SPECIFICATION = [
    ('NUMBER', r'[\+|-]?\d+(\.\d*)?'),          # 整数または浮動小数点数
    ('STRING', r'"(.*)"'),                      # 文字列
    ('ID', r'[A-Za-z_]\w*'),                    # 識別子
    ('OP', r'\*\*|==|!=|<=|>=|([+\-*/%=,<>])'), # 演算子
    ('PARENTHESES', r'[()]'),                   # かっこ
    ('BRACKETS', r'[\[\]]'),                    # 鉤括弧
    ('NEWLINE', r'\r\n|[\r\n]'),                # 改行
    ('SKIP', r'[ \t]+'),                        # スペースとタブ
    ('BLOCK', r'[┃┗]'),                         # 条件分岐とループの塊
    ('OTHERCHARS', r'[\u3040-\u30FF\u4E00-\u9FFF:【】]+'),           # 漢字やひらがななど
]

# エラー処理用の例外クラス
class LexerError(Exception):
    def __init__(self, message):
        super().__init__(message)

# Lexer クラス
class Lexer:
    def __init__(self, code: str):
        self.code = code
        self.line = 1  # 行数の追跡

    def tokenize(self) -> List[Tuple[str, str]]:
        tokens = []
        position = 0
        while position < len(self.code):
            match = None
            for regex_type, regex in TOKEN_SPECIFICATION:
                pattern = re.compile(regex)
                match = pattern.match(self.code, position)
                if match:
                    token_value = match.group(0)
                    if regex_type == 'NEWLINE':
                        self.line += 1  # 改行で行数をインクリメント
                    if regex_type != 'SKIP':  # スキップ以外はトークンに追加
                        tokens.append((regex_type, token_value))
                    position = match.end(0)
                    break
            if not match:
                raise LexerError(f"不明なトークン '{self.code[position]}' at line {self.line}")
        tokens.append(('EOF', ""))
        return tokens

Lexerはすごく楽に書けました(いつも使っているC++に比べて)

Parser

構文解析器です。
謎にエラーが出たので、Parser.pyではなくMyParser.pyにしています。

MyParser.py
from .Expression import Expression

# エラー処理用の例外クラス
class ParserError(Exception):
    def __init__(self, message):
        super().__init__(message)

# Parser クラス
class Parser:
    def __init__(self, tokens: list[tuple[str, str]]):
        self.tokens = tokens
    
    def factor(self, token_num: int) -> tuple[int, Expression]:
        type = self.tokens[token_num][0]
        content = self.tokens[token_num][1]

        if type == 'NUMBER':
            return (token_num + 1, Expression('NUMBER', [int(content)]))
        elif type == 'STRING':
            return (token_num + 1, Expression('STRING', [content]))
        elif type == 'ID':
            if self.check_operator(token_num + 1, ["["]):
                new_token_num, subscript = self.assign(token_num + 2)
                self.token(new_token_num, "]")
                return (new_token_num + 1, Expression('ELM', [Expression('VAR', [content]), subscript]))
            
            return (token_num + 1, Expression('VAR', [content]))
        elif type == 'OTHERCHARS':
            if content == "【外部からの入力】":
                return (token_num + 1, Expression('INPUT', []))

            if self.check_operator(token_num + 1, ["("]):
                params = []
                new_token_num = token_num + 2

                while not self.check_operator(new_token_num, [")"]):
                    new_token_num, expression = self.assign(new_token_num)
                    params.append(expression)

                    if self.check_operator(new_token_num, [")"]):
                        continue
                    if self.check_operator(new_token_num, [","]):
                        new_token_num += 1
                        continue
                    raise ParserError("不明なトークンです。1{0}".format(self.tokens[new_token_num]))
                
                return (new_token_num + 1, Expression('FUNC', [content] + params))
            return (token_num + 1, Expression('VAR', [content]))
        elif content == "[":
                params = []
                new_token_num = token_num + 1

                while not self.check_operator(new_token_num, ["]"]):
                    new_token_num, expression = self.assign(new_token_num)
                    params.append(expression)

                    if self.check_operator(new_token_num, ["]"]):
                        continue
                    if self.check_operator(new_token_num, [","]):
                        new_token_num += 1
                        continue
                    raise ParserError("不明なトークンです。2{0}".format(self.tokens[new_token_num]))
                
                return (new_token_num + 1, Expression('ARRAY', params))
        elif content == "(":
            new_token_num, expression = self.assign(token_num + 1)
            self.token(new_token_num, ")")
            return (new_token_num + 1, expression)
        else:
            raise ParserError("不明なトークンです。3{0}".format(content))
    
    def power(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.factor(token_num)

        if self.check_operator(new_token_num_left, ["**"]):
            new_token_num_right, right = self.power(new_token_num_left + 1)
            return (new_token_num_right, Expression('OP', ["**", left, right]))
        
        return (new_token_num_left, left)
    
    def mlt_or_div(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.power(token_num)

        while self.check_operator(new_token_num_left, ["*", "/", "%"]):
            new_token_num_right, right = self.power(new_token_num_left + 1)
            left = Expression('OP', [self.tokens[new_token_num_left][1], left, right])
            new_token_num_left = new_token_num_right
        
        return (new_token_num_left, left)
    
    def add_or_sub(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.mlt_or_div(token_num)

        while self.check_operator(new_token_num_left, ["+", "-"]):
            new_token_num_right, right = self.mlt_or_div(new_token_num_left + 1)
            left = Expression('OP', [self.tokens[new_token_num_left][1], left, right])
            new_token_num_left = new_token_num_right
        
        return (new_token_num_left, left)
    
    def compare(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.add_or_sub(token_num)

        while self.check_operator(new_token_num_left, ["==", "!=", "<", ">", "<=", ">="]):
            new_token_num_right, right = self.add_or_sub(new_token_num_left + 1)
            left = Expression('OP', [self.tokens[new_token_num_left][1], left, right])
            new_token_num_left = new_token_num_right
        
        return (new_token_num_left, left)
    
    def logicalnot(self, token_num: int) -> tuple[int, Expression]:
        if self.check_operator(token_num, ["not"]):
            new_token_num, child = self.logicalnot(token_num + 1)
            return (new_token_num, Expression('OP', ["not", child]))
        
        new_token_num, child = self.compare(token_num)
        return (new_token_num, child)
    
    def logicaland(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.logicalnot(token_num)

        while self.check_operator(new_token_num_left, ["and"]):
            new_token_num_right, right = self.logicalnot(new_token_num_left + 1)
            left = Expression('OP', ["and", left, right])
            new_token_num_left = new_token_num_right
        
        return (new_token_num_left, left)
    
    def logicalor(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.logicaland(token_num)

        while self.check_operator(new_token_num_left, ["or"]):
            new_token_num_right, right = self.logicaland(new_token_num_left + 1)
            left = Expression('OP', ["or", left, right])
            new_token_num_left = new_token_num_right
        
        return (new_token_num_left, left)
    
    def assign(self, token_num: int) -> tuple[int, Expression]:
        new_token_num_left, left = self.logicalor(token_num)

        if self.check_operator(new_token_num_left, ["="]):
            new_token_num_right, right = self.assign(new_token_num_left + 1)
            return (new_token_num_right, Expression('OP', ["=", left, right]))
        
        return (new_token_num_left, left)
    
    def statement(self, token_num: int) -> tuple[int, Expression]:
        if self.check_operator(token_num, ["もし"]):
            block_list = []
            condition_exps = []

            new_token_num, condition_exp = self.assign(token_num + 1)
            new_token_num = self.token(new_token_num, "ならば:")
            new_token_num = self.token(new_token_num, "\r\n")
            level = self.count_level(new_token_num)
            new_token_num, expressions = self.block(new_token_num, level, True)

            condition_exps.append(condition_exp)
            block_list.append(expressions)

            while self.check_operator(new_token_num, ["そうでなくもし"]):
                new_token_num, condition_exp = self.assign(new_token_num + 1)
                new_token_num = self.token(new_token_num, "ならば:")
                new_token_num = self.token(new_token_num, "\r\n")
                new_token_num, expressions = self.block(new_token_num, level, True)

                condition_exps.append(condition_exp)
                block_list.append(expressions)

            if self.check_operator(new_token_num, ["そうでなければ:"]):
                new_token_num = self.token(new_token_num + 1, "\r\n")
                new_token_num, expressions = self.block(new_token_num, level)

                condition_exps.append(None)
                block_list.append(expressions)

            return new_token_num, Expression('STMT', ["if", condition_exps, block_list])
        
        elif self.check_operator(token_num + 1, [""]):
            var = Expression('VAR', [self.tokens[token_num][1]])
            new_token_num = self.token(token_num + 1, "")
            new_token_num, start_value = self.assign(new_token_num)
            new_token_num = self.token(new_token_num, "から")
            new_token_num, stop_value = self.assign(new_token_num)
            new_token_num = self.token(new_token_num, "まで")
            new_token_num, step_value = self.assign(new_token_num)

            if self.check_operator(new_token_num, ["ずつ増やしながら繰り返す:"]):
                new_token_num = self.token(new_token_num, "ずつ増やしながら繰り返す:")
                new_token_num = self.token(new_token_num, "\r\n")
                level = self.count_level(new_token_num)
                new_token_num, block = self.block(new_token_num, level)

                return new_token_num, Expression('STMT', ["forup", var, start_value, stop_value, step_value, block])
            elif self.check_operator(new_token_num, ["ずつ減らしながら繰り返す:"]):
                new_token_num = self.token(new_token_num, "ずつ減らしながら繰り返す:")
                new_token_num = self.token(new_token_num, "\r\n")
                level = self.count_level(new_token_num)
                new_token_num, block = self.block(new_token_num, level)

                return new_token_num, Expression('STMT', ["fordown", var, start_value, stop_value, step_value, block])
            else:
                raise ParserError("for文が適切に終わっていません。")
            
        new_token_num, expression = self.assign(token_num)
        if self.check_operator(new_token_num, ["の間繰り返す:"]):
            new_token_num = self.token(new_token_num, "の間繰り返す:")
            new_token_num = self.token(new_token_num, "\r\n")
            level = self.count_level(new_token_num)
            new_token_num, block = self.block(new_token_num, level)

            return new_token_num, Expression('STMT', ["while", expression, block])
        elif self.check_operator(new_token_num, ["のすべての値を"]):
            new_token_num = self.token(new_token_num, "のすべての値を")
            new_token_num, val = self.assign(new_token_num)
            new_token_num = self.token(new_token_num, "にする")

            return new_token_num, Expression('STMT', ["resetarray", expression, val])

        return new_token_num, expression
    
    def program(self, token_num: int) -> tuple[int, Expression]:
        new_token_num = token_num
        stmts = []
        while self.tokens[new_token_num][0] != 'EOF':
            new_token_num, stmt = self.statement(new_token_num)
            stmts.append(stmt)
            if self.check_operator(new_token_num, [","]):
                new_token_num = self.token(new_token_num, ",")
            while self.check_operator(new_token_num, ["\r\n"]):
                new_token_num = self.token(new_token_num, "\r\n")
        
        return 0, Expression('PROGRAM', [stmts])
                                                         
    def check_operator(self, token_num: int, operators: list[str]) -> bool:
        if token_num >= len(self.tokens):
            return False

        flag = False
        for operator in operators:
            flag = self.tokens[token_num][1] == operator or flag
            
        return flag
    
    def token(self, token_num: int, token: str) -> int:
        if self.check_operator(token_num, [token]):
            return token_num + 1
        raise ParserError("不明なトークンです。{0}\n正しいトークンは {1}。".format(self.tokens[token_num], token))
    
    def block(self, token_num: int, level: int, iscontinuation: bool = False) -> tuple[int, list[Expression]]:
        new_token_num = token_num
        expressions: list[tuple[int, Expression]] = []
        while True:
            for l in range(level - 1):
                new_token_num = self.token(new_token_num, "")

            if(self.check_operator(new_token_num, [""])):
                new_token_num = self.token(new_token_num, "")
                new_token_num, exp = self.assign(new_token_num)
                expressions.append(exp)
                return (new_token_num, expressions)
            
            try:
                new_token_num = self.token(new_token_num, "")
                new_token_num, stmt = self.statement(new_token_num)
                new_token_num = self.token(new_token_num, "\r\n")
                expressions.append(stmt)
            except ParserError as e:
                if iscontinuation:
                    return new_token_num, expressions
                raise ParserError("文の終わりが見つかりません。")
            
    def count_level(self, token_num: int) -> int:
        level = 0
        while self.check_operator(token_num + level, ["", ""]):
            level += 1
        if level == 0:
            raise ParserError("ブロックが見つかりません。")
        return level
        

基本は普通のプログラミング言語開発と同じです。
block -> 文 -> 式 -> factor

Expression

Parserで生成するプログラムを表すクラスです。

Expression.py
import math
import random

# エラー処理用の例外クラス
class ExpressionError(Exception):
    def __init__(self, message):
        super().__init__(message)

# 変数管理用のクラス
class Environment:
    __variables: dict[str, any] = {}
    
    @staticmethod
    def get(var: str):
        return Environment.__variables[var]
    
    @staticmethod
    def set(var: str, val: any):
        Environment.__variables[var] = val

# 入出力管理用のクラス
class IOProcess:
    __output: list[str] = []
    __input: list[any] = []
    __index: int = 0

    @staticmethod
    def init():
        IOProcess.__output.clear()
        IOProcess.__input.clear()
    
    @staticmethod
    def input(s: str):
        def try_convert_to_float(value):
            try:
                return float(value)
            except ValueError:
                return value
        IOProcess.__input = s.split("\r\n")
        IOProcess.__input = list(map(try_convert_to_float, IOProcess.__input))
        IOProcess.__index = 0

    @staticmethod
    def output(s: str):
        IOProcess.__output.append(s)

    @staticmethod
    def get_input() -> any:
        IOProcess.__index += 1
        return IOProcess.__input[IOProcess.__index - 1]

    @staticmethod
    def get_output():
        return IOProcess.__output

# Expression クラス
class Expression:
    def __init__(self, type: str, children: list):
        self.type = type
        self.children = children

    def evaluate(self):
        if self.type == 'NUMBER':
            return self.children[0]
        elif self.type == 'STRING':
            return self.children[0]
        elif self.type == 'VAR':
            return Environment.get(self.children[0])
        elif self.type == 'FUNC':
            if self.children[0] == "要素数":
                return len(self.children[1].evaluate())
            elif self.children[0] == "整数":
                return round(self.children[1].evaluate())
            elif self.children[0] == "乱数":
                return random.random()
            elif self.children[0] == "表示する":
                s = "".join(map(lambda c : str(c.evaluate()), self.children[1:]))
                IOProcess.output(s)
        elif self.type == 'INPUT':
            print('INPUT')
            return IOProcess.get_input()
        elif self.type == 'ARRAY':
            contents = []
            for content in self.children:
                contents.append(content.evaluate())
            return contents
        elif self.type == 'ELM':
            return (self.children[0].evaluate())[self.children[1].evaluate()]
        elif self.type == 'OP':
            if self.children[0] == "**":
                return math.pow(self.children[1].evaluate(), self.children[2].evaluate())
            elif self.children[0] == "*":
                return self.children[1].evaluate() * self.children[2].evaluate()
            elif self.children[0] == "/":
                return self.children[1].evaluate() / self.children[2].evaluate()
            elif self.children[0] == "%":
                return self.children[1].evaluate() % self.children[2].evaluate()
            elif self.children[0] == "+":
                child1 = self.children[1].evaluate()
                child2 = self.children[2].evaluate()
                if type(child1) == str or type(child2) == str:
                    return str(child1) + str(child2)
                return child1 + child2
            elif self.children[0] == "-":
                return self.children[1].evaluate() - self.children[2].evaluate()
            elif self.children[0] == "==":
                return self.children[1].evaluate() == self.children[2].evaluate()
            elif self.children[0] == "!=":
                return self.children[1].evaluate() != self.children[2].evaluate()
            elif self.children[0] == "<":
                return self.children[1].evaluate() < self.children[2].evaluate()
            elif self.children[0] == ">":
                return self.children[1].evaluate() > self.children[2].evaluate()
            elif self.children[0] == "<=":
                return self.children[1].evaluate() <= self.children[2].evaluate()
            elif self.children[0] == ">=":
                return self.children[1].evaluate() >= self.children[2].evaluate()
            elif self.children[0] == "not":
                if self.children[1].evaluate() == 0:
                    return 1
                else:
                    return 0
            elif self.children[0] == "and":
                if self.children[1].evaluate() == 0 or self.children[2].evaluate() == 0:
                    return 0
                else:
                    return 1
            elif self.children[0] == "or":
                if self.children[1].evaluate() == 0 and self.children[2].evaluate() == 0:
                    return 0
                else:
                    return 1
            elif self.children[0] == "=":
                if self.children[1].type != 'VAR':
                    raise ExpressionError("=の左辺が変数ではありません")
                Environment.set(self.children[1].children[0], self.children[2].evaluate())
                return self.children[2].evaluate()
            
        elif self.type == 'STMT':
            if self.children[0] == "if":
                for i, block in enumerate(self.children[1]):
                    if not (self.children[1][i] is None or self.children[1][i].evaluate() == 0):
                        for j, expression in enumerate(self.children[2][i]):
                            expression.evaluate()
                        return None
                    elif self.children[1][i] is None:
                        for j, expression in enumerate(self.children[2][i]):
                            expression.evaluate()
                        return None
                return None
            
            elif self.children[0] == "forup":
                var = self.children[1]
                start_value = self.children[2]
                stop_value = self.children[3]
                step_value = self.children[4]
                block = self.children[5]
                for i in range(start_value.evaluate(), stop_value.evaluate() + 1, step_value.evaluate()):
                    if var.type != 'VAR':
                        raise ExpressionError("変数が指定されていません。")
                    Environment.set(var.children[0], i)
                    for stmt in block:
                        stmt.evaluate()
                return None
            
            elif self.children[0] == "fordown":
                var = self.children[1]
                start_value = self.children[2]
                stop_value = self.children[3]
                step_value = self.children[4]
                block = self.children[5]
                for i in range(start_value.evaluate(), stop_value.evaluate() - 1, -step_value.evaluate()):
                    if var.type != 'VAR':
                        raise ExpressionError("変数が指定されていません。")
                    Environment.set(var.children[0], i)
                    for stmt in block:
                        stmt.evaluate()
                return None
            
            elif self.children[0] == "while":
                con = self.children[1]
                block = self.children[2]
                while con.evaluate() != 0:
                    for stmt in block:
                        stmt.evaluate()
                return None
            
            elif self.children[0] == "resetarray":
                array_name = self.children[1]
                val = self.children[2]
                if array_name.type != 'VAR':
                    raise ExpressionError("変数が指定されていません。")
                array = Environment.get(array_name.children[0])
                array = list(map(lambda x : val.evaluate(), array))
                Environment.set(array_name.children[0], array)
                return None

        elif self.type == 'PROGRAM':
            for stmt in self.children[0]:
                stmt.evaluate()
            return 0

Environmentクラスは変数管理用クラス、IOProcessは入出力管理用クラスです。

実行する

execute.py
lexer = Lexer(code)
try:
    tokens = lexer.tokenize()
except LexerError as e:
    result += str(e)

parser = Parser(tokens)
try:
    IOProcess.init()
    IOProcess.input(input_data)
    _, program = parser.program(0)
    program.evaluate()
    result += "正常に実行されました\r\n"
    for o in IOProcess.get_output():
        result += "{0}\r\n".format(o)
except ParserError as e:
    result += str(e)

最後に

これがあれば共通テスト用プログラミング表記も楽に解けますね!
問題があったらコメントで教えてください。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?