Parsing refactoring (#111)

* WIP, nothing work

* delete ParExpr, ParSem, rewrite exprs, RootCompanion, WIP

* list of tokens to tree

* fix leaf ast

* move expr

* it works?

* small changes

* handle errors

* some refactoring

* WIP

* WIP

* merge

* comments, cleaning up

* split Companion and RootCompanion

* clean up

* couple tests

* fix tests

* ParPrefix?

* Expr code reorganisation

* ParExpr/ParSem returned

* Test fixed

Co-authored-by: dmitry <dmitry@fluence.one>
This commit is contained in:
Dima 2021-05-20 16:12:13 +03:00 committed by GitHub
parent 3d6cc02382
commit 720de27f14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 515 additions and 275 deletions

View File

@ -1,8 +1,23 @@
service Test("123"):
doSomething: -> ()
service Peer("peer"):
is_connected: string -> bool
func b(me: string, a: bool, b: bool):
on me:
if a:
if b:
Test.doSomething()
service Op("op"):
identity: -> ()
data User:
peer_id: string
relay_id: string
name: string
service Test("test"):
getUserList: -> []User
doSomething: -> bool
func betterMessage(relay: string):
on relay:
Peer.is_connected("something")
par isOnline <- Peer.is_connected(relay)
par on "quray":
Peer.is_connected("qurara")
if isOnline:
Test.doSomething()

View File

@ -1,6 +1,6 @@
package aqua
import aqua.parser.Ast
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
import aqua.parser.lift.{FileSpan, LiftParser, Span}
import cats.data.ValidatedNec
@ -9,13 +9,21 @@ object Aqua {
def parseString(input: String): ValidatedNec[AquaError, Ast[Span.F]] =
Ast
.fromString[Span.F](input)
.leftMap(_.map(pe => SyntaxError(pe.failedAtOffset, pe.expected)))
.leftMap(_.map {
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
case LexerError(pe) => SyntaxError(pe.failedAtOffset, pe.expected)
})
def parseFileString(name: String, input: String): ValidatedNec[AquaError, Ast[FileSpan.F]] = {
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
Ast
.fromString[FileSpan.F](input)
.leftMap(_.map(pe => SyntaxError(pe.failedAtOffset, pe.expected)))
.leftMap(_.map {
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1.span, message)
case FuncReturnError(point, message) => CustomSyntaxError(point._1.span, message)
case LexerError(pe) => SyntaxError(pe.failedAtOffset, pe.expected)
})
}
}

View File

