Что лучший способ состоит в том, чтобы записать синтаксическому анализатору вручную? [закрытый]

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

Предположим, что у нас очень простой фрейм данных:

dat <- data.frame(x = 1:4,
                  y = 5:8)

, и мы хотели бы написать функцию, которая создает новый столбец z, который представляет собой сумму столбцов x и y.

Очень распространенный камень преткновения здесь состоит в том, что естественная (но некорректная) попытка часто выглядит так:

foo <- function(df,col_name,col1,col2){
      df$col_name <- df$col1 + df$col2
      df
}

#Call foo() like this:    
foo(dat,z,x,y)

Проблема здесь в том, что df$col1 t оценить выражение col1. Он просто ищет столбец в df, буквально называемый col1. Это поведение описано в ?Extract в разделе «Рекурсивные (похожие на список) объекты».

Самое простое и наиболее часто рекомендуемое решение - это просто переключиться с $ на [[ и передать аргументы функции как строки:

new_column1 <- function(df,col_name,col1,col2){
    #Create new column col_name as sum of col1 and col2
    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column1(dat,"z","x","y")
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

Это часто считается «лучшей практикой», так как это самый сложный метод. Передача имен столбцов в виде строк примерно такая же однозначная, как вы можете получить.

Следующие два варианта более продвинутые. Многие популярные пакеты используют эти методы, но использование их хорошо требует большей осторожности и умения, поскольку они могут вводить тонкие сложности и непредвиденные точки отказа. Этот раздел книги Хэдли Advanced R является отличной ссылкой для некоторых из этих проблем.

Если вы действительно хотите сохранить пользователя от ввода всех этих кавычки, одним из вариантов может быть преобразование голой, некотируемых имен столбцов в строки с помощью deparse(substitute()):

new_column2 <- function(df,col_name,col1,col2){
    col_name <- deparse(substitute(col_name))
    col1 <- deparse(substitute(col1))
    col2 <- deparse(substitute(col2))

    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column2(dat,z,x,y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

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

Наконец, если мы хотим получить действительно фантазию, мы можем решить, что вместо того, чтобы проходить в именах добавляемых двух столбцов мы хотели бы быть более гибкими и допускать другие комбинации двух переменных. В этом случае мы, скорее всего, прибегнем к использованию eval() в выражении, включающем два столбца:

new_column3 <- function(df,col_name,expr){
    col_name <- deparse(substitute(col_name))
    df[[col_name]] <- eval(substitute(expr),df,parent.frame())
    df
}

Просто для удовольствия я все еще использую deparse(substitute()) для имени нового столбца , Здесь будет работать все следующее:

> new_column3(dat,z,x+y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12
> new_column3(dat,z,x-y)
  x y  z
1 1 5 -4
2 2 6 -4
3 3 7 -4
4 4 8 -4
> new_column3(dat,z,x*y)
  x y  z
1 1 5  5
2 2 6 12
3 3 7 21
4 4 8 32

Итак, короткий ответ в основном: передать имена столбцов data.frame в виде строк и использовать [[ для выбора отдельных столбцов. Только начинайте разбираться в eval, substitute и т. Д., Если вы действительно знаете, что делаете.

19
задан Simon 4 August 2009 в 08:23
поделиться

16 ответов

Я настоятельно рекомендую язык F # в качестве предпочтительного языка для анализа на платформе .NET. Его корни в семействе языков ML означают, что он имеет отличную поддержку языкового программирования.

Дискриминационные объединения и сопоставление с образцом позволяют создать очень сжатую и мощную спецификацию вашего AST. Функции высшего порядка позволяют определять операции синтаксического анализа и их состав. Первоклассная поддержка монадических типов позволяет неявно обрабатывать управление состоянием, значительно упрощая состав синтаксических анализаторов. Мощный вывод типов очень помогает при определении этих (сложных) типов. И все это можно указать и выполнить в интерактивном режиме, что позволяет быстро создавать прототипы.

Стефан Толксдорф применил это на практике с помощью своей библиотеки комбинаторов синтаксического анализатора FParsec

Из его примеров мы видим, насколько естественно определяется AST:

type expr =
    | Val of string
    | Int of int
    | Float of float
    | Decr of expr

type stmt =
    | Assign of string * expr
    | While of expr * stmt
    | Seq of stmt list
    | IfThen of expr * stmt
    | IfThenElse of expr * stmt * stmt
    | Print of expr

type prog = Prog of stmt list

реализация синтаксического анализатора (частично исключена) столь же лаконична :

let stmt, stmtRef = createParserForwardedToRef()

let stmtList = sepBy1 stmt (ch ';')

let assign =
    pipe2 id (str ":=" >>. expr) (fun id e -> Assign(id, e))

let print = str "print" >>. expr |>> Print

let pwhile =
    pipe2 (str "while" >>. expr) (str "do" >>. stmt) (fun e s -> While(e, s))

let seq =
    str "begin" >>. stmtList .>> str "end" |>> Seq

let ifthen =
    pipe3 (str "if" >>. expr) (str "then" >>. stmt) (opt (str "else" >>. stmt))
          (fun e s1 optS2 ->
               match optS2 with
               | None    -> IfThen(e, s1)
               | Some s2 -> IfThenElse(e, s1, s2))

do stmtRef:= choice [ifthen; pwhile; seq; print; assign]


let prog =
    ws >>. stmtList .>> eof |>> Prog

Например, во второй строке stmt и ch - парсеры, а sepBy1 - комбинатор монадического парсера, который принимает два простых парсера и возвращает комбинированный парсер. В этом случае sepBy1 p sep возвращает синтаксический анализатор, который анализирует одно или несколько вхождений p , разделенных sep . Таким образом, вы можете увидеть, как быстро мощный синтаксический анализатор можно объединить с простыми синтаксическими анализаторами. Поддержка F # переопределенных операторов также позволяет использовать краткую инфиксную нотацию, например комбинатор последовательности и комбинатор выбора могут быть указаны как >>. и <|> .

Удачи,

Дэнни

17
ответ дан 30 November 2019 в 02:18
поделиться

JFlex является реализацией гибкого провода для Java, и существует теперь порт C# того проекта http://sourceforge.net/projects/csflex/ . Также, кажется, существует порт C# происходящего КУБКА, который может быть найден здесь: http://sourceforge.net/projects/datagraph/

я также рекомендовал бы избежать руки, обрабатывающей Ваше собственное решение. Я попробовал это однажды сам для очень простого языка (часть университетского проекта), и это было невероятно трудоемким и трудным. Также чрезвычайно трудно поддержать и измениться когда-то записанный.

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

0
ответ дан 30 November 2019 в 02:18
поделиться

Взгляд gplex и gppg, лексический анализатор и парсеры-генераторы для.NET. Они работают хорошо и основаны на том же (и почти совместимый) вход как закон и yacc, который относительно прост в использовании.

0
ответ дан 30 November 2019 в 02:18
поделиться

Я также голосовал бы за использование существующего синтаксического анализатора + лексический анализатор.

единственная причина I видит в выполнении его вручную:

  • , если Вы хотите, чтобы что-то относительно простое (как проверка/парсинг некоторого входа)
  • изучило/поняло принципы.
0
ответ дан 30 November 2019 в 02:18
поделиться

Причина Вы не хотите таблично-управляемый синтаксический анализатор, состоит в том, что Вы не сможете создать разумные сообщения об ошибках. Это хорошо для сгенерированного языка, но не того, где люди вовлечены. Сообщения об ошибках, произведенные подобными языку C компиляторами, представляют достаточные свидетельства, которые люди могут адаптировать к чему-либо, неважно, как плохо.

0
ответ дан 30 November 2019 в 02:18
поделиться

Хорошо, если Вы не возражаете использовать другой инструмент компилятора компилятора как ANTLR, я предлагаю, чтобы Вы смотрели на Coco/R

, я использовал его в прошлом, и это было довольно хорошо...

0
ответ дан 30 November 2019 в 02:18
поделиться

На вашем месте я сделал бы, чтобы другие пошли в ANTLRv3 с помощью ANTLRWorks GUI, который дает Вам очень удобный способ протестировать Вашу грамматику. Мы используем ANTLR в нашем проекте и хотя кривая обучения может быть немного крутой в начале, после того как Вы узнаете, что это довольно удобно. Также на их электронной новостной рассылке существует много людей, которые очень услужливы.

пз. IIRC у них также есть грамматика SQL, на которую Вы могли смотреть.

hth

1
ответ дан 30 November 2019 в 02:18
поделиться

В C/Unix традиционный путь состоит в том, чтобы использовать закон и yacc. С GNU эквивалентные инструменты являются гибким проводом и бизоном. Я не знаю для Windows/C#.

1
ответ дан 30 November 2019 в 02:18
поделиться

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

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

Пример:

Data = Object | Value;
Value = Ident, '=', Literal;
Object = '{', DataList, '}';
DataList = Data | DataList, Data;


ParseData {
  if PeekToken = '{' then 
    return ParseObject;
  if PeekToken = Ident then
    return ParseValue;
  return Error;
}

ParseValue {
  ident = TokenValue;
  if NextToken <> '=' then 
    return Error;
  if NextToken <> Literal then
    return Error;
  return(ident, TokenValue);
 }

ParseObject {
  AssertToken('{');
  temp = ParseDataList;
  AssertToken('}');
  return temp;
}

ParseDataList {
  data = ParseData;
  temp = []
  while Ok(data) {
    temp = temp + data;
    data = ParseData;
  }
}
3
ответ дан 30 November 2019 в 02:18
поделиться

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

, Например, это производство:

A => aC 
A => bD
A => eF

становится чем-то простым как:

int A() {
   chr = read();
   switch char
     case 'a': C();
     case 'b': D();
     case 'e': F();
     default: throw_syntax_error('A', chr);
}

И нет никаких намного более трудных случаев здесь (что является более трудным, удостоверяются, что Ваша грамматика находится в точно правильной форме, но это позволяет Вам управление, которое Вы упомянули).

Ссылка Anton , также кажется превосходным.

6
ответ дан 30 November 2019 в 02:18
поделиться

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

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

7
ответ дан 30 November 2019 в 02:18
поделиться

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

, Если Вы подлежите синтаксическому анализатору RD, необходимо проверить, что грамматика SQL не является леворекурсивной (и устраните рекурсию при необходимости), и затем в основном запишите функцию для каждого грамматического правила. См. это для дальнейшей ссылки.

10
ответ дан 30 November 2019 в 02:18
поделиться

Добавление моей речи к хору в пользу рекурсивного спуска (LL1). Они просты, быстро, и IMO, нисколько трудно для поддержания.

Однако бросают хороший взгляд на Ваш язык, чтобы удостовериться, что это - LL1. Если у Вас есть какой-либо синтаксис как C, имеет, как ((((тип)) нечто) []) , где Вам, возможно, придется убывать несколько слоев круглых скобок перед ровным обнаружением, смотрите ли Вы на тип, переменную или выражение, то LL1 будет очень трудными, и восходящими победами.

7
ответ дан 30 November 2019 в 02:18
поделиться

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

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

я - вполне уверенные реализации ANTLR синтаксический анализатор с рекурсивным спуском так или иначе: существует упоминание о нем в интервью о ANTLR 3.0.

я также нашел серию сообщений в блоге приблизительно запись синтаксического анализатора в C#. Это кажется довольно нежным.

2
ответ дан 30 November 2019 в 02:18
поделиться

Рискуя оскорбить OP, писать синтаксический анализатор для большого языка, такого как SQL какого-либо конкретного поставщика, вручную, когда доступны хорошие инструменты генератора синтаксического анализатора (такие как ANTLR), просто безумие. Вы потратите гораздо больше времени, переписывая свой синтаксический анализатор вручную, чем исправляя «крайние случаи» с помощью генератора синтаксического анализатора, и вам неизбежно придется возвращаться и пересматривать синтаксический анализатор в любом случае по мере продвижения стандартов SQL или вы обнаружите, что неправильно поняли что-то другое. Если вы недостаточно хорошо разбираетесь в своей технологии синтаксического анализа, потратьте время на ее понимание. Вам не понадобятся месяцы, чтобы понять, как справиться с крайними случаями с генератором синтаксического анализатора, и вы уже признали это, что готовы потратить месяцы, делая это вручную.

Сказав это, если вы это делаете одержим переделкой вручную,

2
ответ дан 30 November 2019 в 02:18
поделиться

Не существует "единственного лучшего" пути. В зависимости от ваших потребностей вам может понадобиться восходящий (LALR1) или рекурсивный спуск (LLk). В статьях , подобных этой , приводятся личные причины предпочтения LALR (1) (снизу вверх) перед LL (k). Однако у каждого типа парсера есть свои преимущества и недостатки. Обычно LALR будет быстрее, поскольку машина с конечным числом состояний генерируется как таблица поиска.

Чтобы выбрать то, что подходит вам, исследуйте вашу ситуацию; ознакомьтесь с инструментами и технологиями. Начинать с некоторых статей Википедии LALR и LL - неплохой выбор. В обоих случаях ВСЕГДА следует начинать с указания грамматики в BNF или EBNF . Я предпочитаю EBNF за его лаконичность.

После того, как вы сосредоточились на том, что вы хотите сделать и как представить это в виде грамматики, (BNF или EBNF) попробуйте несколько различных инструментов и запустите их по репрезентативным образцам текста для анализа.

Как ни странно:

Однако я слышал, что LL (k) более гибкий. Я никогда не удосужился выяснить это для себя.Из своего небольшого опыта создания синтаксического анализатора я заметил, что, независимо от того, является ли это LALR или LL (k), лучший способ выбрать то, что лучше всего подходит для ваших нужд, - это начать с написания грамматики. Я написал свою собственную библиотеку шаблонов построителя синтаксического анализатора C ++ EBNF RD, использовал Lex / YACC и написал небольшой синтаксический анализатор R-D. Это было растянуто на большую часть 15 лет, и я потратил не более 2 месяцев на самый длинный из трех проектов.

2
ответ дан 30 November 2019 в 02:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: