本当は「「共通テスト用プログラミング表記」のインタプリタを作った話」という記事を出していたのですが、トラブルがあったので書き直します。
共通テスト用プログラミング表記とは?
作られた目的
共通テスト用プログラミング表記は、共通テストのために作られた擬似言語です(Pythonでいいじゃん)
元々DNCL(共通テスト手順記述標準言語)という別の謎言語が使われていましたが、令和7年度からこの共通テスト用プログラミング表記が使われるようになります。
文法
ざっくり説明すると(詳しくはこのサイトの下の方を参照)、
3 + 2
5 ** 2
10 ÷ 3
x = a + 5
表示する(x)
整数(x)
etc...
x = 【外部からの入力】
もしx==5ならば:
┃ 処理1
┃ 処理2
そうでなくもしx==1ならば:
┃ 処理3
そうでなければ:
┃ 処理4
┗ 処理5
i を 3 から 0 まで 1 ずつ減らしながら繰り返す:
┗ 処理1
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(共通テスト手順記述標準言語)を実行するサイトはあったのですが自分が将来受ける共通テストに出るであろう共通テスト用プログラミング表記を実行するサイトは見当たらなかったので作りました。
見た目
プログラム
インタプリタのコードの説明だけします。適当に作ったのでParserのコードとかは汚いです。
読まなくていい人は「最後に」まで飛ばしてください。
Lexer
字句解析器です。
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にしています。
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で生成するプログラムを表すクラスです。
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は入出力管理用クラスです。
実行する
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)
最後に
これがあれば共通テスト用プログラミング表記も楽に解けますね!
問題があったらコメントで教えてください。