@ -10,6 +10,23 @@ sealed trait AquaError {
def showForConsole(script: String): String
}
case class CustomSyntaxError(span: Span, message: String) extends AquaError {
override def showForConsole(script: String): String =
span
.focus(Eval.later(LocationMap(script)), 2)
.map(
_.toConsoleStr(
message,
Console.RED
)
)
.getOrElse(
"(offset is beyond the script, syntax errors) Error: " + Console.RED + message
.mkString(", ")
) + Console.RESET + "\n"
}
case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) extends AquaError {
override def showForConsole(script: String): String =

View File

@ -1,12 +1,11 @@
package aqua.parser
import aqua.parser.expr._
import aqua.parser.head.{HeadExpr, HeaderExpr, ImportExpr}
import aqua.parser.lexer.Token._
import aqua.parser.head.{HeadExpr, HeaderExpr}
import aqua.parser.lift.LiftParser
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.data.{Chain, Validated, ValidatedNec}
import cats.free.Cofree
import cats.parse.{Parser => P, Parser0 => P0}
import cats.parse.{Parser0 => P0}
import cats.{Comonad, Eval}
case class Ast[F[_]](head: Ast.Head[F], tree: Ast.Tree[F]) {
@ -19,29 +18,16 @@ object Ast {
type Tree[F[_]] = Cofree[Chain, Expr[F]]
type Head[F[_]] = Cofree[Chain, HeaderExpr[F]]
def treeExprs: List[Expr.Companion] =
ServiceExpr :: AliasExpr :: DataStructExpr :: ConstantExpr :: FuncExpr :: Nil
def parser[F[_]: LiftParser: Comonad](): P0[ValidatedNec[ParserError[F], Ast[F]]] =
(HeadExpr.ast[F].with1 ~ RootExpr.ast[F]()).map { case (head, bodyMaybe) =>
bodyMaybe.map(Ast(head, _))
}
def headExprs: List[HeaderExpr.Companion] =
ImportExpr :: Nil
def fromString[F[_]: LiftParser: Comonad](script: String): ValidatedNec[ParserError[F], Ast[F]] =
parser[F]()
.parseAll(script) match {
case Right(value) => value
case Left(e) => Validated.invalidNec(LexerError[F](e))
}
def parser[F[_]: LiftParser: Comonad](ps: Indent): P0[Ast[F]] =
((P.repSep0(P.oneOf(headExprs.map(_.ast[F])), ` \n+`) <* ` \n+`).? ~ P.repSep0(
P.oneOf(treeExprs.map(_.ast[F](ps))),
` \n+`
)).surroundedBy(` \n+`.?)
.map {
case (Some(head), tree) => Chain.fromSeq(head) -> Chain.fromSeq(tree)
case (_, tree) => Chain.empty[Head[F]] -> Chain.fromSeq(tree)
}
.map { case (hs, ls) =>
Ast(Cofree(HeadExpr(), Eval.now(hs)), Cofree(RootExpr(), Eval.now(ls)))
}
def fromString[F[_]: LiftParser: Comonad](script: String): ValidatedNec[P.Error, Ast[F]] =
Validated
.fromEither(
parser[F](Indent()).parseAll(script)
)
.leftMap(NonEmptyChain.one)
}

View File

@ -3,58 +3,221 @@ package aqua.parser
import aqua.parser.Ast.Tree
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import cats.data.Chain
import aqua.parser.lift.LiftParser._
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.free.Cofree
import cats.parse.{Parser => P}
import cats.syntax.comonad._
import cats.{Comonad, Eval}
import Chain.:==
trait Expr[F[_]]
abstract class Expr[F[_]](val companion: Expr.Companion) {
lazy val isBlock: Boolean = companion match {
case _: Expr.Block => true
case _ => false
}
}
object Expr {
trait Companion {
def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Ast.Tree[F]]]
}
trait Lexem extends Companion {
def p[F[_]: LiftParser: Comonad]: P[Expr[F]]
def ast[F[_]: LiftParser: Comonad](ps: Indent): P[Ast.Tree[F]]
def readLine[F[_]: LiftParser: Comonad]: P[Ast.Tree[F]] =
p.map(Cofree[Chain, Expr[F]](_, Eval.now(Chain.empty)))
}
def defer(companion: => Companion): Companion = new Companion {
override def p[F[_]: LiftParser: Comonad]: P[Expr[F]] = companion.p[F]
trait Leaf extends Lexem {
override def ast[F[_]: LiftParser: Comonad](ps: Indent): P[Tree[F]] = companion.ast[F](ps)
override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Tree[F]]] =
p[F].map(e =>
Validated.validNec(
Cofree[Chain, Expr[F]](
e,
Eval.now(Chain.empty)
)
)
)
}
abstract class Leaf extends Companion {
def defer(companion: => Lexem): Lexem = new Lexem {
private lazy val c = companion
override def ast[F[_]: LiftParser: Comonad](ps: Indent): P[Ast.Tree[F]] =
p[F].map(Cofree[Chain, Expr[F]](_, Eval.now(Chain.empty)))
override def readLine[F[_]: LiftParser: Comonad]: P[Ast.Tree[F]] = c.readLine[F]
override def p[F[_]: LiftParser: Comonad]: P[Expr[F]] = c.p[F]
override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Ast.Tree[F]]] =
c.ast[F]()
}
trait And extends Companion {
def validChildren: List[Companion]
// expression that could have children
// that will be parsed by `ast` method to a tree
trait Block extends Lexem {
override def readLine[F[_]: LiftParser: Comonad]: P[Ast.Tree[F]] = super.readLine[F] <* ` : `
}
abstract class AndThen extends And {
trait Prefix extends Lexem {
def continueWith: List[Lexem]
override def ast[F[_]: LiftParser: Comonad](ps: Indent): P[Ast.Tree[F]] =
(p[F] ~ (` *` *> P
.oneOf(validChildren.map(_.ast[F](ps)))
.map(Chain.one))).map { case (expr, internal) =>
Cofree[Chain, Expr[F]](expr, Eval.now(internal))
override def readLine[F[_]: LiftParser: Comonad]: P[Ast.Tree[F]] =
((super.readLine[F] <* ` `) ~ P.oneOf(continueWith.map(_.readLine.backtrack))).map {
case (h, t) => h.copy(tail = Eval.now(Chain.one(t)))
}
override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Tree[F]]] =
((super.readLine[F] <* ` `) ~ P.oneOf(continueWith.map(_.ast().backtrack))).map {
case (h, tm) => tm.map(t => h.copy(tail = Eval.now(Chain.one(t))))
}
}
abstract class AndIndented extends And {
abstract class AndIndented extends Block {
def validChildren: List[Lexem]
override def ast[F[_]: LiftParser: Comonad](ps: Indent): P[Ast.Tree[F]] =
(p[F] ~ (` : \n+` *> indented(
s => {
val psI = ps.copy(indent = s)
P.oneOf(validChildren.map(_.ast[F](psI).backtrack))
},
ps.indent
)).map(_.toList).map(Chain.fromSeq)).map { case (expr, internal) =>
Cofree[Chain, Expr[F]](expr, Eval.now(internal))
}
private def leaf[F[_]](expr: Expr[F]): Ast.Tree[F] =
Cofree[Chain, Expr[F]](
expr,
Eval.now(Chain.empty)
)
private def last[F[_]](tree: Ast.Tree[F]): Expr[F] =
tree.tailForced.lastOption.fold(tree.head)(last)
private def setLeafs[F[_]](tree: Ast.Tree[F], children: Chain[Tree[F]]): Tree[F] =
tree.copy(tail = tree.tail.map {
case pref :== last =>
pref :+ setLeafs(last, children)
case _ =>
children
})
case class Acc[F[_]](
block: Option[(F[String], Tree[F])] = None,
window: Chain[(F[String], Tree[F])] = Chain.empty,
currentChildren: Chain[Ast.Tree[F]] = Chain.empty,
error: Chain[ParserError[F]] = Chain.empty
)
// converts list of expressions to a tree
def listToTree[F[_]: Comonad: LiftParser](
head: Tree[F],
exprs: Chain[(F[String], Ast.Tree[F])]
): ValidatedNec[ParserError[F], Ast.Tree[F]] = {
// if we don't have elements in a list, then head is a leaf
exprs.headOption
.fold[ValidatedNec[ParserError[F], Ast.Tree[F]]](Validated.validNec(head)) { lHead =>
// size of an indentation
val initialIndent = lHead._1.extract.length
// recursively creating a tree
// moving a window on a list depending on the nesting of the code
val acc = exprs.foldLeft(
Acc[F]()
) {
case (acc, (indent, currentExpr)) if acc.error.isEmpty =>
acc.block match {
case None =>
last(currentExpr) match {
// if next is block companion, start to gather all expressions under this block
case block if block.isBlock =>
acc.copy(block = Some(indent -> currentExpr))
// create leaf if token is on current level
case e =>
acc.copy(currentChildren = acc.currentChildren.append(leaf(e)))
}
// if we have root companion, gather all expressions that have indent > than current
case r @ Some((_, block)) =>
if (indent.extract.length > initialIndent) {
Acc[F](
r,
acc.window.append((indent, currentExpr)),
acc.currentChildren,
acc.error
)
} else if (indent.extract.length == initialIndent) {
// if root have no tokens in it - return an error
if (acc.window.isEmpty) {
Acc(error =
Chain.one(BlockIndentError(indent, "Block expression has no body"))
)
} else {
// create a tree from gathered expressions and continue
listToTree[F](block, acc.window).fold(
e => acc.copy(error = e.toChain),
tree => {
val withTree = acc.currentChildren.append(tree)
last(currentExpr) match {
// if next expression is root companion, start to gather all tokens under this root
case block if block.isBlock =>
acc.copy(
block = Some(indent -> currentExpr),
currentChildren = withTree,
window = Chain.empty
)
// create leaf if token is on current level
case e =>
acc.copy(
block = None,
currentChildren = withTree.append(leaf(e)),
window = Chain.empty
)
}
}
)
}
} else {
Acc[F](error =
Chain.one(
BlockIndentError(
indent,
"Wrong indentation. It must match the indentation of the previous expressions."
)
)
)
}
}
case (acc, _) =>
acc
}
// finalize all `tails` in the accumulator
NonEmptyChain.fromChain(acc.error) match {
case None =>
acc.block match {
case Some((i, headExpr)) =>
if (acc.window.isEmpty) {
Validated.invalidNec(BlockIndentError(i, "Block expression has no body"))
} else {
// create a tree from the last expressions if the window is not empty
// this may happen if a function ended in a nested expression
val tree = listToTree[F](headExpr, acc.window)
tree.map(t => setLeafs(head, acc.currentChildren :+ t))
}
case None =>
Validated.validNec(setLeafs(head, acc.currentChildren))
}
// pass through an error
case Some(err) => Validated.invalid(err)
}
}
}
override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Ast.Tree[F]]] =
(readLine[F] ~ (` \n+` *>
(P.repSep(
` `.lift ~ P.oneOf(validChildren.map(_.readLine[F].backtrack)),
` \n+`
) <* ` \n`.?)))
.map(t => listToTree(t._1, Chain.fromSeq(t._2.toList)))
}
}

