Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

yukicoder April Fool Contest 2025 「Simple Programming Language」

Last updated at Posted at 2025-03-29

サンプルプログラム

$\sum_{i = 1}^W A_{1, i}$ と $\sum_{i = 1}^W B_{1, i}$ のうち、小さい方を変数 $1$ に、大きい方を変数 $2$ に格納し、それらを出力するプログラムの一例です。

$0 = 1
while($0 <= W)
  $1 = $1 + A[1,$0]
  $2 = $2 + B[1,$0]
  $0 = $0 + 1
end
if($1 > $2)
  $3 = $1
  $1 = $2
  $2 = $3
end
return($1, $2)

Simplang の BNF 表記

Simplang プログラムは、以下の BNF の <program> である必要があります。

<program> ::= <statements> "return" <result> <endlines>
<result> ::= "(" <expression> "," <expression> ")"

<statements> ::= <statement> | <statements> <endlines> <statement>
<statement> ::= <assignment> | <if_statement> | <while_statement>

<assignments> ::= <assignment> | <assignments> <endlines> <assignment>
<assignment> ::= <variable> "=" <expression>

<if_statement> ::= "if(" <condition> ")" <endlines> <assignments> <endlines> "end"
<while_statement> ::= "while(" <condition> ")" <endlines> <assignments> <endlines> "end"

<condition> ::= <statement> <compare_operator> <statement>
<compare_operator> ::= "<" | "<=" | ">" | ">=" | "==" | "!="

<expression> ::= <term> | <expression> "+" <term> | <expression> "-" <term>
<term> ::= <value> | <term> "*" <value> | <term> "/" <value> | <term> "%" <value>
<value> ::= <variable> | <constant> | <number> | <expression>

<variable> ::= "$" <number>
<constant> ::= "H" | "W" | "A[" <expression> "," <expression> "]" | "B[" <expression> "," <expression> "]"
<number> ::= 0 以上 10の100乗 未満の整数

<endlines> ::= "\n" | <endlines> "\n"

Simplang のインタプリタ実装(Python)

Python3 または PyPy3 で実行することができます。
標準入力から作成した Simplang プログラムを与えることで、実行されます。

ただし、仕様を満たさないプログラムを与えた場合、実行時エラーになります。
仕様は yukicoder: Simple Programming Language を参照してください。

from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional, Tuple, Union
import sys

# 実行行数の上限定数
LIMIT_TEXT_LENGTH = 100
LIMIT_ROW_LENGTH = 100
LIMIT_EXEC_ROW = 10**5
LIMIT_VALUE = 10**100

##############################
# ASTクラスの定義
##############################

class ASTNode:
    pass

# <program> ::= <statements> "return" <result> <endlines>
# <result> ::= "(" <expression> "," <expression> ")"
@dataclass
class Program(ASTNode):
    statements: List[Statement]
    result: Tuple[Expression, Expression]

class Statement(ASTNode):
    pass

# <assignment> ::= <variable> "=" <expression>
@dataclass
class Assignment(Statement):
    variable: Variable
    expression: Expression

# <if_statement> ::= "if(" <condition> ")" <endlines> <assignments> <endlines> "end"
@dataclass
class IfStatement(Statement):
    condition: Condition
    assignments: List[Assignment]

# <while_statement> ::= "while(" <condition> ")" <endlines> <assignments> <endlines> "end"
@dataclass
class WhileStatement(Statement):
    condition: Condition
    assignments: List[Assignment]

# <condition> ::= <statement> <compare_operator> <statement>
@dataclass
class Condition(ASTNode):
    left: Expression
    operator: str     # "<", "<=", ">", ">=", "==", "!="
    right: Expression

class Expression(ASTNode):
    pass

# <expression> ::= <expression> ("+"|"-") <term> | <term>
@dataclass
class BinOp(Expression):
    left: Expression
    op: str  # '+', '-', '*', '/', '%'
    right: Expression

# 数値リテラル
@dataclass
class Num(Expression):
    value: int

# <variable> ::= "$" <number>
@dataclass
class Variable(Expression):
    index: int

