120 improve output (#137)

This commit is contained in:
Dima 2021-05-31 12:50:31 +03:00 committed by GitHub
parent 9990eb0a66
commit f34cd3a4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 93 deletions

View File

@ -1,28 +1,29 @@
package aqua package aqua
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
import aqua.parser.lift.{FileSpan, LiftParser, Span} import aqua.parser.lift.{FileSpan, LiftParser, Span}
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
import cats.Eval
import cats.data.ValidatedNec import cats.data.ValidatedNec
import cats.parse.LocationMap
object Aqua { object Aqua {
def parseString(input: String): ValidatedNec[AquaError, Ast[Span.F]] =
Ast
.fromString[Span.F](input)
.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]] = { def parseFileString(name: String, input: String): ValidatedNec[AquaError, Ast[FileSpan.F]] = {
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input) implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
Ast Ast
.fromString[FileSpan.F](input) .fromString[FileSpan.F](input)
.leftMap(_.map { .leftMap(_.map {
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1.span, message) case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
case FuncReturnError(point, message) => CustomSyntaxError(point._1.span, message) case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
case LexerError(pe) => SyntaxError(pe.failedAtOffset, pe.expected) case LexerError(pe) =>
val fileSpan =
FileSpan(
name,
input,
Eval.later(LocationMap(input)),
Span(pe.failedAtOffset, pe.failedAtOffset + 1)
)
SyntaxError(fileSpan, pe.expected)
}) })
} }

View File

@ -3,7 +3,7 @@ package aqua
import aqua.model.transform.BodyConfig import aqua.model.transform.BodyConfig
import cats.data.Validated import cats.data.Validated
import cats.effect._ import cats.effect._
import cats.effect.std.Console import cats.effect.std.{Console => ConsoleEff}
import cats.syntax.apply._ import cats.syntax.apply._
import cats.syntax.functor._ import cats.syntax.functor._
import com.monovore.decline.Opts import com.monovore.decline.Opts
@ -11,12 +11,22 @@ import com.monovore.decline.effect.CommandIOApp
import fs2.io.file.Files import fs2.io.file.Files
import org.typelevel.log4cats.slf4j.Slf4jLogger import org.typelevel.log4cats.slf4j.Slf4jLogger
import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger} import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger}
import wvlet.log.{LogSupport, Logger => WLogger} import wvlet.log.LogFormatter.{appendStackTrace, highlightLog}
import wvlet.log.{LogFormatter, LogRecord, LogSupport, Logger => WLogger}
object CustomLogFormatter extends LogFormatter {
override def formatLog(r: LogRecord): String = {
val log =
s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}"
appendStackTrace(log, r)
}
}
object AquaCli extends IOApp with LogSupport { object AquaCli extends IOApp with LogSupport {
import AppOps._ import AppOps._
def main[F[_]: Concurrent: Files: Console: Logger]: Opts[F[ExitCode]] = { def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
versionOpt versionOpt
.as( .as(
versionAndExit versionAndExit
@ -34,6 +44,7 @@ object AquaCli extends IOApp with LogSupport {
logLevelOpt logLevelOpt
).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) => ).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) =>
WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel)) WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel))
WLogger.setDefaultFormatter(CustomLogFormatter)
// if there is `--help` or `--version` flag - show help and version // if there is `--help` or `--version` flag - show help and version
// otherwise continue program execution // otherwise continue program execution
@ -52,8 +63,8 @@ object AquaCli extends IOApp with LogSupport {
case Validated.Invalid(errs) => case Validated.Invalid(errs) =>
errs.map(println) errs.map(println)
ExitCode.Error ExitCode.Error
case Validated.Valid(res) => case Validated.Valid(results) =>
res.map(println) results.map(println)
ExitCode.Success ExitCode.Success
} }
} }

View File