View File

@ -1,3 +0,0 @@
package aqua.parser
case class Indent(indent: String = "")

View File

@ -0,0 +1,9 @@
package aqua.parser
import cats.parse.Parser
trait ParserError[F[_]]
case class LexerError[F[_]](err: Parser.Error) extends ParserError[F]
case class BlockIndentError[F[_]](indent: F[String], message: String) extends ParserError[F]
case class FuncReturnError[F[_]](point: F[Unit], message: String) extends ParserError[F]

View File

@ -7,13 +7,13 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.{Parser => P}
case class AbilityIdExpr[F[_]](ability: Ability[F], id: Value[F]) extends Expr[F]
case class AbilityIdExpr[F[_]](ability: Ability[F], id: Value[F]) extends Expr[F](AbilityIdExpr)
object AbilityIdExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: P[AbilityIdExpr[F]] =
((Ability.ab[F] <* ` `) ~ Value.`value`).map {
case (ability, id) => AbilityIdExpr(ability, id)
((Ability.ab[F] <* ` `) ~ Value.`value`).map { case (ability, id) =>
AbilityIdExpr(ability, id)
}
}

View File

@ -7,12 +7,14 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class AliasExpr[F[_]](name: CustomTypeToken[F], target: TypeToken[F]) extends Expr[F]
case class AliasExpr[F[_]](name: CustomTypeToken[F], target: TypeToken[F])
extends Expr[F](AliasExpr)
object AliasExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: Parser[AliasExpr[F]] =
((`alias` *> ` ` *> CustomTypeToken.ct[F] <* ` : `) ~ TypeToken.`typedef`[F]).map { case (name, target) =>
AliasExpr(name, target)
((`alias` *> ` ` *> CustomTypeToken.ct[F] <* ` : `) ~ TypeToken.`typedef`[F]).map {
case (name, target) =>
AliasExpr(name, target)
}
}