# <constant> ::= "H" | "W" | "A[" <expression> "," <expression> "]" | "B[" <expression> "," <expression> "]"
@dataclass
class Constant(Expression):
    name: str  # "H", "W", "A", "B"
    args: Optional[Tuple[Expression, Expression]] = None

##############################
# Lexer: 字句解析器
##############################

@dataclass
class Token:
    type: str
    value: Optional[str] = None

# トークンの種類
TT_NEWLINE    = "NEWLINE"
TT_IF         = "IF"
TT_WHILE      = "WHILE"
TT_END        = "END"
TT_RETURN     = "RETURN"
TT_NUMBER     = "NUMBER"
TT_DOLLAR     = "DOLLAR"
TT_IDENTIFIER = "IDENTIFIER"
TT_PLUS       = "PLUS"
TT_MINUS      = "MINUS"
TT_MUL        = "MUL"
TT_DIV        = "DIV"
TT_MOD        = "MOD"
TT_LPAREN     = "LPAREN"
TT_RPAREN     = "RPAREN"
TT_LBRACKET   = "LBRACKET"
TT_RBRACKET   = "RBRACKET"
TT_COMMA      = "COMMA"
TT_EQUAL      = "EQUAL"   # '='
TT_LT         = "LT"
TT_LE         = "LE"
TT_GT         = "GT"
TT_GE         = "GE"
TT_EQ         = "EQ"      # "=="
TT_NE         = "NE"      # "!="
TT_EOF        = "EOF"

class Lexer:
    def __init__(self, text: str):
        self.text = text
        self.pos = 0
        self.current_char: Optional[str] = self.text[self.pos] if self.text else None

    def advance(self):
        self.pos += 1
        if self.pos < len(self.text):
            self.current_char = self.text[self.pos]
        else:
            self.current_char = None

    def peek(self):
        peek_pos = self.pos + 1
        if peek_pos < len(self.text):
            return self.text[peek_pos]
        return None

    def skip_whitespace(self):
        while self.current_char is not None and self.current_char in " \t":
            self.advance()

    def number(self) -> Token:
        result = ''
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
        return Token(TT_NUMBER, result)

    def identifier(self) -> Token:
        result = ''
        while self.current_char is not None and self.current_char.isalpha():
            result += self.current_char
            self.advance()
        if result == "if":
            return Token(TT_IF, result)
        elif result == "while":
            return Token(TT_WHILE, result)
        elif result == "return":
            return Token(TT_RETURN, result)
        elif result == "end":
            return Token(TT_END, result)
        else:
            return Token(TT_IDENTIFIER, result)

    def tokenize(self) -> List[Token]:
        tokens: List[Token] = []
        while self.current_char is not None:
            if self.current_char in " \t":
                self.skip_whitespace()
                continue
            if self.current_char == "\n":
                tokens.append(Token(TT_NEWLINE, "\n"))
                self.advance()
                continue
            if self.current_char == '<':
                self.advance()
                if self.current_char == '=':
                    tokens.append(Token(TT_LE, "<="))
                    self.advance()
                else:
                    tokens.append(Token(TT_LT, "<"))
                continue
            if self.current_char == '>':
                self.advance()
                if self.current_char == '=':
                    tokens.append(Token(TT_GE, ">="))
                    self.advance()
                else:
                    tokens.append(Token(TT_GT, ">"))
                continue
            if self.current_char == '=':
                self.advance()
                if self.current_char == '=':
                    tokens.append(Token(TT_EQ, "=="))
                    self.advance()
                else:
                    tokens.append(Token(TT_EQUAL, "="))
                continue
            if self.current_char == '!':
                self.advance()
                if self.current_char == '=':
                    tokens.append(Token(TT_NE, "!="))
                    self.advance()
                else:
                    raise Exception("予期しない文字 '!'")
                continue
            if self.current_char == '+':
                tokens.append(Token(TT_PLUS, "+"))
                self.advance()
                continue
            if self.current_char == '-':
                tokens.append(Token(TT_MINUS, "-"))
                self.advance()
                continue
            if self.current_char == '*':
                tokens.append(Token(TT_MUL, "*"))
                self.advance()
                continue
            if self.current_char == '/':
                tokens.append(Token(TT_DIV, "/"))
                self.advance()
                continue
            if self.current_char == '%':
                tokens.append(Token(TT_MOD, "%"))
                self.advance()
                continue
            if self.current_char == '(':
                tokens.append(Token(TT_LPAREN, "("))
                self.advance()
                continue
            if self.current_char == ')':
                tokens.append(Token(TT_RPAREN, ")"))
                self.advance()
                continue
            if self.current_char == '[':
                tokens.append(Token(TT_LBRACKET, "["))
                self.advance()
                continue
            if self.current_char == ']':
                tokens.append(Token(TT_RBRACKET, "]"))
                self.advance()
                continue
            if self.current_char == ',':
                tokens.append(Token(TT_COMMA, ","))
                self.advance()
                continue
            if self.current_char == '$':
                tokens.append(Token(TT_DOLLAR, "$"))
                self.advance()
                continue
            if self.current_char.isdigit():
                tokens.append(self.number())
                continue
            if self.current_char.isalpha():
                tokens.append(self.identifier())
                continue
            raise Exception(f"不明な文字: {self.current_char}")
        tokens.append(Token(TT_EOF, None))
        return tokens

