Fix incorrect resolving pushing stream from func to a value (#275) (#297)

This commit is contained in:
Dima 2021-09-10 14:36:01 +03:00 committed by GitHub
parent dc1f6c2faa
commit 7362b46384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 41 deletions

View File

@ -1,7 +1,12 @@
module Import service SomeService("erf"):
someCall()
use "export.aqua" func string_nil() -> *string:
value_nil: *string
SomeService.someCall()
<- value_nil
func foo_wrapper() -> string: func post() -> *string:
z <- Export.foo() relay_nil: **string
<- z relay_nil <- string_nil()
<- relay_nil

View File

@ -29,7 +29,7 @@ object JavaScriptCommon {
.concat(List("callParams")) .concat(List("callParams"))
.mkString(", ") .mkString(", ")
val callCallbackStatement = s"$callbackName(${arrowArgumentsToCallbackArgumentsList})" val callCallbackStatement = s"$callbackName($arrowArgumentsToCallbackArgumentsList)"
val callCallbackStatementAndReturn = val callCallbackStatementAndReturn =
at.res.fold(s"${callCallbackStatement}; resp.result = {}")(`type` => at.res.fold(s"${callCallbackStatement}; resp.result = {}")(`type` =>

View File

@ -3,41 +3,68 @@ package aqua
import aqua.compiler.* import aqua.compiler.*
import aqua.files.FileModuleId import aqua.files.FileModuleId
import aqua.io.AquaFileError import aqua.io.AquaFileError
import aqua.parser.lift.FileSpan import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{BlockIndentError, FuncReturnError, LexerError} import aqua.parser.{BlockIndentError, FuncReturnError, LexerError}
import aqua.semantics.{HeaderError, RulesViolated, WrongAST} import aqua.semantics.{HeaderError, RulesViolated, WrongAST}
import cats.Show import cats.{Eval, Show}
import cats.parse.LocationMap
import cats.parse.Parser.Expectation
import cats.parse.Parser.Expectation.*
object ErrorRendering { object ErrorRendering {
def showForConsole(span: FileSpan, message: String): String = def expectationToString(expectation: Expectation, locationMap: Eval[LocationMap], currentOffset: Int): String = {
// add column number if it is not on the current offset
def makeMsg(msg: String, offset: Int): String = {
if (offset == currentOffset) msg
else {
val focus = Span(offset, offset + 1).focus(locationMap, 0)
focus.map(f => s"$msg on ${f.line._1}:${f.column}").getOrElse(msg)
}
}
// TODO: match all expectations
expectation match {
case wc@WithContext(str, offset) => makeMsg(str, wc.offset)
case InRange(offset, lower, upper) =>
if (lower == upper)
makeMsg(s"Expected symbol '$lower'", offset)
else
makeMsg(s"Expected symbols from '$lower' to '$upper'", offset)
case OneOfStr(offset, strs) =>
makeMsg(s"Expected one of these strings: ${strs.map(s => s"'$s'").mkString(", ")}", offset)
case e => "Expected: " + e.toString
}
}
def showForConsole(span: FileSpan, messages: List[String]): String =
span span
.focus(3) .focus(3)
.map( .map(
_.toConsoleStr( _.toConsoleStr(
message, messages,
Console.RED Console.RED
) )
) )
.getOrElse( .getOrElse(
"(offset is beyond the script, syntax errors) Error: " + Console.RED + message "(offset is beyond the script, syntax errors) Error: " + Console.RED + messages.mkString(", ")
.mkString(", ")
) + Console.RESET + "\n" ) + Console.RESET + "\n"
implicit val showError: Show[AquaError[FileModuleId, AquaFileError, FileSpan.F]] = Show.show { implicit val showError: Show[AquaError[FileModuleId, AquaFileError, FileSpan.F]] = Show.show {
case ParserErr(err) => case ParserErr(err) =>
err match { err match {
case BlockIndentError(indent, message) => showForConsole(indent._1, message) case BlockIndentError(indent, message) => showForConsole(indent._1, message :: Nil)
case FuncReturnError(point, message) => showForConsole(point._1, message) case FuncReturnError(point, message) => showForConsole(point._1, message :: Nil)
case LexerError((span, e)) => case LexerError((span, e)) =>
span span
.focus(3) .focus(3)
.map(spanFocus => .map { spanFocus =>
val errorMessages = e.expected.map(exp => expectationToString(exp, span.locationMap, span.span.startIndex))
spanFocus.toConsoleStr( spanFocus.toConsoleStr(
s"Syntax error, expected: ${e.expected.toList.mkString(", ")}", s"Syntax error: ${errorMessages.head}" :: errorMessages.tail.map(t => "OR " + t),
Console.RED Console.RED
) )
) }
.getOrElse( .getOrElse(
"(offset is beyond the script, syntax errors) " + Console.RED + e.expected.toList "(offset is beyond the script, syntax errors) " + Console.RED + e.expected.toList
.mkString(", ") .mkString(", ")
@ -47,11 +74,11 @@ object ErrorRendering {
Console.RED + err.showForConsole + Console.RESET Console.RED + err.showForConsole + Console.RESET
case ResolveImportsErr(_, token, err) => case ResolveImportsErr(_, token, err) =>
val span = token.unit._1 val span = token.unit._1
showForConsole(span, s"Cannot resolve imports: ${err.showForConsole}") showForConsole(span, s"Cannot resolve imports: ${err.showForConsole}" :: Nil)
case ImportErr(token) => case ImportErr(token) =>
val span = token.unit._1 val span = token.unit._1
showForConsole(span, s"Cannot resolve import") showForConsole(span, s"Cannot resolve import" :: Nil)
case CycleError(modules) => case CycleError(modules) =>
s"Cycle loops detected in imports: ${modules.map(_.file.fileName)}" s"Cycle loops detected in imports: ${modules.map(_.file.fileName)}"
case CompileError(err) => case CompileError(err) =>
@ -59,12 +86,12 @@ object ErrorRendering {
case RulesViolated(token, message) => case RulesViolated(token, message) =>
token.unit._1 token.unit._1
.focus(2) .focus(2)
.map(_.toConsoleStr(message, Console.CYAN)) .map(_.toConsoleStr(message :: Nil, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n" .getOrElse("(Dup error, but offset is beyond the script)") + "\n"
case HeaderError(token, message) => case HeaderError(token, message) =>
token.unit._1 token.unit._1
.focus(2) .focus(2)
.map(_.toConsoleStr(message, Console.CYAN)) .map(_.toConsoleStr(message :: Nil, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n" .getOrElse("(Dup error, but offset is beyond the script)") + "\n"
case WrongAST(ast) => case WrongAST(ast) =>
s"Semantic error" s"Semantic error"

View File

@ -30,13 +30,14 @@ case class FuncCallable(
.head) .head)
} }
def extractStreamArgs(args: Map[String, ValueModel]): Map[String, ValueModel] = def isStream(vm: ValueModel): Boolean =
args.filter { arg => vm.`type` match {
arg._2.`type` match {
case StreamType(_) => true case StreamType(_) => true
case _ => false case _ => false
} }
}
def extractStreamArgs(args: Map[String, ValueModel]): Map[String, ValueModel] =
args.filter(arg => isStream(arg._2))
// Apply a callable function, get its fully resolved body & optional value, if any // Apply a callable function, get its fully resolved body & optional value, if any
def resolve( def resolve(
@ -153,7 +154,10 @@ case class FuncCallable(
val (ops, rets) = (call.exportTo zip resolvedResult) val (ops, rets) = (call.exportTo zip resolvedResult)
.map[(Option[FuncOp], ValueModel)] { .map[(Option[FuncOp], ValueModel)] {
case (exp @ Call.Export(_, StreamType(_)), res) if isStream(res) =>
None -> res
case (exp @ Call.Export(_, StreamType(_)), res) => case (exp @ Call.Export(_, StreamType(_)), res) =>
res.`type`
// pass nested function results to a stream // pass nested function results to a stream
Some(FuncOps.ap(res, exp)) -> exp.model Some(FuncOps.ap(res, exp)) -> exp.model
case (_, res) => case (_, res) =>

View File

@ -1,13 +1,13 @@
package aqua.parser.lexer package aqua.parser.lexer
import aqua.parser.lexer.Token._ import aqua.parser.lexer.Token.*
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._ import aqua.parser.lift.LiftParser.*
import aqua.types.ScalarType import aqua.types.ScalarType
import cats.Comonad import cats.Comonad
import cats.parse.{Parser => P} import cats.parse.{Accumulator0, Parser as P}
import cats.syntax.comonad._ import cats.syntax.comonad.*
import cats.syntax.functor._ import cats.syntax.functor.*
import cats.~> import cats.~>
sealed trait TypeToken[F[_]] extends Token[F] { sealed trait TypeToken[F[_]] extends Token[F] {
@ -40,7 +40,9 @@ case class StreamTypeToken[F[_]: Comonad](override val unit: F[Unit], data: Data
object StreamTypeToken { object StreamTypeToken {
def `streamtypedef`[F[_]: LiftParser: Comonad]: P[StreamTypeToken[F]] = def `streamtypedef`[F[_]: LiftParser: Comonad]: P[StreamTypeToken[F]] =
(`*`.lift ~ DataTypeToken.`datatypedef`[F]).map(ud => StreamTypeToken(ud._1, ud._2)) ((`*`.lift <* P.not(`*`).withContext("Nested streams '**type' is prohibited"))
~ DataTypeToken.`withoutstreamdatatypedef`[F])
.map(ud => StreamTypeToken(ud._1, ud._2))
} }
@ -137,6 +139,14 @@ object DataTypeToken {
`⊥`.lift.map(TopBottomToken(_, isTop = false)) | `⊥`.lift.map(TopBottomToken(_, isTop = false)) |
``.lift.map(TopBottomToken(_, isTop = true)) ``.lift.map(TopBottomToken(_, isTop = true))
def `withoutstreamdatatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf(
P.defer(`arraytypedef`[F]) :: P.defer(
OptionTypeToken.`optiontypedef`
) :: BasicTypeToken
.`basictypedef`[F] :: CustomTypeToken.dotted[F] :: Nil
)
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] = def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf( P.oneOf(
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer( P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer(

View File

@ -17,10 +17,10 @@ object FileSpan {
case class Focus(name: String, locationMap: Eval[LocationMap], ctx: Int, spanFocus: Span.Focus) { case class Focus(name: String, locationMap: Eval[LocationMap], ctx: Int, spanFocus: Span.Focus) {
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String = def toConsoleStr(msgs: List[String], onLeft: String, onRight: String = Console.RESET): String =
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" + s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" +
spanFocus.toConsoleStr( spanFocus.toConsoleStr(
msg, msgs,
onLeft, onLeft,
onRight onRight
) )

View File

@ -1,6 +1,7 @@
package aqua.parser.lift package aqua.parser.lift
import cats.parse.{LocationMap, Parser0, Parser => P} import cats.data.NonEmptyList
import cats.parse.{LocationMap, Parser0, Parser as P}
import cats.{Comonad, Eval} import cats.{Comonad, Eval}
import scala.language.implicitConversions import scala.language.implicitConversions
@ -54,12 +55,13 @@ object Span {
} }
def toConsoleStr( def toConsoleStr(
msg: String, msgs: List[String],
onLeft: String, onLeft: String,
onRight: String = Console.RESET onRight: String = Console.RESET
): String = { ): String = {
val line3Length = line._3.length val line3Length = line._3.length
val line3Mult = if (line3Length == 0) 1 else line3Length val line3Mult = if (line3Length == 0) 1 else line3Length
val message = msgs.map(m => (" " * (line._2.length + lastNSize + 1)) + m).mkString("\n")
pre.map(formatLine(_, onLeft, onRight)).mkString("\n") + pre.map(formatLine(_, onLeft, onRight)).mkString("\n") +
"\n" + "\n" +
formatLN(line._1, onLeft, onRight) + formatLN(line._1, onLeft, onRight) +
@ -75,9 +77,8 @@ object Span {
("=" * line._4.length) + ("=" * line._4.length) +
onRight + onRight +
"\n" + "\n" +
(" " * (line._2.length + lastNSize + 1)) +
onLeft + onLeft +
msg + message +
onRight + onRight +
"\n" + "\n" +
post.map(formatLine(_, onLeft, onRight)).mkString("\n") post.map(formatLine(_, onLeft, onRight)).mkString("\n")
@ -106,8 +107,15 @@ object Span {
(Span(i, i), v) (Span(i, i), v)
} }
override def wrapErr(e: P.Error): (Span, P.Error) = override def wrapErr(e: P.Error): (Span, P.Error) = {
(Span(e.failedAtOffset, e.failedAtOffset + 1), e) // Find all WithContext expectations, get last by offset.
// This will be the final error handled by hand.
val withContext = e.expected.collect {
case e@P.Expectation.WithContext(_, _) => e
}.sortBy(_.offset).headOption
val exp = withContext.map(e => P.Error(e.offset, NonEmptyList.one(e))).getOrElse(e)
(Span(exp.failedAtOffset, exp.failedAtOffset + 1), exp)
}
} }
} }