View File

@ -7,7 +7,8 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class ArrowTypeExpr[F[_]](name: Name[F], `type`: ArrowTypeToken[F]) extends Expr[F]
case class ArrowTypeExpr[F[_]](name: Name[F], `type`: ArrowTypeToken[F])
extends Expr[F](ArrowTypeExpr)
object ArrowTypeExpr extends Expr.Leaf {

View File

@ -10,7 +10,7 @@ import cats.parse.{Parser => P}
case class AssignmentExpr[F[_]](
variable: Name[F],
value: Value[F]
) extends Expr[F]
) extends Expr[F](AssignmentExpr)
object AssignmentExpr extends Expr.Leaf {

View File

@ -12,7 +12,7 @@ case class CallArrowExpr[F[_]](
ability: Option[Ability[F]],
funcName: Name[F],
args: List[Value[F]]
) extends Expr[F]
) extends Expr[F](CallArrowExpr)
object CallArrowExpr extends Expr.Leaf {

View File

@ -11,7 +11,7 @@ case class ConstantExpr[F[_]](
name: Name[F],
value: Value[F],
skipIfAlreadyDefined: Boolean
) extends Expr[F]
) extends Expr[F](ConstantExpr)
object ConstantExpr extends Expr.Leaf {

View File

@ -7,11 +7,11 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class DataStructExpr[F[_]](name: CustomTypeToken[F]) extends Expr[F]
case class DataStructExpr[F[_]](name: CustomTypeToken[F]) extends Expr[F](DataStructExpr)
object DataStructExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] = List(FieldTypeExpr)
override def validChildren: List[Expr.Lexem] = FieldTypeExpr :: Nil
override def p[F[_]: LiftParser: Comonad]: Parser[DataStructExpr[F]] =
`data` *> ` ` *> CustomTypeToken.ct[F].map(DataStructExpr(_))

View File

@ -1,13 +1,14 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.lexer.Token._
import aqua.parser.lexer.{Name, Token, TypeToken}
import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
import Token._
case class DeclareStreamExpr[F[_]](name: Name[F], `type`: TypeToken[F]) extends Expr[F]
case class DeclareStreamExpr[F[_]](name: Name[F], `type`: TypeToken[F])
extends Expr[F](DeclareStreamExpr)
object DeclareStreamExpr extends Expr.Leaf {

View File

@ -7,20 +7,11 @@ import aqua.parser.lift.LiftParser._
import cats.Comonad
import cats.parse.Parser
case class ElseOtherwiseExpr[F[_]](point: F[Unit]) extends Expr[F]
case class ElseOtherwiseExpr[F[_]](point: F[Unit]) extends Expr[F](ElseOtherwiseExpr)
object ElseOtherwiseExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] =
List(
Expr.defer(OnExpr),
ParExpr,
CallArrowExpr,
AbilityIdExpr,
Expr.defer(ForExpr),
Expr.defer(IfExpr),
Expr.defer(ElseOtherwiseExpr)
)
override def validChildren: List[Expr.Lexem] = ForExpr.validChildren
override def p[F[_]: LiftParser: Comonad]: Parser[ElseOtherwiseExpr[F]] =
(`else` | `otherwise`).lift.map(ElseOtherwiseExpr(_))

View File