##############################
# Parser: 再帰下降パーサー
##############################

class Parser:
    def __init__(self, tokens: List[Token]):
        self.tokens = tokens
        self.pos = 0

    def current_token(self) -> Token:
        return self.tokens[self.pos]

    def eat(self, token_type: str, token_value: Optional[str] = None):
        token = self.current_token()
        if token.type == token_type and (token_value is None or token.value == token_value):
            self.pos += 1
        else:
            raise Exception(f"期待されたトークン {token_type}({token_value}) ですが、得られたのは {token.type}({token.value})")

    def parse(self) -> Program:
        stmts = self.parse_statements()
        self.eat(TT_RETURN)
        result = self.parse_result()
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
        self.eat(TT_EOF)
        return Program(statements=stmts, result=result)

    def parse_result(self) -> Tuple[Expression, Expression]:
        self.eat(TT_LPAREN)
        left = self.parse_expression()
        self.eat(TT_COMMA)
        right = self.parse_expression()
        self.eat(TT_RPAREN)
        return (left, right)

    def parse_statements(self) -> List[Statement]:
        stmts = [self.parse_statement()]
        while self.current_token().type == TT_NEWLINE:
            while self.current_token().type == TT_NEWLINE:
                self.eat(TT_NEWLINE)
            if self.current_token().type in (TT_RETURN, TT_EOF, TT_END):
                break
            stmts.append(self.parse_statement())
        return stmts

    def parse_statement(self) -> Statement:
        token = self.current_token()
        if token.type == TT_IF:
            return self.parse_if_statement()
        elif token.type == TT_WHILE:
            return self.parse_while_statement()
        else:
            return self.parse_assignment()

    def parse_assignment(self) -> Assignment:
        var = self.parse_variable()
        self.eat(TT_EQUAL)
        expr = self.parse_expression()
        return Assignment(variable=var, expression=expr)

    def parse_if_statement(self) -> IfStatement:
        self.eat(TT_IF)
        self.eat(TT_LPAREN)
        cond = self.parse_condition()
        self.eat(TT_RPAREN)
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
        assigns = self.parse_assignments()
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
        self.eat(TT_END)
        return IfStatement(condition=cond, assignments=assigns)

    def parse_while_statement(self) -> WhileStatement:
        self.eat(TT_WHILE)
        self.eat(TT_LPAREN)
        cond = self.parse_condition()
        self.eat(TT_RPAREN)
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
        assigns = self.parse_assignments()
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
        self.eat(TT_END)
        return WhileStatement(condition=cond, assignments=assigns)

    def parse_assignments(self) -> List[Assignment]:
        assigns = [self.parse_assignment()]
        while self.current_token().type == TT_NEWLINE:
            self.eat(TT_NEWLINE)
            if self.current_token().type in (TT_END, TT_RETURN, TT_EOF):
                break
            assigns.append(self.parse_assignment())
        return assigns

    def parse_condition(self) -> Condition:
        left_expr = self.parse_expression()
        op_token = self.current_token()
        if op_token.type in (TT_LT, TT_LE, TT_GT, TT_GE, TT_EQ, TT_NE):
            self.eat(op_token.type)
        else:
            raise Exception("比較演算子が見つかりません")
        right_expr = self.parse_expression()
        return Condition(left=left_expr, operator=op_token.value, right=right_expr)

    def parse_expression(self) -> Expression:
        expr = self.parse_term()
        while self.current_token().type in (TT_PLUS, TT_MINUS):
            op_token = self.current_token()
            self.eat(op_token.type)
            right = self.parse_term()
            expr = BinOp(left=expr, op=op_token.value, right=right)
        return expr

    def parse_term(self) -> Expression:
        term = self.parse_value()
        while self.current_token().type in (TT_MUL, TT_DIV, TT_MOD):
            op_token = self.current_token()
            self.eat(op_token.type)
            right = self.parse_value()
            term = BinOp(left=term, op=op_token.value, right=right)
        return term

    def parse_value(self) -> Expression:
        token = self.current_token()
        if token.type == TT_LPAREN:
            self.eat(TT_LPAREN)
            expr = self.parse_expression()
            self.eat(TT_RPAREN)
            return expr
        elif token.type == TT_DOLLAR:
            return self.parse_variable()
        elif token.type == TT_NUMBER:
            self.eat(TT_NUMBER)
            return Num(int(token.value))
        elif token.type == TT_IDENTIFIER:
            ident = token.value
            self.eat(TT_IDENTIFIER)
            if ident in ("H", "W"):
                return Constant(name=ident)
            elif ident in ("A", "B"):
                if self.current_token().type == TT_LBRACKET:
                    self.eat(TT_LBRACKET)
                    expr1 = self.parse_expression()
                    self.eat(TT_COMMA)
                    expr2 = self.parse_expression()
                    self.eat(TT_RBRACKET)
                    return Constant(name=ident, args=(expr1, expr2))
                else:
                    return Constant(name=ident)
            else:
                raise Exception(f"未知の識別子: {ident}")
        else:
            raise Exception(f"予期しないトークン: {token.type}({token.value})")

    def parse_variable(self) -> Variable:
        self.eat(TT_DOLLAR)
        token = self.current_token()
        if token.type != TT_NUMBER:
            raise Exception("変数は '$' に続く数値でなければなりません")
        self.eat(TT_NUMBER)
        return Variable(index=int(token.value))

