Парсер вызова функции - FParsec - PullRequest
0 голосов
/ 03 февраля 2019

Я пытаюсь разобрать вызов функции, вот варианты:

add 8 2
add x y
add (inc x) (dec y)
funcWithoutArgs

В зависимости от того, как я распределяю свои анализаторы в коде, и, возможно, также, как они кодируются, я получаю ошибки,а также успешные, но нежелательные анализы.Например, это:

add 4 7

возвращает следующее значение AST:

[Call ("foo",[Number 4]);
 Number 7]

Поэтому он принимает только первый параметр.

Когда я это делаю:

foo x y

Он отправляет мне обратно этот AST:

[Call ("foo",[Call ("x",[Call ("y",[])])])]

И это не то, что я хочу, так как здесь каждый параметр вызывает следующий параметр как параметр.

Другой пример, когда я делаю это:

foo x y
inc x

Я получаю:

[Call ("foo",[Call ("x",[Call ("y",[Call ("inc",[Call ("x",[])])])])])]

Он делает то же, что и выше, но также вызывает код, следующий за строкой.Когда я спрашиваю свой анализатор о новой строке (см. Код), он отправляет мне следующее:

[Call ("foo",[]); Call ("x",[]); Call ("y",[]); Call ("inc",[]); Call ("x",[])]

Даже в скобках это не работает:

foo (x) (y)

Give:

[Call ("foo",[]); Call ("x",[]); Call ("y",[])]

И:

add (inc x) (dec y)

Да:

Error in Ln: 1 Col: 1
Note: The error occurred on an empty line.

The parser backtracked after:
  Error in Ln: 2 Col: 5
  add (inc x) (dec y)
      ^
  Expecting: end of input or integer number (32-bit, signed)

  The parser backtracked after:
    Error in Ln: 2 Col: 10
    add (inc x) (dec y)
             ^
    Expecting: ')'

[]

Короче говоря, мой анализатор вызовов функций не работает должным образом.Каждый раз, когда я что-то меняю, например, новую строку, попытку или другую иерархию, что-то не работает ... У вас есть идеи, как решить эту очень раздражающую проблему?

Вот минимумиспользованный функциональный код:

open FParsec

// Ast

type Expression =
    | Number of int
    | Call of string * Expression list

type Program = Expression list

// Tools

let private bws p =
    spaces >>? p .>>? spaces

let private suiteOf p =
    sepEndBy p spaces1

let inline private betweenParentheses p label =
    between (pstring "(") (pstring ")") p
    <?> (label + " between parentheses")

let private identifier =
    many1Satisfy2 isLetter (fun c -> isLetter c)

// Expressions

let rec private call = parse {
        let! call = pipe2 (spaces >>? identifier) (spaces >>? parameters)
                        (fun id parameters -> Call(id, parameters)) // .>>? newline
        return call
    }

and private parameters = suiteOf expression

and private callFuncWithoutArgs =
    identifier |>> fun id -> Call(id, [])

and private number = pint32 |>> Number

and private betweenParenthesesExpression =
    parse { let! ex = betweenParentheses expression "expression"
            return ex }

and private expression =
    bws (attempt betweenParenthesesExpression <|>
         attempt number <|>
         attempt call <|>
         callFuncWithoutArgs)

// -------------------------------

let parse code =
    let parser = many expression .>>? eof

    match run parser code with
        | Success(result, _, _) -> result
        | Failure(msg, _, _) ->
            printfn "%s" msg
            []

System.Console.Clear()

parse @"
add 4 7

foo x y

inc x

foo (x) (y)

add (inc x) (dec y)

" |> printfn "%A"

1 Ответ

0 голосов
/ 06 февраля 2019

Ваша основная проблема в том, что у вас неправильный высокоуровневый дизайн для вашего синтаксического анализатора.

Ваш текущий дизайн состоит в том, что выражение может быть:

  1. Выражение (a "подвыражение ", так сказать) между круглыми скобками (здесь нет проблем)
  2. Число (здесь нет проблем)
  3. Вызов с параметрами, который является идентификатором, за которым следует пробелсписок выражений (это основная часть проблемы)
  4. вызов без параметров, который является единым идентификатором (это способствует возникновению проблемы)

просмотр выражения foo x y, давайте применим эти правила по порядку, как это сделал бы парсер.Здесь нет круглых скобок, и foo не является числом, поэтому это либо 3, либо 4. Сначала мы пробуем 3. foo сопровождается x y: x y анализирует как выражение?Почему, да, это так: он анализируется как вызов с параметрами, где x - это функция, а y - это параметр.Поскольку x y соответствует 3, он анализирует в соответствии с правилом 3 без проверки правила 4, и поэтому foo x y соответствует, как foo (x y): вызов foo с одним параметром, который является вызовом x спараметр y.