@ -7,7 +7,8 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class FieldTypeExpr[F[_]](name: Name[F], `type`: DataTypeToken[F]) extends Expr[F]
case class FieldTypeExpr[F[_]](name: Name[F], `type`: DataTypeToken[F])
extends Expr[F](FieldTypeExpr)
object FieldTypeExpr extends Expr.Leaf {

View File

@ -13,22 +13,21 @@ case class ForExpr[F[_]](
item: Name[F],
iterable: Value[F],
mode: Option[(F[ForExpr.Mode], ForExpr.Mode)]
) extends Expr[F]
) extends Expr[F](ForExpr)
object ForExpr extends Expr.AndIndented {
sealed trait Mode
case object TryMode extends Mode
case object ParMode extends Mode
override def validChildren: List[Expr.Companion] = List(
Expr.defer(OnExpr),
ParExpr,
Expr.defer(ForExpr),
CallArrowExpr,
AbilityIdExpr,
Expr.defer(IfExpr),
Expr.defer(ElseOtherwiseExpr)
)
override def validChildren: List[Expr.Lexem] =
Expr.defer(OnExpr) ::
Expr.defer(ForExpr) ::
CallArrowExpr ::
AbilityIdExpr ::
Expr.defer(IfExpr) ::
Expr.defer(ElseOtherwiseExpr) ::
Nil
override def p[F[_]: LiftParser: Comonad]: P[ForExpr[F]] =
((`for` *> ` ` *> Name.p[F] <* ` <- `) ~ Value

View File

@ -1,11 +1,11 @@
package aqua.parser.expr
import aqua.parser.Ast.Tree
import aqua.parser.lexer.Token._
import aqua.parser.lexer.{Arg, DataTypeToken, Name, Value}
import aqua.parser.lift.LiftParser
import aqua.parser.{Expr, Indent}
import aqua.parser.{Ast, Expr, FuncReturnError, ParserError}
import cats.Comonad
import cats.data.{Validated, ValidatedNec}
import cats.free.Cofree
import cats.parse.Parser
@ -14,21 +14,21 @@ case class FuncExpr[F[_]](
args: List[Arg[F]],
ret: Option[DataTypeToken[F]],
retValue: Option[Value[F]]
) extends Expr[F]
) extends Expr[F](FuncExpr)
object FuncExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] = List(
OnExpr,
AbilityIdExpr,
ReturnExpr,
CallArrowExpr,
ParExpr,
ForExpr,
IfExpr,
ElseOtherwiseExpr,
DeclareStreamExpr
)
override def validChildren: List[Expr.Lexem] =
AbilityIdExpr ::
ReturnExpr ::
ForExpr ::
Expr.defer(OnExpr) ::
CallArrowExpr ::
IfExpr ::
ElseOtherwiseExpr ::
ParExpr ::
DeclareStreamExpr ::
Nil
override def p[F[_]: LiftParser: Comonad]: Parser[FuncExpr[F]] =
((`func` *> ` ` *> Name.p[F]) ~ comma0(Arg.p)
@ -37,32 +37,45 @@ object FuncExpr extends Expr.AndIndented {
FuncExpr(name, args, ret, None)
}
override def ast[F[_]: LiftParser: Comonad](ps: Indent): Parser[Tree[F]] =
super.ast(ps).flatMap { tree =>
tree.head match {
case funcExpr: FuncExpr[F] if funcExpr.ret.isDefined =>
tree.tail.value.lastOption.map(_.head) match {
case Some(re: ReturnExpr[F]) =>
Parser.pure(
Cofree(funcExpr.copy(retValue = Some(re.value)), tree.tail)
)
case _ =>
Parser.failWith(
"Return type is defined for function, but nothing returned. Use `<- value` as the last expression inside function body."
)
}
override def ast[F[_]: LiftParser: Comonad](): Parser[ValidatedNec[ParserError[F], Ast.Tree[F]]] =
super
.ast()
.map(
_.andThen(tree =>
tree.head match {
case funcExpr: FuncExpr[F] =>
funcExpr.ret match {
case Some(ret) =>
tree.tail.value.lastOption.map(_.head) match {
case Some(re: ReturnExpr[F]) =>
Validated
.validNec(Cofree(funcExpr.copy(retValue = Some(re.value)), tree.tail))
case _: FuncExpr[F] =>
tree.tail.value.lastOption.map(_.head) match {
case Some(_: ReturnExpr[F]) =>
Parser.failWith(
"Trying to return a value from function that has no return type. Please add return type to function declaration, e.g. `func foo() -> RetType:`"
)
case _ =>
Parser.pure(tree)
}
case _ =>
Validated.invalidNec(
FuncReturnError[F](
ret.unit,
"Return type is defined for function, but nothing returned. Use `<- value` as the last expression inside function body."
)
)
}
case _ => Parser.pure(tree)
}
}
case None =>
tree.tail.value.lastOption.map(_.head) match {
case Some(re: ReturnExpr[F]) =>
Validated.invalidNec(
FuncReturnError[F](
re.value.unit,
"Trying to return a value from function that has no return type. Please add return type to function declaration, e.g. `func foo() -> RetType:`"
)
)
case _ =>
Validated.validNec(tree)
}
}
case _ => Validated.validNec(tree)
}
)
)
}

View File

