2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

年賀状でふざけるために言語作ってみた

Posted at

年賀状でふざけたい!

と言うことで、年賀状を難読プログラミング言語で書くことにしました

どの言語にするか

Brainfuck は王道すぎてつまらない
Ook! とかもいいけど、正月感がない

そうだ、作ってしまえ!

作ることにしました
色々考えた末、 Ook! の形式で、Happy New Year で構成することにしました
名前は Happy New Year の頭文字をとって hnyfuck とします

リポジトリはこちらです
https://github.com/kimuti-tsukai/hnyfuck

作る過程

※初めての言語作成で、多少下手なところがあると思いますが、温かい目でお見守り下さい

命令はそれぞれ次のようにします
< => Happy New
> => New Year
+ => Year Happy
- => Happy Year
. => Year New
, => New Happy
[ => Happy Happy
] => New New

初めてとは言っても、 Rust の proc_macro はたまに作るので、 proc_macro, syn を真似て作ってみます

とりあえず命令をこんなふうに定義します

const SHIFT_LEFT: (&str, &str) = ("Happy", "New");
const SHIFT_RIGHT: (&str, &str) = ("New", "Year");
const INCREMENT: (&str, &str) = ("Year", "Happy");
const DECREMENT: (&str, &str) = ("Happy", "Year");
const OUTPUT: (&str, &str) = ("Year", "New");
const INPUT: (&str, &str) = ("New", "Happy");
const LOOP_START: (&str, &str) = ("Happy", "Happy");
const LOOP_END: (&str, &str) = ("New", "New");

TokenStream を実装します
Queue のように使いたいので VecDeque をラップします

#[derive(Debug, Clone)]
struct TokenStream {
    tokens: VecDeque<String>,
}

impl TokenStream {
    fn new() -> TokenStream {
        TokenStream {
            tokens: VecDeque::new(),
        }
    }

    fn push(&mut self, token: String) {
        self.tokens.push_back(token);
    }

    fn from_str(input: &str) -> TokenStream {
        let mut stream = TokenStream::new();
        for token in input.split_whitespace() {
            stream.push(token.to_string());
        }
        stream
    }

    fn next(&mut self) -> Option<String> {
        self.tokens.pop_front()
    }

    fn next2(&mut self) -> Option<(String, String)> {
        let first = self.next();
        let second = self.next();
        match (first, second) {
            (Some(f), Some(s)) => Some((f, s)),
            _ => None,
        }
    }

    fn peek(&self) -> Option<&String> {
        self.tokens.front()
    }

    fn peekn(&self, n: usize) -> Option<&String> {
        self.tokens.get(n)
    }
}

標準入力を受け取るために InputStream を実装します

#[derive(Debug)]
struct InputStream {
    stdin: io::Bytes<io::Stdin>,
}

impl InputStream {
    fn new() -> InputStream {
        InputStream {
            stdin: io::stdin().bytes(),
        }
    }

    fn next(&mut self) -> Option<u8> {
        self.stdin.next().and_then(|result| result.ok())
    }
}

状態を管理するために、 State と言う構造体を定義します
先頭への追加も高速に行いたいので VecDeque を使います

#[derive(Debug)]
struct State {
    state: VecDeque<u8>,
    index: usize,
    input: InputStream,
}

命令を実装します

impl State {
    fn new() -> State {
        let mut state = VecDeque::new();
        state.push_back(0);
        State {
            state,
            index: 0,
            input: InputStream::new(),
        }
    }

    fn shift_left(&mut self) {
        match self.index {
            0 => self.state.push_front(0),
            _ => self.index -= 1,
        }
    }

    fn shiht_right(&mut self) {
        match self.index {
            i if i == self.state.len() - 1 => {
                self.state.push_back(0);
                self.index += 1;
            }
            _ => self.index += 1,
        }
    }

    fn increment(&mut self) {
        if let Some(cell) = self.state.get_mut(self.index) {
            *cell += 1;
        }
    }

    fn decrement(&mut self) {
        if let Some(cell) = self.state.get_mut(self.index) {
            *cell -= 1;
        }
    }

    fn output(&mut self) {
        if let Some(cell) = self.state.get(self.index) {
            print!("{}", *cell as char);
        }
    }

    fn input(&mut self) {
        if let Some(cell) = self.state.get_mut(self.index) {
            if let Some(byte) = self.input.next() {
                *cell = byte;
            }
        }
    }

    fn cond(&self) -> bool {
        self.state.get(self.index).map_or(false, |cell| *cell != 0)
    }
}

プログラムの状態を初期化、実行するための HnyFuck と言う構造体を定義します

#[derive(Debug)]
struct HnyFuck {
    stream: TokenStream,
    state: State,
}

ループの実装の仕方はよく知らないので、今回はループ内のコードをコピーして現在の状態で実装します

impl HnyFuck {
    fn new(stream: TokenStream) -> Self {
        Self {
            stream,
            state: State::new(),
        }
    }

    fn from_str(input: &str) -> Self {
        Self::new(TokenStream::from_str(input))
    }

    fn run(&mut self) {
        while let Some((first, second)) = self.stream.next2() {
            match (first.as_str(), second.as_str()) {
                SHIFT_LEFT => self.state.shift_left(),
                SHIFT_RIGHT => self.state.shiht_right(),
                INCREMENT => self.state.increment(),
                DECREMENT => self.state.decrement(),
                OUTPUT => self.state.output(),
                INPUT => self.state.input(),
                LOOP_START => {
                    let mut token_stream = TokenStream::new();
                    let mut depth = 1;
                    while let Some((token1, token2)) = self.stream.next2() {
                        match (token1.as_str(), token2.as_str()) {
                            LOOP_START => depth += 1,
                            LOOP_END => {
                                depth -= 1;
                                if depth == 0 {
                                    break;
                                }
                            }
                            _ => (),
                        }
                        token_stream.push(token1);
                        token_stream.push(token2);
                    }

                    let state = std::mem::replace(&mut self.state, State::new());

                    let mut nest = Self {
                        stream: token_stream.clone(),
                        state,
                    };

                    while {
                        nest.run();

                        nest.stream = token_stream.clone();

                        nest.state.cond()
                    } {}

                    self.state = nest.state;
                }
                _ => panic!("Invalid token"),
            }
        }
    }
}

これで完成です

Brainfuck から変換するやつも実装したのでどうぞ

fn from_brainfuck(code: &str) -> String {
    code
        .chars()
        .map(|c| match c {
            '>' => SHIFT_RIGHT,
            '<' => SHIFT_LEFT,
            '+' => INCREMENT,
            '-' => DECREMENT,
            '.' => OUTPUT,
            ',' => INPUT,
            '[' => LOOP_START,
            ']' => LOOP_END,
            _ => panic!("Invalid character"),
        })
        .map(|(a, b)| format!("{} {}", a, b))
        .collect::<Vec<String>>()
        .join(" ")
}

ほとんどコードの記事になってすみません

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?