Javascript로 Brainf x k의 해석기를 써봤어요.

27964 단어 JavaScripttech
※ 브레인프×k의 정확한 명칭에는 문제가 있는 단어가 포함되어 있어 이 글에는 복자를 사용했습니다.

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 자체가 실용적이라고 하기는 어렵지만 해석기와 컴파일러를 쓰는 연습이 될 수 있다.

    좋은 웹페이지 즐겨찾기