@ -8,20 +8,18 @@ import aqua.types.LiteralType
import cats.Comonad
import cats.parse.{Parser => P}
case class IfExpr[F[_]](left: Value[F], eqOp: EqOp[F], right: Value[F]) extends Expr[F]
case class IfExpr[F[_]](left: Value[F], eqOp: EqOp[F], right: Value[F]) extends Expr[F](IfExpr)
object IfExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] =
List(
Expr.defer(OnExpr),
ParExpr,
CallArrowExpr,
AbilityIdExpr,
Expr.defer(ForExpr),
Expr.defer(IfExpr),
Expr.defer(ElseOtherwiseExpr)
)
override def validChildren: List[Expr.Lexem] =
Expr.defer(OnExpr) ::
CallArrowExpr ::
AbilityIdExpr ::
Expr.defer(ForExpr) ::
Expr.defer(IfExpr) ::
Expr.defer(ElseOtherwiseExpr) ::
Nil
override def p[F[_]: LiftParser: Comonad]: P[IfExpr[F]] =
(`if` *> ` ` *> Value.`value`[F] ~ (` ` *> EqOp.p[F] ~ (` ` *> Value.`value`[F])).?).map {

View File

@ -7,24 +7,24 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.{Parser => P}
case class OnExpr[F[_]](peerId: Value[F], via: List[Value[F]]) extends Expr[F]
case class OnExpr[F[_]](peerId: Value[F], via: List[Value[F]]) extends Expr[F](OnExpr)
object OnExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] =
List(
Expr.defer(OnExpr),
ParExpr,
CallArrowExpr,
AbilityIdExpr,
Expr.defer(ForExpr),
Expr.defer(IfExpr),
Expr.defer(ElseOtherwiseExpr)
)
override def validChildren: List[Expr.Lexem] =
Expr.defer(OnExpr) ::
CallArrowExpr ::
AbilityIdExpr ::
ParExpr ::
Expr.defer(ForExpr) ::
Expr.defer(IfExpr) ::
Expr.defer(ElseOtherwiseExpr) ::
Nil
override def p[F[_]: LiftParser: Comonad]: P[OnExpr[F]] =
(`on` *> ` ` *> Value.`value`[F] ~ (` ` *> `via` *> ` ` *> Value.`value`[F]).rep0).map {
case (peerId, via) =>
OnExpr(peerId, via)
override def p[F[_]: LiftParser: Comonad]: P[OnExpr[F]] = {
(`on` *> ` ` *> Value
.`value`[F] ~ (` ` *> `via` *> ` ` *> Value.`value`[F]).rep0).map { case (peerId, via) =>
OnExpr(peerId, via)
}
}
}

View File

@ -1,19 +1,18 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._
import cats.Comonad
import cats.parse.Parser
case class ParExpr[F[_]](point: F[Unit]) extends Expr[F]
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._
object ParExpr extends Expr.AndThen {
case class ParExpr[F[_]](point: F[Unit]) extends Expr[F](ParExpr)
override def validChildren: List[Expr.Companion] =
List(Expr.defer(OnExpr), CallArrowExpr, Expr.defer(ForExpr))
object ParExpr extends Expr.Prefix {
override def continueWith: List[Expr.Lexem] = CallArrowExpr :: OnExpr :: ForExpr :: Nil
override def p[F[_]: LiftParser: Comonad]: Parser[Expr[F]] = `par`.lift.map(ParExpr(_))
override def p[F[_]: LiftParser: Comonad]: Parser[ParExpr[F]] =
`par`.lift.map(ParExpr(_))
}

View File

@ -7,7 +7,7 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class ReturnExpr[F[_]](value: Value[F]) extends Expr[F]
case class ReturnExpr[F[_]](value: Value[F]) extends Expr[F](ReturnExpr)
object ReturnExpr extends Expr.Leaf {

View File

@ -1,5 +1,35 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.Ast.Tree
import aqua.parser.lexer.Token.` \n+`
import aqua.parser.{Expr, ParserError}
import aqua.parser.lift.LiftParser
import cats.{Comonad, Eval}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.free.Cofree
import cats.parse.{Parser => P}
case class RootExpr[F[_]]() extends Expr[F]
case class RootExpr[F[_]]() extends Expr[F](RootExpr)
object RootExpr extends Expr.Companion {
def validChildren: List[Expr.Lexem] =
ServiceExpr :: AliasExpr :: DataStructExpr :: ConstantExpr :: FuncExpr :: Nil
override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Tree[F]]] =
P.repSep(
P.oneOf(RootExpr.validChildren.map(_.ast[F]())),
` \n+`
).surroundedBy(` \n+`.?)
.map(_.foldLeft[(Chain[ParserError[F]], Chain[Tree[F]])](Chain.empty -> Chain.empty) {
case ((errs, trees), Validated.Valid(tree)) => (errs, trees :+ tree)
case ((errs, trees), Validated.Invalid(err)) => (errs ++ err.toChain, trees)
})
.map { case (errs, trees) =>
NonEmptyChain
.fromChain(errs)
.fold[ValidatedNec[ParserError[F], Tree[F]]](
Validated.validNec(Cofree(RootExpr[F](), Eval.now(trees)))
)(Validated.invalid)
}
}

View File

