Информация об изменениях

Сообщение Re[13]: [Nitra] Пример простого языка вычисляющего выражения от 10.12.2015 10:57

Изменено 10.12.2015 13:00 VladD2

Здравствуйте, novitk, Вы писали:

N>Это абсолютно обычный FP-код.


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

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

Вот это:
syntax module SampleSyntax
{
  using Nitra.Core;
  using Nitra.CStyleComments;

  regex Keyword = "var";

  [Reference] token Reference = !Keyword IdentifierBody;
  [Name]      token Name    = !Keyword IdentifierBody;

  [StartRule] syntax TopRule = (VariableDeclaration nl)*;

  syntax VariableDeclaration = "var" sm Name sm "=" sm Expression ";";

  syntax Expression
  {
    | [SpanClass(Number)] Num = Digits {  Digits = ['0'..'9']+; }
    | Braces = "(" Expression ")";
    | Variable = Reference;
  precedence Sum:
    | Sum = Expression sm Operator="+" sm Expression;
    | Sub = Expression sm Operator="-" sm Expression;
  precedence Mul:
    | Mul = Expression sm Operator="*" sm Expression;
    | Div = Expression sm Operator="/" sm Expression;
  precedence Unary:
    | Plus  = Operator="+" Expression
    | Minus = Operator="-" Expression
  }
}

грамматика.
А это:
[scala]
class Expr;
class BinOp(val evalF: (Int, Int) => Int, val expr: Expr);
def mkBinOp = (new BinOp(_, _)).curried
val binOpGroups = Seq(
Seq("*" -> mkBinOp(_*_), "/" -> mkBinOp(_/_)),
Seq("^^" -> mkBinOp(Math.pow(_, _).toInt)),
Seq("+" -> mkBinOp(_+_), "+" -> mkBinOp(_+_)))

case class NumberExpr(num: Int) extends Expr;
case class VarExpr(name: String) extends Expr;
case class OpExpr(expr: Expr, subExprs: List[BinOp]) extends Expr;
class VarDef(val name: String, val expr: Expr)

def makeOpExpr(p: ~[Expr, List[BinOp]]): Expr = p match {
case f ~ Nil => f
case f ~ l => OpExpr(f, l)
}
def binOpGroupParser(termAbove: Parser[Expr], opGroup: Seq[(String, Expr => BinOp)]): Parser[Expr] = {
val opGroupParsers = opGroup.map { case (opCode, constr) => opCode ~> termAbove ^^ constr }.reduce(_|_)
(termAbove ~ rep(opGroupParsers)) ^^ makeOpExpr
}

def factor: Parser[Expr] = wholeNumber ^^ (s => NumberExpr(s.toInt)) | ident ^^ VarExpr | "(" ~> expr <~ ")"
def expr: Parser[Expr] = binOpGroups.foldLeft(factor)(binOpGroupParser(_, _))
def varDef: Parser[VarDef] = ("var" ~> ident) ~ ("=" ~> expr <~ ";") ^^ { case n ~ e => new VarDef(n, e) }
def varDefParseAll(in: CharSequence): ParseResult[VarDef] = parseAll(varDef, in)
[/scala]
закарючки.

N>Вам parsec не нравиться что-ли?


Конечно, нет. Грамматика ANTLR мне нравится, а закарчки на Хаскеле эмулирующие грамматику — нет.

N>Его там всего 4 строчки. Предположи, что я взял ANTLR или что тебе там нравится, и забудь.


Зачем мне что-то предполагать? Ты обещал, что на Скале будет проще. Я вижу что получился какой-то не читаемый пипец с кучей упрощений, не поддерживающий IDE ни в каком виде и в добавок еще и потенциально тормозной из-за комбинаторов.

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

N>O чем ты?

Вот об этом "(s => NumberExpr(s.toInt))", об это "{ case n ~ e => new VarDef(n, e) }" и о другой грязи.

Я уже говорил, о том, что это мы проходили уже очень давно. У нас есть в разы более чистый путь — rule-методы:
  syntax Expr
  {
    Value() : double;

   | [SpanClass(Number)] Number
      {
        regex Digit = ['0'..'9'];
        regex Number = Digit+ ('.' Digit+)?;
 
        override Value = double.Parse(GetText(Number));
      }

    | ParenthesesParentheses = '(' Expr ')' { override Value = Expr.Value(); }
  precedence Additive:
    | Add         = Expr '+' Expr { override Value = Expr1.Value() + Expr2.Value(); }
    | Sub         = Expr '-' Expr { override Value = Expr1.Value() - Expr2.Value(); }
  precedence Multiplicative:
    | Mul         = Expr '*' Expr { override Value = Expr1.Value() * Expr2.Value(); }
    | Div         = Expr '/' Expr { override Value = Expr1.Value() / Expr2.Value(); }
  precedence Power:
    | Pow      = Expr '^' Expr precedence 30 right-associative { override Value = Math.Pow(Expr1.Value(), Expr2.Value()); }
  precedence Unary:
    | Neg         = '-' Expr { override Value = -Expr.Value(); }
  }

это выглядит не плохо только на примитивных примерах. Смешение кода и грамматики — плохая идея. Да и вычисления прямо на дереве разбора — тоже.

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

Так же есть и чисто технические аспекты которые на практике так же важны.

N>EvalContext абсолютно независим от парсера. Я поместил его в один файл только для удобства оформления.


Сомнительное удобство, кстати. Погоня за минимизацией символов. Людям это не надо. Людям нужно минимизировать число сущностей которые они обязаны одновременно держать в голове. А количество строк их абсолютно не волнует. Иначе все писали бы даже не на Хаскеле, а на каком нибудь J или K.
Re[13]: [Nitra] Пример простого языка вычисляющего выражения
Здравствуйте, novitk, Вы писали:

N>Это абсолютно обычный FP-код.


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

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

Вот это:
syntax module SampleSyntax
{
  using Nitra.Core;
  using Nitra.CStyleComments;

  regex Keyword = "var";

  [Reference] token Reference = !Keyword IdentifierBody;
  [Name]      token Name    = !Keyword IdentifierBody;

  [StartRule] syntax TopRule = (VariableDeclaration nl)*;

  syntax VariableDeclaration = "var" sm Name sm "=" sm Expression ";";

  syntax Expression
  {
    | [SpanClass(Number)] Num = Digits {  Digits = ['0'..'9']+; }
    | Braces = "(" Expression ")";
    | Variable = Reference;
  precedence Sum:
    | Sum = Expression sm Operator="+" sm Expression;
    | Sub = Expression sm Operator="-" sm Expression;
  precedence Mul:
    | Mul = Expression sm Operator="*" sm Expression;
    | Div = Expression sm Operator="/" sm Expression;
  precedence Unary:
    | Plus  = Operator="+" Expression
    | Minus = Operator="-" Expression
  }
}

грамматика.
А это:
  class Expr;
  class BinOp(val evalF: (Int, Int) => Int, val expr: Expr);
  def mkBinOp = (new BinOp(_, _)).curried
  val binOpGroups = Seq(
      Seq("*" -> mkBinOp(_*_), "/" -> mkBinOp(_/_)),
      Seq("^^" -> mkBinOp(Math.pow(_, _).toInt)),
      Seq("+" -> mkBinOp(_+_), "+" -> mkBinOp(_+_)))

  case class NumberExpr(num: Int) extends Expr;
  case class VarExpr(name: String) extends Expr;
  case class OpExpr(expr: Expr, subExprs: List[BinOp]) extends Expr;
  class VarDef(val name: String, val expr: Expr)
  
  def makeOpExpr(p: ~[Expr, List[BinOp]]): Expr = p match {
    case f ~ Nil => f
    case f ~ l => OpExpr(f, l)
  }
  def binOpGroupParser(termAbove: Parser[Expr], opGroup: Seq[(String, Expr => BinOp)]): Parser[Expr] = {
    val opGroupParsers = opGroup.map { case (opCode, constr) => opCode ~> termAbove ^^ constr }.reduce(_|_)
    (termAbove ~ rep(opGroupParsers)) ^^ makeOpExpr
  }  

  def factor: Parser[Expr] = wholeNumber ^^ (s => NumberExpr(s.toInt)) | ident ^^ VarExpr | "(" ~> expr <~ ")"
  def expr: Parser[Expr] = binOpGroups.foldLeft(factor)(binOpGroupParser(_, _))
  def varDef: Parser[VarDef] = ("var" ~> ident) ~ ("=" ~> expr <~ ";") ^^ { case n ~ e => new VarDef(n, e) }
  def varDefParseAll(in: CharSequence): ParseResult[VarDef] = parseAll(varDef, in)

закарючки.

N>Вам parsec не нравиться что-ли?


Конечно, нет. Грамматика ANTLR мне нравится, а закарчки на Хаскеле эмулирующие грамматику — нет.

N>Его там всего 4 строчки. Предположи, что я взял ANTLR или что тебе там нравится, и забудь.


Зачем мне что-то предполагать? Ты обещал, что на Скале будет проще. Я вижу что получился какой-то не читаемый пипец с кучей упрощений, не поддерживающий IDE ни в каком виде и в добавок еще и потенциально тормозной из-за комбинаторов.

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

N>O чем ты?

Вот об этом "(s => NumberExpr(s.toInt))", об это "{ case n ~ e => new VarDef(n, e) }" и о другой грязи.

Я уже говорил, о том, что это мы проходили уже очень давно. У нас есть в разы более чистый путь — rule-методы:
  syntax Expr
  {
    Value() : double;

   | [SpanClass(Number)] Number
      {
        regex Digit = ['0'..'9'];
        regex Number = Digit+ ('.' Digit+)?;
 
        override Value = double.Parse(GetText(Number));
      }

    | ParenthesesParentheses = '(' Expr ')' { override Value = Expr.Value(); }
  precedence Additive:
    | Add         = Expr '+' Expr { override Value = Expr1.Value() + Expr2.Value(); }
    | Sub         = Expr '-' Expr { override Value = Expr1.Value() - Expr2.Value(); }
  precedence Multiplicative:
    | Mul         = Expr '*' Expr { override Value = Expr1.Value() * Expr2.Value(); }
    | Div         = Expr '/' Expr { override Value = Expr1.Value() / Expr2.Value(); }
  precedence Power:
    | Pow      = Expr '^' Expr precedence 30 right-associative { override Value = Math.Pow(Expr1.Value(), Expr2.Value()); }
  precedence Unary:
    | Neg         = '-' Expr { override Value = -Expr.Value(); }
  }

это выглядит не плохо только на примитивных примерах. Смешение кода и грамматики — плохая идея. Да и вычисления прямо на дереве разбора — тоже.

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

Так же есть и чисто технические аспекты которые на практике так же важны.

N>EvalContext абсолютно независим от парсера. Я поместил его в один файл только для удобства оформления.


Сомнительное удобство, кстати. Погоня за минимизацией символов. Людям это не надо. Людям нужно минимизировать число сущностей которые они обязаны одновременно держать в голове. А количество строк их абсолютно не волнует. Иначе все писали бы даже не на Хаскеле, а на каком нибудь J или K.