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:
z <- Export.foo()
<- z
func post() -> *string:
relay_nil: **string
relay_nil <- string_nil()
<- relay_nil

View File

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

View File

@ -3,41 +3,68 @@ package aqua
import aqua.compiler.*
import aqua.files.FileModuleId
import aqua.io.AquaFileError
import aqua.parser.lift.FileSpan
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{BlockIndentError, FuncReturnError, LexerError}
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 {
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
.focus(3)
.map(
_.toConsoleStr(
message,
messages,
Console.RED
)
)
.getOrElse(
"(offset is beyond the script, syntax errors) Error: " + Console.RED + message
.mkString(", ")
"(offset is beyond the script, syntax errors) Error: " + Console.RED + messages.mkString(", ")
) + Console.RESET + "\n"
implicit val showError: Show[AquaError[FileModuleId, AquaFileError, FileSpan.F]] = Show.show {
case ParserErr(err) =>
err match {
case BlockIndentError(indent, message) => showForConsole(indent._1, message)
case FuncReturnError(point, message) => showForConsole(point._1, message)
case BlockIndentError(indent, message) => showForConsole(indent._1, message :: Nil)
case FuncReturnError(point, message) => showForConsole(point._1, message :: Nil)
case LexerError((span, e)) =>
span
.focus(3)
.map(spanFocus =>
.map { spanFocus =>
val errorMessages = e.expected.map(exp => expectationToString(exp, span.locationMap, span.span.startIndex))
spanFocus.toConsoleStr(
s"Syntax error, expected: ${e.expected.toList.mkString(", ")}",
s"Syntax error: ${errorMessages.head}" :: errorMessages.tail.map(t => "OR " + t),
Console.RED
)
)
}
.getOrElse(
"(offset is beyond the script, syntax errors) " + Console.RED + e.expected.toList
.mkString(", ")
@ -47,11 +74,11 @@ object ErrorRendering {
Console.RED + err.showForConsole + Console.RESET
case ResolveImportsErr(_, token, err) =>
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) =>
val span = token.unit._1
showForConsole(span, s"Cannot resolve import")
showForConsole(span, s"Cannot resolve import" :: Nil)
case CycleError(modules) =>
s"Cycle loops detected in imports: ${modules.map(_.file.fileName)}"
case CompileError(err) =>
@ -59,12 +86,12 @@ object ErrorRendering {
case RulesViolated(token, message) =>
token.unit._1
.focus(2)
.map(_.toConsoleStr(message, Console.CYAN))
.map(_.toConsoleStr(message :: Nil, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
case HeaderError(token, message) =>
token.unit._1
.focus(2)
.map(_.toConsoleStr(message, Console.CYAN))
.map(_.toConsoleStr(message :: Nil, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
case WrongAST(ast) =>
s"Semantic error"

View File

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

View File

@ -1,13 +1,13 @@
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.types.ScalarType
import cats.Comonad
import cats.parse.{Parser => P}
import cats.syntax.comonad._
import cats.syntax.functor._
import cats.parse.{Accumulator0, Parser as P}
import cats.syntax.comonad.*
import cats.syntax.functor.*
import cats.~>
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 {
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 = 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]] =
P.oneOf(
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) {
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" +
spanFocus.toConsoleStr(
msg,
msgs,
onLeft,
onRight
)

View File

@ -1,6 +1,7 @@
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 scala.language.implicitConversions
@ -54,12 +55,13 @@ object Span {
}
def toConsoleStr(
msg: String,
msgs: List[String],
onLeft: String,
onRight: String = Console.RESET
): String = {
val line3Length = line._3.length
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") +
"\n" +
formatLN(line._1, onLeft, onRight) +
@ -75,9 +77,8 @@ object Span {
("=" * line._4.length) +
onRight +
"\n" +
(" " * (line._2.length + lastNSize + 1)) +
onLeft +
msg +
message +
onRight +
"\n" +
post.map(formatLine(_, onLeft, onRight)).mkString("\n")
@ -106,8 +107,15 @@ object Span {
(Span(i, i), v)
}
override def wrapErr(e: P.Error): (Span, P.Error) =
(Span(e.failedAtOffset, e.failedAtOffset + 1), e)
override def wrapErr(e: P.Error): (Span, P.Error) = {
// 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)
}
}
}