@ -7,11 +7,11 @@ import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
case class ServiceExpr[F[_]](name: Ability[F], id: Option[Value[F]]) extends Expr[F]
case class ServiceExpr[F[_]](name: Ability[F], id: Option[Value[F]]) extends Expr[F](ServiceExpr)
object ServiceExpr extends Expr.AndIndented {
override def validChildren: List[Expr.Companion] = List(ArrowTypeExpr)
override def validChildren: List[Expr.Lexem] = ArrowTypeExpr :: Nil
override def p[F[_]: LiftParser: Comonad]: Parser[ServiceExpr[F]] =
(`service` *> ` ` *> Ability.ab[F] ~ Value.`value`[F].between(`(`, `)`).backtrack.?).map {

View File

@ -1,3 +1,27 @@
package aqua.parser.head
import aqua.parser.Ast
import aqua.parser.lexer.Token.` \n+`
import aqua.parser.lift.LiftParser
import cats.{Comonad, Eval}
import cats.data.Chain
import cats.free.Cofree
import cats.parse.{Parser => P, Parser0 => P0}
case class HeadExpr[F[_]]() extends HeaderExpr[F]
object HeadExpr {
def headExprs: List[HeaderExpr.Companion] =
ImportExpr :: Nil
def ast[F[_]: LiftParser: Comonad]: P0[Ast.Head[F]] =
P.repSep0(P.oneOf(headExprs.map(_.ast[F])), ` \n+`)
.surroundedBy(` \n+`.?)
.?
.map {
case Some(exprs) => Chain.fromSeq(exprs)
case None => Chain.empty[Ast.Head[F]]
}
.map(exprs => Cofree(HeadExpr[F](), Eval.now(exprs)))
}

View File

@ -67,15 +67,11 @@ object Token {
val `=` : P[Unit] = P.string("=")
val ` = ` : P[Unit] = P.string("=").surroundedBy(` `.?)
val `?` : P[Unit] = P.string("?")
val `<-` : P[Unit] = P.string("<-").backtrack
val `<-` : P[Unit] = P.string("<-")
def comma[T](p: P[T]): P[NonEmptyList[T]] =
P.repSep(p, `,` <* ` \n+`.rep0)
def comma0[T](p: P[T]): P0[List[T]] =
P.repSep0(p, `,` <* ` \n+`.rep0)
def indented[T](p: String => P[T], baseIndent: String): P[NonEmptyList[T]] =
(if (baseIndent.nonEmpty) P.string(baseIndent) ~ ` ` else ` `).string
.flatMap(indent p(indent).repSep((` \n+` *> P.string(indent)).backtrack))
}

View File

@ -1,42 +1,11 @@
package aqua
import aqua.parser.expr.{
AbilityIdExpr,
AliasExpr,
ArrowTypeExpr,
AssignmentExpr,
CallArrowExpr,
ConstantExpr,
DataStructExpr,
ElseOtherwiseExpr,
FieldTypeExpr,
ForExpr,
FuncExpr,
IfExpr,
OnExpr,
ParExpr,
ReturnExpr,
ServiceExpr
}
import aqua.parser.lexer.{
Ability,
Arg,
ArrayTypeToken,
ArrowTypeToken,
BasicTypeToken,
CustomTypeToken,
DataTypeToken,
EqOp,
IntoField,
Literal,
Name,
TypeToken,
VarLambda
}
import cats.Id
import aqua.parser.expr._
import aqua.parser.lexer._
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.types.LiteralType.{bool, number, string}
import aqua.types.{LiteralType, ScalarType}
import cats.Id
import org.scalatest.EitherValues
import scala.collection.mutable
@ -97,9 +66,6 @@ trait AquaSpec extends EitherValues {
def parseOn(str: String): OnExpr[Id] =
OnExpr.p[Id].parseAll(str).value
def parsePar(str: String): ParExpr[Id] =
ParExpr.p[Id].parseAll(str).value
def parseReturn(str: String): ReturnExpr[Id] =
ReturnExpr.p[Id].parseAll(str).value

View File

@ -22,7 +22,21 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
)
parseExpr("func(arg.doSomething)") should be(
CallArrowExpr[Id](None, None, Name[Id]("func"), List(toVarLambda("arg", List("doSomething"))))
CallArrowExpr[Id](
None,
None,
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething")))
)
)
parseExpr("func(arg.doSomething.and.doSomethingElse)") should be(
CallArrowExpr[Id](
None,
None,
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething", "and", "doSomethingElse")))
)
)
parseExpr("func(arg.doSomething.and.doSomethingElse)") should be(

View File

@ -9,7 +9,7 @@ import org.scalatest.matchers.should.Matchers
class ForExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
"for expression" should "be parsed" in {
parseFor("for some <- \"a\"") should be(
ForExpr[Id]("some", toStr("a"), None)
)
@ -33,5 +33,6 @@ class ForExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseFor("for some <- false try") should be(
ForExpr[Id]("some", toBool(false), Some(ForExpr.TryMode -> ForExpr.TryMode))
)
}
}

View File

@ -87,7 +87,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| Peer "some id"
| call(true)""".stripMargin
val tree = FuncExpr.ast[Id](Indent()).parseAll(script).value
val tree = FuncExpr.ast[Id]().parseAll(script).value.toEither.value
val funcBody = checkHeadGetTail(tree, FuncExpr("a", Nil, None, None), 1).toList
val ifBody =
@ -97,12 +97,35 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
3
).toList
ifBody.head.head should be(CallArrowExpr(Some(toName("x")), Some(toAb("Ab")), "func", Nil))
ifBody.head.head should be(
CallArrowExpr(Some(toName("x")), Some(toAb("Ab")), "func", Nil)
)
ifBody(1).head should be(AbilityIdExpr(toAb("Peer"), toStr("some id")))
ifBody(2).head should be(CallArrowExpr(None, None, "call", List(toBool(true))))
}
"function with wrong indent" should "parse with error" in {
val script =
"""func tryGen() -> bool:
| on "deeper" via "deep":
| v <- Local.gt()
| <- v
|""".stripMargin
parser[Id]().parseAll(script).value.toEither shouldBe Symbol("left")
}
"function with root expression without children" should "parse with error" in {
val script =
"""func tryGen() -> bool:
| on "deeper" via "deep":
| <- v
|""".stripMargin
parser[Id]().parseAll(script).value.toEither shouldBe Symbol("left")
}
"multi function expression" should "parse" in {
val script =
"""service Local("local"):
@ -120,7 +143,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| three <- Local.gt()
| <- two""".stripMargin
val tree = parser[Id](Indent()).parseAll(script).value
val tree = parser[Id]().parseAll(script).value.toEither.value
val qTree = tree.tree.foldLeft(mutable.Queue.empty[Expr[Id]]) { case (acc, tag) =>
acc.enqueue(tag)

View File

@ -1,17 +1,34 @@
package aqua.parser
import aqua.AquaSpec
import aqua.parser.expr.{OnExpr, ParExpr}
import cats.Id
import aqua.parser.expr.{CallArrowExpr, ParExpr}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.{Eval, Id}
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.data.Chain
import cats.free.Cofree
class ParExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
parsePar("par") should be(
ParExpr[Id](())
"par" should "be parsed" in {
ParExpr.readLine[Id].parseAll("par x <- y()").value should be(
Cofree[Chain, Expr[Id]](
ParExpr[Id](()),
Eval.now(
Chain(
Cofree[Chain, Expr[Id]](
CallArrowExpr(
Some(AquaSpec.toName("x")),
None,
AquaSpec.toName("y"),
Nil
),
Eval.now(Chain.empty)
)
)
)
)
)
}
}

View File

@ -1,8 +1,6 @@
package aqua.parser.lexer
import aqua.parser.lexer.Token._
import cats.data.NonEmptyList
import cats.parse.{Parser => P}
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
@ -46,35 +44,4 @@ class TokenSpec extends AnyFlatSpec with Matchers with EitherValues {
|""".stripMargin).right.value should be(())
}
"indented" should "parse 1 or more lines" in {
indented(_ => `.`, "").parseAll(" .\n .").right.value should be(NonEmptyList.of((), ()))
indented(_ => `.`, "").parseAll(" .\n .\n .").right.value should be(NonEmptyList.of((), (), ()))
indented(_ => `.`, "").parse(" .\n .\n .\n").right.value should be(
("\n", NonEmptyList.of((), (), ()))
)
indented(_ => `.`, "").parse(" .\n .\n .\n ").right.value should be(
("\n ", NonEmptyList.of((), (), ()))
)
indented(_ => `.`, "").parse(" .\n .\n .\n .").right.value should be(
("", NonEmptyList.of((), (), (), ()))
)
indented(_ => `.`, "").parseAll(" .").right.value should be(NonEmptyList.of(()))
indented(_ => `.`, " ").parse(" .\n .").right.value should be(("\n .", NonEmptyList.of(())))
indented(_ => `.`, " ").parse(" .\n ").right.value should be(("\n ", NonEmptyList.of(())))
}
"nested indented" should "not fail on empty lines" in {
sealed trait Tree
case object Leaf extends Tree
case class Node(branches: NonEmptyList[Tree]) extends Tree
lazy val p: P[NonEmptyList[Tree]] =
indented(
_ => P.string("newline") *> (` \n+` *> P.defer(p)).?.map(_.fold[Tree](Leaf)(Node)),
""
)
p.parseAll(" newline").right.value should be(NonEmptyList.of(Leaf))
}
}

