Языковой парсер с условиями .NET - PullRequest
1 голос
/ 05 июля 2011

Я ищу синтаксический анализатор языков, который может разрешать что-то вроде следующего:

(x=7 OR y=1) AND (x>0)

Я думал об использовании ANTLR Parser Generator. Существует ли более простой анализатор языков в более продвинутой среде .NET (.NET 3.5, .NET 4.0)?

Ответы [ 4 ]

4 голосов
/ 05 июля 2011

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

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

Просто для выражения вы можете закодировать анализатор рекурсивного спуска непосредственно из BNF для вашего выражения. Убедись, что ты первый левый фактор - общность для каждого правила, например, не

 SUM = TERM '+' TERM ;
 SUM = TERM '-' TERM ;

но

 SUM = TERM ( '+' TERM |  '-' TERM ) ;

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

Так что для грамматики:

EXP = SUM ;
SUM = TERM ( '+' TERM |  '-' TERM ) ;
TERM = PRIMARY ( '*' PRIMARY | '/' PRIMARY ) ;
PRIMARY = '-' PRIMARY | '(' EXP ')' | NUMBER | VARIABLE ;

И дан буфер символов INPUT, содержащий выражение, и глобальная переменная I индекс в INPUT, код примерно:

float EXP()
{  return SUM();
}

float SUM()
{  float t=TERM();
   if MATCH("+") return t+TERM(); 
   if MATCH("-") return t-TERM();
   throw SYNTAXERROR;
}

float TERM()
{  float t= PRIMARY();
   if MATCH("*") return t*PRIMARY();
   if MATCH("/") return t/PRIMARY();
   throw SYNTAXERROR;
}

float PRIMARY()
{  float t;
   if MATCH("-") return -PRIMARY();
   if MATCH("(")
       { t=EXP();
         if MATCH(")") return t;
         else throw SYNTAXERROR
       }
   try t=NUMBER();
   catch SYNTAXERROR
      return VARIABLE();
   endtry
}

float NUMBER()  // simple float input conversion
{ float t=0;
  fractiondigits=0;
  exponent=0;
  switch INPUT(I)
  {  case "0".."9":
     { t=t*10+INPUT(I)-"0"; I++;
       while ISDIGIT(INPUT(I))
          { t=t*10+INPUT(I)-"0"; I++ }
       if MATCH(".")
          goto collect_fraction;
       else goto collect_exponent
      }
     case ".": goto collect_fraction;
     default: throw SYNTAXERROR
  }
  collect_fraction:
     while ISDIGIT(INPUT(I))
       { t=t*10+INPUT(I)-"0"; I++; fraction_digits++; }
  collect_exponent:
     if MATCH("E")
       { sign=false;
         if MATCH("-") sign=true;
         if !ISDIGIT(INPUT(I)) throw SYNTAXERROR;
         while ISDIGIT(INPUT(I))
           { exponent=exponent*10+INPUT(I)-"0"; I++; }
         if sign=true then exponenent=-exponent;
       }
       return t*10^(exponent-fractiondigits);
}

float VARIABLE() // handles single letter variable names.
{  if ISLETTER(INPUT(I))
   { I++;
     return VARIABLEVALUE[INPUT(I)-"A"]
   }
   else throw SYNTAXERROR
}

 boolean MATCH(c: char)
 {  if INPUT(I)==c
       {  I++;
          return true;
       }
    else return false

Вы, очевидно, захотите написать грамматику для выражений, которые вы хотите оценить. Но при условии, что вы добавляете только реляционные операторы и операторы AND, OR и NOT, следуя этому стилю, вам потребуется около 30 минут, чтобы все это кодировать.

Это не учитывает, как входное выражение было собрано в буфере INPUT, и не учитывает вопрос о том, как переменные получают значения; Я предположил, что карта имен переменных к значениям была каким-то волшебным образом заполнена заранее. Если вы хотите разрешить простые назначения, а также выражения, просто немного увеличьте BNF, добавив правило, разрешающее назначения, например,

EXP = VARIABLE ':=' EXP ;

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

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

0 голосов
/ 06 декабря 2011

Вы можете взглянуть на Ирония и SampleExpressionEvaluator в примерах.

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

0 голосов
/ 05 июля 2011

Если вам просто нужны простые выражения, как в вашем примере, вы можете использовать NCalc .Быстрый и простой в использовании.

0 голосов
/ 05 июля 2011

Для простых выражений, подобных приведенным, я написал бы парсер рекурсивного спуска.Сначала запишите свой BNF, а затем просто закодируйте его.Вы можете использовать один из трех подходов в плане оценки:

  1. Emit выражения деревьев
  2. Emit IL
  3. Свернуть свой собственный байт-код VM

Я бы пошел с (1), так как это самый простой подход.

...