Javascript로 Brainf x k의 해석기를 써봤어요.
27964 단어 JavaScripttech
Brainf 소개
실행 형식의 컴파일러, 해석기는 모두 100바이트 정도의 놀라운 소형으로 유명한 이해하기 어려운 프로그래밍 언어이다.
실행 시스템 내부에는 다음과 같은 네 가지 변수가 있다.
데이터를 포함하는 수조와 데이터 포인터
단지 바이트 열 (바이트 열) 이다.
일반적인 프로그래밍 언어에 해당하는 변수라고 볼 수 있다.
가변 길이 배열에도 문제가 없기 때문
Array
도 괜찮지만, 이번에Uint8Array
로.데이터 포인터는 데이터를 포함하는 그룹의 특정 요소의 인덱스입니다.
데이터 지침이 가리키는 요소만 후술 명령의 조작 대상이다.
명령어의 배열과 명령 포인터
명령은 모두 8종이다.
명령하다
의향
>
데이터 포인터의 값을 더하다.<
데이터 포인터의 값을 빼다.+
데이터를 추가합니다.-
데이터를 빼다..
데이터를 출력하다.,
입력한 후 1바이트를 데이터로 합니다.[
데이터가 0이면 명령 포인터를 해당 위치로 이동합니다]
.]
데이터가 0이 아닌 경우 명령 포인터를 해당 위치에 놓습니다[
.※ 표의'데이터'는'데이터 포인터가 가리키는 데이터 배열의 값'을 나타낸다.
명령의 배열은 Brainf x k의 소스 코드와 같습니다.
여기에 특별히 원본 코드를 부르지 않은 것은 명령에 부합되지 않는 문자를 무시하는 규칙이 있기 때문이다.
후술한 이유에 따라 설치 중 원본 코드와 다른 배열을 준비했습니다.
물론 특별한 이유가 없다면 원본 코드와 마찬가지로 문제없다.
명령 지침은 매번 명령을 집행할 때마다 가산한다.
[
또는 ]
에서 명령 포인터가 변동된 경우 이후 명령부터 실행됩니다.명령 포인터가 입력 명령의 그룹 길이와 같으면 프로그램의 실행이 끝납니다.
이루어지다
Brainf×k의 구조를 이해한 후 본제 해석기 설치에 들어갑니다.
먼저 구문 분석(parse)
해석기가 원본 코드를 실행하기 전에 먼저 문법을 해석합니다.
그 이유는 세 가지다.
코드가 정확한지 미리 확인하고 싶어요.
[
와 ]
의 대응 관계만 확인하면 문법 오류라면 집행 전에 확인하는 것이 좋다.어느 쪽이든 파생계에 대응했으면 좋겠어요.
브레인프×k에는 간단한 문장에서 8개의 명령에 해당하는 문장을 다양한 것으로 바꾸는 파생어가 많다.파생에 대응할 수 있는 처리를 해석기에 편입하면 복잡해진다.
어쨌든 컴파일러를 만들고 싶어요.
해석기와 문법 해석을 공유할 수 있다.그리고 컴파일러도 파생 시스템에 대응할 수 있다.
입력 컨트롤러
입력과 출력은 Node입니다.js라면 표준 입력과 출력을 사용하면 되죠.
그러나 웹 클라이언트는 그 웹에 따라 다릅니다.
따라서 입력 출력 목적지는 해석기에서 끊어져서 실행할 때 지정할 수 있습니다.
방법은 이동전화로 입력을 받는 반면 출력은 프로그램의 실행 방법으로 되돌아오는 것이다.
문법 해석이 닫혔기 때문에, 실행 중 오류는 입력이 닫혔지만 입력을 읽을 때만 발생합니다.
완성된 소스 코드
상기 규격에 따라 완성된 원본 코드는 여기에 있습니다.
/*
* 0x01などの値に特に意味はありません。
* 重複しなければなんでもOKです。
*/
const Instruction = Object.freeze( {
INCREMENT_POINTER: 0x01,
DECREMENT_POINTER: 0x02,
INCREMENT: 0x03,
DECREMENT: 0x04,
OUTPUT: 0x05,
INPUT: 0x06,
JUMP_FOWARD: 0x07,
JUMP_BACK: 0x08,
} )
const DEFAULT_DATA_SIZE = 30000
/*
* ただ0のみを返すダミーの入力です。
* /dev/zeroのようなものです。
*/
const DEFAULT_INPUT_STREAM = (function*(){
for (;;) yield 0
})()
class BrainFKSyntaxError extends Error {
constructor( ...args ) {
super( ...args )
}
}
class BrainFKRuntimeError extends Error {
constructor( ...args ) {
super( ...args )
}
}
const parseCode = ( code ) => {
/*
* 以下の3つが戻り値です。
* instructions: 命令の入った配列
* jumpInfo : [, ]でジャンプする先の命令ポインタの値の入った連想配列
* debugInfo : 命令ポインタの値とソースコード上の文字列位置の対応関係の入った連想配列
*/
const instructions = [],
jumpInfo = new Map,
debugInfo = new Map
const stack = []
let index = 0, instructionPointer = 0
for ( ; index < code.length; index ++ ) {
let instruction
switch ( code[ index ] ) {
case ">":
instruction = Instruction.INCREMENT_POINTER
break
case "<":
instruction = Instruction.DECREMENT_POINTER
break
case "+":
instruction = Instruction.INCREMENT
break
case "-":
instruction = Instruction.DECREMENT
break
case ".":
instruction = Instruction.OUTPUT
break
case ",":
instruction = Instruction.INPUT
break
case "[":
instruction = Instruction.JUMP_FOWARD
stack.push( instructionPointer )
break
case "]":
instruction = Instruction.JUMP_BACK
if ( stack.length == 0 ) {
throw new BrainFKSyntaxError( `missing "[" index=${ index }` )
}
const foward = stack.pop()
jumpInfo.set( foward, instructionPointer )
jumpInfo.set( instructionPointer, foward )
break
default:
continue
}
debugInfo.set( instructionPointer, [ index, index + 1 ] )
instructions.push( instruction )
instructionPointer ++
}
if ( stack.length > 0 ) {
throw new BrainFKSyntaxError( `missing "]" index=${ index }` )
}
return {
instructions: Uint8Array.from( instructions ),
jumpInfo,
debugInfo,
}
}
class BrainFKInterpreter {
#instructions
#jumpInfo
#data
#inputStream
#debugInfo
constructor( code, { dataSize = DEFAULT_DATA_SIZE, inputStream = DEFAULT_INPUT_STREAM } = {} ) {
const { instructions, jumpInfo, debugInfo } = parseCode( code )
this.#instructions = instructions
this.#jumpInfo = jumpInfo
this.#data = new Uint8Array( Number( dataSize ) )
this.#inputStream = inputStream
this.#debugInfo = debugInfo
}
async execute() {
const outputs = []
for await ( const [ ,,, output ] of this.step() ) {
if ( output != null ) {
outputs.push( output )
}
}
return Uint8Array.from( outputs )
}
/*
* ジェネレーターにすることで、命令ごとにステップ実行できます。
*/
async * step() {
const instructions = this.#instructions
const jumpInfo = this.#jumpInfo
const data = this.#data
const debugInfo = this.#debugInfo
for ( let instructionPointer = 0, dataPointer = 0; instructionPointer < instructions.length; instructionPointer ++ ) {
let output = null
switch ( instructions[ instructionPointer ] ) {
case Instruction.INCREMENT_POINTER:
++ dataPointer
break
case Instruction.DECREMENT_POINTER:
-- dataPointer
break
case Instruction.INCREMENT:
++ data[ dataPointer ]
break
case Instruction.DECREMENT:
-- data[ dataPointer ]
break
case Instruction.OUTPUT:
output = data[ dataPointer ]
break
case Instruction.INPUT:
const input = await this.#inputStream.next()
if ( input.done ) {
throw new BrainFKRuntimeError( `input is already closed.` )
}
data[ dataPointer ] = input.value
break
case Instruction.JUMP_FOWARD:
if ( data[ dataPointer ] == 0 ) {
instructionPointer = jumpInfo.get( instructionPointer )
}
break
case Instruction.JUMP_BACK:
if ( data[ dataPointer ] != 0 ) {
instructionPointer = jumpInfo.get( instructionPointer )
}
break
}
yield [ dataPointer, data[ dataPointer ], debugInfo.get( instructionPointer ), output ]
}
}
}
끝맺다
이상!
브레인프×k 자체가 실용적이라고 하기는 어렵지만 해석기와 컴파일러를 쓰는 연습이 될 수 있다.
Reference
이 문제에 관하여(Javascript로 Brainf x k의 해석기를 써봤어요.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/37cohina/articles/2614b1231fd39e텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)