View File

@ -3,10 +3,10 @@ package aqua.semantics
import aqua.model.Model
import aqua.parser.Expr
import aqua.parser.expr._
import aqua.semantics.expr._
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.semantics.expr._
object ExprSem {

View File

@ -1,8 +1,8 @@
package aqua.semantics.expr
import aqua.model.{Model, ValueModel}
import aqua.model.func.Call
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
import aqua.model.{Model, ValueModel}
import aqua.parser.expr.CallArrowExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
@ -72,6 +72,7 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
Call(argsResolved, (variable.map(_.value), t).mapN(Call.Export))
)
)
}
.map(Option(_))
})
@ -85,6 +86,7 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
Call(argsResolved, (variable.map(_.value), t).mapN(Call.Export))
)
)
}
.map(Option(_))
})

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{ForTag, FuncOp, NextTag, OpTag, ParTag, SeqTag, XorTag}
import aqua.model.func.body._
import aqua.parser.expr.ForExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra

View File

@ -34,7 +34,6 @@ class OnSem[F[_]](val expr: OnExpr[F]) extends AnyVal {
),
op
)
case m => Model.error("On body is not an op, it's " + m)
})
)

View File

@ -10,7 +10,8 @@ class ParSem[F[_]](val expr: ParExpr[F]) extends AnyVal {
def program[Alg[_]]: Prog[Alg, Model] =
Prog.after[Alg, Model] {
case g: FuncOp => Free.pure[Alg, Model](FuncOp.wrap(ParTag, g))
case g: FuncOp =>
Free.pure[Alg, Model](FuncOp.wrap(ParTag, g))
case g => Free.pure[Alg, Model](g)
}
}