7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PegjsでAltjsを作る 第3回 計算の式と項

Last updated at Posted at 2013-04-28

今回はpegjsで 3 + 4 * (5 + 6) みたいな式をパースすることを目標にします。

注意!

今回は pegjs-coffee-plugin https://npmjs.org/package/pegjs-coffee-plugin を使って、PEG.jsのブロックの中はcoffeescriptで記述しています。僕が楽だからです。

Lispの木は構文木

まずLispの話をします。Lispは構文木そのものだからです。
まず、計算式を表す前提として、慣れ親しんだ掛け算と括弧による優先順位を付けねばなりません。

3 + 4 * (5 + 6) をLispで表現すると、次の様になります

(+ 3 (* 4 (+ 5 6)))

Lispでは計算順は自明です。

ここでルールを明文化しましょう。

  • 式は複数の項で表現される
  • 項は * か / 、または(...)で結合される値のグループである

これをPEG.jsで表してみましょう

全文

start = Program
Symbol = $([a-zA-Z] [a-zA-Z0-9]*)
Number = $(("+" / "-")? _ [1-9] [0-9]* ("." [0-9]+)? )
Whitespace = [\t\v\f \u00A0\uFEFF]
LineTerminator = [\n\r\u2028\u2029]
_  = (Whitespace / LineTerminator)*
__ = Whitespace+

Program = AdditiveStatement
AdditiveOperator = "+" / "-"
AdditiveStatement = head:Term tail:(_ op:AdditiveOperator _ term:Term { op:op, term:term })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.term
    root
  }

MulticativeOperator = "*" / "/"
Term
  = head:Primary tail:(_ op:MulticativeOperator _ primary:Primary { op:op, primary:primary })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.primary
    root
  }
  / Primary

Primary
  = "(" _ statement:AdditiveStatement _ ")" { statement }
  / Value

Value
  = symbol:Symbol { identifier:symbol }
  / number:Number { {number} }

結果

入力

3 + 4 * (5 + 6)

出力

left:
  number: 3
op:    +
right:
  left:
    number: 4
  op:    *
  right:
    left:
      number: 5
    op:    +
    right:
      number: 6

解説

最初のはおまじないというか頻出パターンのスニペット

Symbol = $([a-zA-Z] [a-zA-Z0-9]*)
Number = $(("+" / "-")? _ [1-9] [0-9]* ("." [0-9]+)? )
Whitespace = [\t\v\f \u00A0\uFEFF]
LineTerminator = [\n\r\u2028\u2029]
_  = (Whitespace / LineTerminator)*
__ = Whitespace+

_ で間に挟まるスペースを表現します。あと識別子と数値型。
小さな単位から見て行きましょう。

括弧

Primary
  = "(" _ statement:AdditiveStatement _ ")" { statement }
  / Value

Value
  = symbol:Symbol { identifier:symbol }
  / number:Number { {number} }

AddtiveStatementはこの式全体を再帰した表現です。()で囲われた式、ということで優先順位をつけます。
あるいは単体のValue。そしてValueはシンボルか数値で表される、というわけです。

MulticativeOperator = "*" / "/"
Term
  = head:Primary tail:(_ op:MulticativeOperator _ primary:Primary { op:op, primary:primary })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.primary
    root
  }
  / Primary

Primary句を * か / で結合したもの。あるいはPrimaryそのもの。

AdditiveOperator = "+" / "-"
AdditiveStatement = head:Term tail:(_ op:AdditiveOperator _ term:Term { op:op, term:term })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.term
    root
  }

Additiveは T [, T]* という形になります
headをrootにして、構文木を「上に」向けて作っていきます。こうしないと + と - が入り混じった式で、意味が反転したりしてしまいます。

実行したスクリプト全文

escodegen = require 'escodegen'
esprima = require 'esprima'
pj = require 'prettyjson'
PEG = require 'pegjs'
PEGjsCoffeePlugin = require 'pegjs-coffee-plugin'
PEGjsCoffeePlugin.addTo PEG
fs = require 'fs'

p = console.log.bind console

# show ast tree
get_js_ast = (code) -> pj.render esprima.parse code
json_dump = (code)-> p pj.render code

# pegjs parser
gen_parser = (src) -> PEG.buildParser src
parse_with_gen = (parser_code, code) ->
  parser = gen_parser parser_code
  parser.parse code

# pegjs parser and ast
parse_with_gen_and_escodegen = (parser_code, code) ->
  parser = gen_parser parser_code
  escodegen.generate parser.parse code

parse_with_gen_and_escodegen_exec = (parser_code, code) ->
  eval parse_with_gen_and_escodegen parser_code, code

# peg_parser = fs.readFileSync('blace.pegjs').toString()
peg_parser = """
start = Program
Symbol = $([a-zA-Z] [a-zA-Z0-9]*)
Number = $(("+" / "-")? _ [1-9] [0-9]* ("." [0-9]+)? )
Whitespace = [\\t\\v\\f \\u00A0\\uFEFF]
LineTerminator = [\\n\\r\\u2028\\u2029]
_  = (Whitespace / LineTerminator)*
__ = Whitespace+

Program = AdditiveStatement
AdditiveOperator = "+" / "-"
AdditiveStatement = head:Term tail:(_ op:AdditiveOperator _ term:Term { op:op, term:term })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.term
    root
  }

MulticativeOperator = "*" / "/"
Term
  = head:Primary tail:(_ op:MulticativeOperator _ primary:Primary { op:op, primary:primary })*
  {
    root = head
    while node = tail.shift()
      root =
        left : root
        op   : node.op
        right: node.primary
    root
  }
  / Primary

Primary
  = "(" _ statement:AdditiveStatement _ ")" { statement }
  / Value

Value
  = symbol:Symbol { identifier:symbol }
  / number:Number { {number} }
"""

code = """
3 + 4 * (5 + 6) / 2
"""

p '-----------' + new Date
data = parse_with_gen peg_parser, code
p code
p pj.render data
7
6
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
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?