@ -10,6 +10,7 @@ import aqua.parser.lexer.Token
import aqua.parser.lift.FileSpan import aqua.parser.lift.FileSpan
import aqua.semantics.{CompilerState, Semantics} import aqua.semantics.{CompilerState, Semantics}
import cats.Applicative import cats.Applicative
import cats.data.Validated.{Invalid, Valid}
import cats.data._ import cats.data._
import cats.effect.kernel.Concurrent import cats.effect.kernel.Concurrent
import cats.syntax.flatMap._ import cats.syntax.flatMap._
@ -18,19 +19,30 @@ import cats.syntax.monoid._
import cats.syntax.show._ import cats.syntax.show._
import fs2.io.file.Files import fs2.io.file.Files
import fs2.text import fs2.text
import wvlet.log.LogSupport
import java.nio.file.Path import java.nio.file.Path
object AquaCompiler { object AquaCompiler extends LogSupport {
sealed trait CompileTarget sealed trait CompileTarget
case object TypescriptTarget extends CompileTarget case object TypescriptTarget extends CompileTarget
case object AirTarget extends CompileTarget case object AirTarget extends CompileTarget
case class Prepared(target: String => Path, model: ScriptModel) { case class Prepared(srcFile: Path, srcDir: Path, model: ScriptModel) {
def hasOutput(target: CompileTarget): Boolean = target match { def hasOutput(target: CompileTarget): Boolean = target match {
case _ => model.funcs.nonEmpty case _ => model.funcs.nonEmpty
} }
def targetPath(ext: String): Validated[Throwable, Path] =
Validated.catchNonFatal {
val fileName = srcFile.getFileName
if (fileName == null) {
throw new Exception(s"Unexpected: 'fileName' is null in path $srcFile")
} else {
srcDir.resolve(fileName.toString.stripSuffix(".aqua") + s".$ext")
}
}
} }
def prepareFiles[F[_]: Files: Concurrent]( def prepareFiles[F[_]: Files: Concurrent](
@ -69,7 +81,19 @@ object AquaCompiler {
(errs ++ showProcErrors(proc.errors), preps) (errs ++ showProcErrors(proc.errors), preps)
case (_, model: ScriptModel) => case (_, model: ScriptModel) =>
(errs, preps :+ Prepared(modId.targetPath(srcPath, targetPath, _), model)) val src = Validated.catchNonFatal {
targetPath.toAbsolutePath
.normalize()
.resolve(
srcPath.toAbsolutePath
.normalize()
.relativize(modId.file.toAbsolutePath.normalize())
)
}
src match {
case Validated.Invalid(t) => (errs :+ t.getMessage, preps)
case Validated.Valid(s) => (errs, preps :+ Prepared(s, srcPath, model))
}
case (_, model) => case (_, model) =>
( (
@ -95,7 +119,7 @@ object AquaCompiler {
): Chain[String] = ): Chain[String] =
errors.map(err => errors.map(err =>
err._1.unit._1 err._1.unit._1
.focus(1) .focus(2)
.map(_.toConsoleStr(err._2, Console.CYAN)) .map(_.toConsoleStr(err._2, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n" .getOrElse("(Dup error, but offset is beyond the script)") + "\n"
) )
@ -108,28 +132,47 @@ object AquaCompiler {
bodyConfig: BodyConfig bodyConfig: BodyConfig
): F[ValidatedNec[String, Chain[String]]] = ): F[ValidatedNec[String, Chain[String]]] =
prepareFiles(srcPath, imports, targetPath) prepareFiles(srcPath, imports, targetPath)
.map(_.map(_.filter(_.hasOutput(compileTo)))) .map(_.map(_.filter { p =>
val hasOutput = p.hasOutput(compileTo)
if (!hasOutput) info(s"Source ${p.srcFile}: compilation OK (nothing to emit)")
else info(s"Source ${p.srcFile}: compilation OK (${p.model.funcs.length} functions)")
hasOutput
}))
.flatMap[ValidatedNec[String, Chain[String]]] { .flatMap[ValidatedNec[String, Chain[String]]] {
case Validated.Invalid(e) => case Validated.Invalid(e) =>
Applicative[F].pure(Validated.invalid(e)) Applicative[F].pure(Validated.invalid(e))
case Validated.Valid(preps) => case Validated.Valid(preps) =>
(compileTo match { (compileTo match {
case TypescriptTarget => case TypescriptTarget =>
preps preps.map { p =>
.map(p => writeFile(p.target("ts"), TypescriptFile(p.model).generateTS(bodyConfig))) val tpV = p.targetPath("ts")
tpV match {
case Invalid(t) =>
EitherT.pure(t.getMessage)
case Valid(tp) =>
writeFile(tp, TypescriptFile(p.model).generateTS(bodyConfig))
}
}
// TODO add function name to AirTarget class // TODO add function name to AirTarget class
case AirTarget => case AirTarget =>
preps preps
.flatMap(p => .flatMap(p =>
p.model.resolveFunctions.map { fc => p.model.resolveFunctions
fc.funcName -> FuncAirGen(fc).generateAir(bodyConfig).show .map(fc => FuncAirGen(fc).generateAir(bodyConfig).show)
}.map { case (n, g) => .map { generated =>
writeFile( val tpV = p.targetPath("ts")
p.target(n + ".air"), tpV match {
g case Invalid(t) =>
) EitherT.pure(t.getMessage)
} case Valid(tp) =>
writeFile(
tp,
generated
)
}
}
) )
}).foldLeft( }).foldLeft(
@ -140,7 +183,7 @@ object AquaCompiler {
w <- writeET.value w <- writeET.value
} yield (a, w) match { } yield (a, w) match {
case (Left(errs), Left(err)) => Left(errs :+ err) case (Left(errs), Left(err)) => Left(errs :+ err)
case (Right(res), Right(r)) => Right(res :+ r) case (Right(res), Right(_)) => Right(res)
case (Left(errs), _) => Left(errs) case (Left(errs), _) => Left(errs)
case (_, Left(err)) => Left(NonEmptyChain.of(err)) case (_, Left(err)) => Left(NonEmptyChain.of(err))
}) })
@ -149,9 +192,9 @@ object AquaCompiler {
} }
def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, String] = def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, Unit] =
EitherT.right[String](Files[F].deleteIfExists(file)) >> EitherT.right[String](Files[F].deleteIfExists(file)) >>
EitherT[F, String, String]( EitherT[F, String, Unit](
fs2.Stream fs2.Stream
.emit( .emit(
content content
@ -165,7 +208,7 @@ object AquaCompiler {
} }
.compile .compile
.drain .drain
.map(_ => Right(s"Compiled $file")) .map(_ => Right(()))
) )
} }

View File

@ -1,20 +1,18 @@
package aqua package aqua
import aqua.parser.lift.Span import aqua.parser.lift.FileSpan
import cats.Eval
import cats.data.NonEmptyList import cats.data.NonEmptyList
import cats.parse.LocationMap
import cats.parse.Parser.Expectation import cats.parse.Parser.Expectation
sealed trait AquaError { sealed trait AquaError {
def showForConsole(script: String): String def showForConsole: String
} }
case class CustomSyntaxError(span: Span, message: String) extends AquaError { case class CustomSyntaxError(span: FileSpan, message: String) extends AquaError {
override def showForConsole(script: String): String = override def showForConsole: String =
span span
.focus(Eval.later(LocationMap(script)), 2) .focus(3)
.map( .map(
_.toConsoleStr( _.toConsoleStr(
message, message,
@ -27,13 +25,13 @@ case class CustomSyntaxError(span: Span, message: String) extends AquaError {
) + Console.RESET + "\n" ) + Console.RESET + "\n"
} }
case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) extends AquaError { case class SyntaxError(span: FileSpan, expectations: NonEmptyList[Expectation]) extends AquaError {
override def showForConsole(script: String): String = override def showForConsole: String =
Span(offset, offset + 1) span
.focus(Eval.later(LocationMap(script)), 2) .focus(3)
.map( .map(spanFocus =>
_.toConsoleStr( spanFocus.toConsoleStr(
s"Syntax error, expected: ${expectations.toList.mkString(", ")}", s"Syntax error, expected: ${expectations.toList.mkString(", ")}",
Console.RED Console.RED
) )
@ -43,12 +41,3 @@ case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) ext
.mkString(", ") .mkString(", ")
) + Console.RESET + "\n" ) + Console.RESET + "\n"
} }
case class CompilerError(span: Span, hint: String) extends AquaError {
override def showForConsole(script: String): String =
span
.focus(Eval.later(LocationMap(script)), 1)
.map(_.toConsoleStr(hint, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
}

View File

@ -25,8 +25,8 @@ object Test extends IOApp.Simple {
.map { .map {
case Validated.Invalid(errs) => case Validated.Invalid(errs) =>
errs.map(println) errs.map(println)
case Validated.Valid(res) => case Validated.Valid(_) =>
res.map(println)
} }
} }

View File

@ -7,10 +7,10 @@ import aqua.parser.head.ImportExpr
import aqua.parser.lift.FileSpan import aqua.parser.lift.FileSpan
import cats.data.{EitherT, NonEmptyChain} import cats.data.{EitherT, NonEmptyChain}
import cats.effect.Concurrent import cats.effect.Concurrent
import cats.syntax.apply._
import cats.syntax.functor._
import fs2.io.file.Files import fs2.io.file.Files
import fs2.text import fs2.text
import cats.syntax.functor._
import cats.syntax.apply._
import java.nio.file.{Path, Paths} import java.nio.file.{Path, Paths}
@ -78,7 +78,7 @@ object AquaFile {
.map(source -> _) .map(source -> _)
.toEither .toEither
.left .left
.map(AquaScriptErrors(file.toString, source, _)) .map(AquaScriptErrors(_))
) )
) )

View File

@ -34,9 +34,8 @@ case class Unresolvable(msg: String) extends AquaFileError {
} }
// TODO there should be no AquaErrors, as they does not fit // TODO there should be no AquaErrors, as they does not fit
case class AquaScriptErrors(name: String, script: String, errors: NonEmptyChain[AquaError]) case class AquaScriptErrors(errors: NonEmptyChain[AquaError]) extends AquaFileError {
extends AquaFileError {
override def showForConsole: String = override def showForConsole: String =
errors.map(_.showForConsole(script)).toChain.toList.mkString("\n") errors.map(_.showForConsole).toChain.toList.mkString("\n")
} }

View File

@ -7,16 +7,7 @@ import cats.syntax.applicative._
import java.nio.file.Path import java.nio.file.Path
case class FileModuleId(file: Path) { case class FileModuleId(file: Path) {}
def targetPath(src: Path, target: Path, ext: String): Path = {
val aqua =
target.toAbsolutePath
.normalize()
.resolve(src.toAbsolutePath.normalize().relativize(file.toAbsolutePath.normalize()))
aqua.getParent.resolve(aqua.getFileName.toString.stripSuffix(".aqua") + s".$ext")
}
}
object FileModuleId { object FileModuleId {

View File

@ -1,10 +1,11 @@
package aqua.parser.lift package aqua.parser.lift
import cats.{Comonad, Eval}
import cats.parse.{LocationMap, Parser => P} import cats.parse.{LocationMap, Parser => P}
import cats.{Comonad, Eval}
import scala.language.implicitConversions import scala.language.implicitConversions
// TODO: rewrite FileSpan and Span under one trait
case class FileSpan(name: String, source: String, locationMap: Eval[LocationMap], span: Span) { case class FileSpan(name: String, source: String, locationMap: Eval[LocationMap], span: Span) {
def focus(ctx: Int): Option[FileSpan.Focus] = def focus(ctx: Int): Option[FileSpan.Focus] =
@ -16,11 +17,12 @@ 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(msg: String, onLeft: String, onRight: String = Console.RESET): String =
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" + spanFocus.toConsoleStr( s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" +
msg, spanFocus.toConsoleStr(
onLeft, msg,
onRight onLeft,
) onRight
)
} }
type F[T] = (FileSpan, T) type F[T] = (FileSpan, T)

View File

@ -12,18 +12,23 @@ case class Span(startIndex: Int, endIndex: Int) {
map.toLineCol(startIndex).flatMap { case (line, column) => map.toLineCol(startIndex).flatMap { case (line, column) =>
map map
.getLine(line) .getLine(line)
.map(l => .map { l =>
val pre =
(Math.max(0, line - ctx) until line).map(i => map.getLine(i).map(i -> _)).toList.flatten
val linePos = {
val (l1, l2) = l.splitAt(column)
val (lc, l3) = l2.splitAt(endIndex - startIndex)
(line, l1, lc, l3)
}
val post =
((line + 1) to (line + ctx)).map(i => map.getLine(i).map(i -> _)).toList.flatten
Span.Focus( Span.Focus(
(Math pre,
.max(0, line - ctx) until line).map(i => map.getLine(i).map(i -> _)).toList.flatten, { linePos,
val (l1, l2) = l.splitAt(column) post,
val (lc, l3) = l2.splitAt(endIndex - startIndex)
(line, l1, lc, l3)
},
((line + 1) to (line + ctx)).map(i => map.getLine(i).map(i -> _)).toList.flatten,
column column
) )
) }
} }
} }
} }
@ -48,7 +53,11 @@ object Span {
onLeft + s + (" " * (lastNSize - s.length)) + onRight + " " onLeft + s + (" " * (lastNSize - s.length)) + onRight + " "
} }
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String = { def toConsoleStr(
msg: String,
onLeft: String,
onRight: String = Console.RESET
): 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
pre.map(formatLine(_, onLeft, onRight)).mkString("\n") + pre.map(formatLine(_, onLeft, onRight)).mkString("\n") +