##############################
# Interpreter: AST実行器(デバッグ・行数上限チェック付き)
##############################

class Interpreter:
    def __init__(self, constants: dict, debug: bool = False):
        self.constants = constants      # 定数環境: 例: {"H": 3, "W": 3, "A": [[...]], "B": [[...]]}
        self.env = {}                   # 変数環境: 変数番号 -> 値(整数)
        self.line_count = 0             # 実行済み行数のカウンタ
        self.debug = debug              # デバッグモードフラグ

    def _increment_line(self, description: str):
        self.line_count += 1
        if self.line_count > LIMIT_EXEC_ROW:
            raise Exception("実行行数が上限を超えました。")
        if self.debug:
            print(f"Line {self.line_count}: {description}", file=sys.stderr)

    def eval_expr(self, expr: Expression) -> int:
        if isinstance(expr, Num):
            val = expr.value
            assert abs(val) <= LIMIT_VALUE, "数値リテラルの絶対値が上限を超えました。"
            return val
        elif isinstance(expr, Variable):
            val = self.env.get(expr.index, 0)
            assert abs(val) <= LIMIT_VALUE, "変数の値が上限を超えました。"
            return val
        elif isinstance(expr, Constant):
            if expr.args is None:
                if expr.name in ("H", "W"):
                    return self.constants[expr.name]
                else:
                    raise Exception(f"定数 {expr.name} は引数が必要です")
            else:
                # AまたはBの場合。引数は1-indexedと仮定して0-indexedに変換
                row = self.eval_expr(expr.args[0])
                col = self.eval_expr(expr.args[1])
                if expr.name in ("A", "B"):
                    matrix = self.constants[expr.name]
                    return matrix[row - 1][col - 1]
                else:
                    raise Exception(f"未知の定数: {expr.name}")
        elif isinstance(expr, BinOp):
            left = self.eval_expr(expr.left)
            right = self.eval_expr(expr.right)
            if expr.op == '+':
                if abs(left + right) >= LIMIT_VALUE:
                    raise Exception("二項演算の結果が上限を超えました。")
                return left + right
            elif expr.op == '-':
                if abs(left - right) >= LIMIT_VALUE:
                    raise Exception("二項演算の結果が上限を超えました。")
                return left - right
            elif expr.op == '*':
                if abs(left * right) >= LIMIT_VALUE:
                    raise Exception("二項演算の結果が上限を超えました。")
                return left * right
            elif expr.op == '/':
                if right == 0:
                    raise Exception("ゼロ除算エラー")
                if abs(left // right) >= LIMIT_VALUE:
                    raise Exception("二項演算の結果が上限を超えました。")
                return left // right
            elif expr.op == '%':
                if right == 0:
                    raise Exception("ゼロによる剰余エラー")
                if abs(left % right) >= LIMIT_VALUE:
                    raise Exception("二項演算の結果が上限を超えました。")
                return left % right
            else:
                raise Exception("未知の二項演算子: " + expr.op)
        else:
            raise Exception("未知の式の型")

    def eval_condition(self, cond: Condition) -> bool:
        left = self.eval_expr(cond.left)
        right = self.eval_expr(cond.right)
        op = cond.operator
        if op == "<":
            return left < right
        elif op == "<=":
            return left <= right
        elif op == ">":
            return left > right
        elif op == ">=":
            return left >= right
        elif op == "==":
            return left == right
        elif op == "!=":
            return left != right
        else:
            raise Exception("未知の比較演算子: " + op)

    def execute_statement(self, stmt: Statement):
        if isinstance(stmt, Assignment):
            self._increment_line(f"Assignment -> {stmt.variable} = {stmt.expression}")
            value = self.eval_expr(stmt.expression)
            self.env[stmt.variable.index] = value
        elif isinstance(stmt, IfStatement):
            self._increment_line(f"If statement -> if({stmt.condition})")
            if self.eval_condition(stmt.condition):
                for s in stmt.assignments:
                    self.execute_statement(s)
        elif isinstance(stmt, WhileStatement):
            self._increment_line(f"While statement header -> while({stmt.condition})")
            while self.eval_condition(stmt.condition):
                for s in stmt.assignments:
                    self.execute_statement(s)
                self._increment_line(f"While statement header -> while({stmt.condition})")
        else:
            raise Exception("未知の文の型")

    def interpret(self, program: Program) -> Tuple[Tuple[int, int], int]:
        for stmt in program.statements:
            self.execute_statement(stmt)
        self._increment_line("Return statement")
        ret_left = self.eval_expr(program.result[0])
        ret_right = self.eval_expr(program.result[1])
        return (ret_left, ret_right), self.line_count


###############################
# プログラムの取得
###############################

def get_output_code():
    rows = []
    row_count = 0
    for _ in range(LIMIT_ROW_LENGTH + 1):
        try:
            line = input()
            rows.append(line)
            assert len(line) < LIMIT_TEXT_LENGTH, "行の長さが上限を超えました。"
        except EOFError:
            break
    
    assert len(rows) <= LIMIT_ROW_LENGTH, "行数が上限を超えました。"
    return "\n".join(rows) + "\n"


##############################
# 使用例
##############################

if __name__ == "__main__":
    output_code = get_output_code()
    # 字句解析
    lexer = Lexer(output_code)
    tokens = lexer.tokenize()
    # 構文解析
    parser = Parser(tokens)
    ast = parser.parse()
    
    # 定数の設定(例として3x3の行列を用いる)
    constants = {
        "H": 3,
        "W": 3,
        "A": [
            [1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
        ],
        "B": [
            [1, 2, 3],
            [4, 5, 6],
            [7, 9, 9]
        ]
    }
    # インタプリタの生成(デバッグモードをOFFにする場合はdebug=False)
    interpreter = Interpreter(constants, debug=True)
    
    # 実行(結果と実行行数を取得)
    result, executed_lines = interpreter.interpret(ast)
    
    print("Result:", result)
    print("Executed lines:", executed_lines, file=sys.stderr)

0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?