Как это исправить?Ну, вы можете попробовать поменять местами порядок 3 и 4, чтобы перед вызовом с параметрами проверялся вызов функции без параметров (что могло бы x y анализировать как x. Но это не получится, потому что foo x yбудет соответствовать просто foo. Таким образом, размещение правила 4 перед правилом 3 здесь не работает.

Реальное решение состоит в том, чтобы разделить правила для выражения на два уровня. «Внутренний» уровень, которыйЯ назову «значение», может быть:

  1. Выражение в скобках
  2. Число
  3. Вызов функции без параметров

И «внешний» уровень, правила синтаксического анализа для выражений, будет выглядеть так:

  1. Вызов функции с параметрами, каждый из которых значения , not выражения
  2. Значение

Обратите внимание, что эти уровни синтаксического анализа являются взаимно рекурсивными, поэтому вам необходимо использовать createParserForwardedToRef в своей реализации.Давайте посмотрим, как foo x y будет проанализирован с этим дизайном:

First, foo анализирует как идентификатор, поэтому проверьте, может ли это быть вызов функции с параметрами.x анализирует как значение?Да, согласно правилу 3 ценностей.И y анализирует как значение?Да, согласно правилу 3 ценностей.Итак, foo x y анализирует как вызов функции.

А что насчет funcWithoutParameters?Это не выполнит правило 1 выражений, потому что за ним не следует список параметров.Таким образом, он будет проверен для правила 2 выражений, а затем совпадет с правилом 3 значений.

Хорошо, базовая проверка работоспособности псевдокода работает, поэтому давайте превратим это в код.Но сначала я упомяну о проблеме other в вашем парсере, о которой я еще не упомянул: вы не понимаете, что синтаксический анализатор FParsec spaces также соответствует символам новой строки .Поэтому, когда вы оборачиваете свой expression парсер в bws («между пробелами»), он также будет использовать новые строки после текста, который он анализирует.Поэтому, когда вы анализируете что-то вроде:

foo a b
inc c

* suiteOf expression видит список a b inc c и превращает все это в параметры для foo.В моем коде ниже я провел различие между парсером spaces в FParsec (который включает в себя новые строки) и парсером, который анализирует только горизонтальные пробелы * (пробел и табуляция, но не новая строка), используя каждый в соответствующем месте.В следующем коде реализован дизайн, о котором я упоминал в этом ответе, и его вывод выглядит мне правильно для всех написанных вами тестовых выражений:

open FParsec

// Ast

type Expression =
    | Number of int
    | Call of string * Expression list

type Program = Expression list

// Tools

let private justSpaces  = skipMany  (pchar ' ' <|> pchar '\t')
let private justSpaces1 = skipMany1 (pchar ' ' <|> pchar '\t')

let private bws p =
    spaces >>? p .>>? spaces

let private suiteOf p =
    sepEndBy1 p (justSpaces1)

let inline private betweenParentheses p label =
    between (pstring "(") (pstring ")") p
    <?> (label + " between parentheses")

let private identifier =
    many1Satisfy2 isLetter (fun c -> isLetter c)

// Expressions

let private expression, expressionImpl = createParserForwardedToRef()

let private betweenParenthesesExpression =
    parse { let! ex = betweenParentheses expression "expression"
            return ex }

let private callFuncWithoutArgs =
    (identifier |>> fun id -> Call(id, []))

let private number = pint32 |>> Number

let private value =
    justSpaces >>? (attempt betweenParenthesesExpression <|>
                    attempt number <|>
                    callFuncWithoutArgs)

let private parameters = suiteOf value

let rec private callImpl = parse {
        let! call = pipe2 (justSpaces >>? identifier) (justSpaces >>? parameters)
                          (fun id parameters -> Call(id, parameters))
        return call }

let call = callImpl

expressionImpl.Value <-
    bws (attempt call <|>
         value)

// -------------------------------

let parse code =
    let parser = many expression .>>? (spaces >>. eof)

    match run parser code with
        | Success(result, _, _) -> result
        | Failure(msg, _, _) ->
            printfn "%s" msg
            []

System.Console.Clear()

parse @"
add 4 7

foo x y

inc x

foo (x) (y)

add (inc x) (dec y)
" |> printfn "%A"

PS Я использовал следующий оператор, предложенный http://www.quanttec.com/fparsec/users-guide/debugging-a-parser.html дляочень помочь мне в отслеживании проблемы:

let (<!>) (p: Parser<_,_>) label : Parser<_,_> =
    fun stream ->
        printfn "%A: Entering %s" stream.Position label
        let reply = p stream
        printfn "%A: Leaving %s (%A)" stream.Position label reply.Status
        reply

Использование: превратить let parseFoo = ... в let parseFoo = ... <!> "foo".Затем вы получите поток отладочной информации в консоли, который выглядит примерно так:

(Ln: 2, Col: 20): Entering expression
(Ln: 3, Col: 1): Entering call
(Ln: 3, Col: 5): Entering parameters
(Ln: 3, Col: 5): Entering bwParens
(Ln: 3, Col: 5): Leaving bwParens (Error)
(Ln: 3, Col: 5): Entering number
(Ln: 3, Col: 6): Leaving number (Ok)
(Ln: 3, Col: 7): Entering bwParens
(Ln: 3, Col: 7): Leaving bwParens (Error)
(Ln: 3, Col: 7): Entering number
(Ln: 3, Col: 8): Leaving number (Ok)
(Ln: 3, Col: 8): Leaving parameters (Ok)
(Ln: 3, Col: 8): Leaving call (Ok)
(Ln: 3, Col: 8): Leaving expression (Ok)

Это очень помогает, когда вы пытаетесь выяснить, почему ваш анализатор не выполняет то, что вы ожидаете.

...