読者です 読者をやめる 読者になる 読者になる

インデント対応パーサ

インデントでなんとなくパースできるのができた
やっぱり文法は普通にしてlexでなんとかするのがいいかな

%default Node

%token<Node> BEGIN END
%token<Node> var expr

%left '+' '-'
%left '*' '/'

%%

program : void
        | program stmt

stmt : begin block end
begin : BEGIN {Console.WriteLine("".PadLeft($1.Indent) + "BEGIN")}
end   : END   {Console.WriteLine("".PadLeft($1.Indent) + "END")}

block : line
      | stmt # いらないかも
      | block line
      | block stmt

line : void EOL
     | expr EOL {Console.WriteLine("".PadLeft($1.Indent + 1) + $1.Name)}

expr : var           {$$ = $1}
     | expr '+' expr {$1.Name = "(" +  $1.Name + ") + " + $3.Name       : $$ = $1}
     | expr '-' expr {$1.Name = "(" +  $1.Name + ") - " + $3.Name       : $$ = $1}
     | expr '*' expr {$1.Name = "(" +  $1.Name + " * "  + $3.Name + ")" : $$ = $1}
     | expr '/' expr {$1.Name = "(" +  $1.Name + " / "  + $3.Name + ")" : $$ = $1}

var : VAR {$$ = New Node With {.Name = $1.Name, .Indent = $1.Value.Indent}}

void :


lex側は
要は前にEOLがあればインデントを数えて前回より上ならBEGINを差し込みスタックに積む、下ならENDを差し込みスタックを下げる
前がEOLでなかったら特になにもしない
EOFが出ればスタックの分だけENDを差し込む
・・・なんだけど、ひたすら長くなった、泣きそう

Private prev_ As IToken(Of Node) = Nothing
Private next_ As IToken(Of Node) = Nothing
Private indent_stack_ As New List(Of Integer)

If Me.next_ Is Nothing Then Me.next_ = Me.ReaderNext

If Me.next_.InputToken = SymbolTypes.EOL Then

    Me.next_.Value.Indent = 0
    If Me.indent_stack_.Count > 0 Then Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1)
    Me.prev_ = Me.next_
    Me.next_ = Nothing
    Return Me.prev_
End If

If Me.prev_ IsNot Nothing AndAlso
    Me.prev_.InputToken <> SymbolTypes.EOL AndAlso
    Me.prev_.InputToken <> SymbolTypes.END AndAlso
    Me.indent_stack_.Count > 0 Then

    Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1)
End If

If Me.indent_stack_.Count > 0 AndAlso
    (Me.next_.InputToken = SymbolTypes._END OrElse
     Me.next_.Value.Indent < Me.indent_stack_(Me.indent_stack_.Count - 1)) Then

    Dim block_end As IToken(Of Node) = Me.CreateBlockEnd(Me.indent_stack_(Me.indent_stack_.Count - 1))
    Me.indent_stack_.RemoveAt(Me.indent_stack_.Count - 1)
    Me.prev_ = block_end
    Return block_end
End If

If Me.next_.InputToken = SymbolTypes._END Then

    Me.next_.Value.Indent = 0
    Me.prev_ = Me.next_
    Me.next_ = Nothing
    Return Me.prev_
End If

If Me.indent_stack_.Count = 0 Then

    Me.indent_stack_.Add(Me.next_.Value.Indent)
    Return Me.CreateBlockBegin(Me.next_.Value.Indent)
End If

If Me.prev_ IsNot Nothing AndAlso
    (Me.prev_.InputToken = SymbolTypes.EOL OrElse
     Me.prev_.InputToken = SymbolTypes.BEGIN OrElse
     Me.prev_.InputToken = SymbolTypes.END) Then

    Dim prev_indent As Integer = Me.indent_stack_(Me.indent_stack_.Count - 1)
    Dim next_indent As Integer = Me.next_.Value.Indent

    If prev_indent = next_indent Then

        Me.prev_ = Me.next_
        Me.next_ = Nothing

    ElseIf prev_indent < next_indent Then

        Me.indent_stack_.Add(next_indent)
        Me.prev_ = Me.CreateBlockBegin(next_indent)
    Else
        Me.indent_stack_.RemoveAt(Me.indent_stack_.Count - 1)
        Me.prev_ = Me.CreateBlockEnd(prev_indent)
    End If

Else
    Me.next_.Value.Indent = Me.indent_stack_(Me.indent_stack_.Count - 1)
    Me.prev_ = Me.next_
    Me.next_ = Nothing
End If

Return Me.prev_



Protected Overridable Function ReaderNext() As IToken(Of Node)

RESTART_:
    If Me.EndOfStream() Then Return Me.CreateEndOfToken_

    ' lex char
    Dim indent As Integer = 0
    Dim c As Char = Me.NextChar
    Do While Char.IsWhiteSpace(c)

        indent += 1
        Me.ReadChar()
        If Me.Parser.IsAccept(Me.eol_) AndAlso (c = Convert.ToChar(10) OrElse c = Convert.ToChar(13)) Then Return Me.CreateEndOfLine
        If Me.EndOfStream() Then Return Me.CreateEndOfToken_
        c = Me.NextChar
    Loop
    If c = "#"c Then

        Me.ReadLineComment()
        GoTo RESTART_
    End If

    Return Me.ReaderToken()
End Function

ちょっとシェルスクリプトっぽい言語を実装してみようか