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
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
import aqua.parser.lift.{FileSpan, LiftParser, Span}
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
import cats.Eval
import cats.data.ValidatedNec
import cats.parse.LocationMap
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]] = {
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
Ast
.fromString[FileSpan.F](input)
.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)
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
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 cats.data.Validated
import cats.effect._
import cats.effect.std.Console
import cats.effect.std.{Console => ConsoleEff}
import cats.syntax.apply._
import cats.syntax.functor._
import com.monovore.decline.Opts
@ -11,12 +11,22 @@ import com.monovore.decline.effect.CommandIOApp
import fs2.io.file.Files
import org.typelevel.log4cats.slf4j.Slf4jLogger
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 {
import AppOps._
def main[F[_]: Concurrent: Files: Console: Logger]: Opts[F[ExitCode]] = {
def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
versionOpt
.as(
versionAndExit
@ -34,6 +44,7 @@ object AquaCli extends IOApp with LogSupport {
logLevelOpt
).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) =>
WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel))
WLogger.setDefaultFormatter(CustomLogFormatter)
// if there is `--help` or `--version` flag - show help and version
// otherwise continue program execution
@ -52,8 +63,8 @@ object AquaCli extends IOApp with LogSupport {
case Validated.Invalid(errs) =>
errs.map(println)
ExitCode.Error
case Validated.Valid(res) =>
res.map(println)
case Validated.Valid(results) =>
results.map(println)
ExitCode.Success
}
}

View File

@ -10,6 +10,7 @@ import aqua.parser.lexer.Token
import aqua.parser.lift.FileSpan
import aqua.semantics.{CompilerState, Semantics}
import cats.Applicative
import cats.data.Validated.{Invalid, Valid}
import cats.data._
import cats.effect.kernel.Concurrent
import cats.syntax.flatMap._
@ -18,19 +19,30 @@ import cats.syntax.monoid._
import cats.syntax.show._
import fs2.io.file.Files
import fs2.text
import wvlet.log.LogSupport
import java.nio.file.Path
object AquaCompiler {
object AquaCompiler extends LogSupport {
sealed trait CompileTarget
case object TypescriptTarget 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 {
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](
@ -69,7 +81,19 @@ object AquaCompiler {
(errs ++ showProcErrors(proc.errors), preps)
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) =>
(
@ -95,7 +119,7 @@ object AquaCompiler {
): Chain[String] =
errors.map(err =>
err._1.unit._1
.focus(1)
.focus(2)
.map(_.toConsoleStr(err._2, Console.CYAN))
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
)
@ -108,28 +132,47 @@ object AquaCompiler {
bodyConfig: BodyConfig
): F[ValidatedNec[String, Chain[String]]] =
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]]] {
case Validated.Invalid(e) =>
Applicative[F].pure(Validated.invalid(e))
case Validated.Valid(preps) =>
(compileTo match {
case TypescriptTarget =>
preps
.map(p => writeFile(p.target("ts"), TypescriptFile(p.model).generateTS(bodyConfig)))
preps.map { p =>
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
case AirTarget =>
preps
.flatMap(p =>
p.model.resolveFunctions.map { fc =>
fc.funcName -> FuncAirGen(fc).generateAir(bodyConfig).show
}.map { case (n, g) =>
writeFile(
p.target(n + ".air"),
g
)
}
p.model.resolveFunctions
.map(fc => FuncAirGen(fc).generateAir(bodyConfig).show)
.map { generated =>
val tpV = p.targetPath("ts")
tpV match {
case Invalid(t) =>
EitherT.pure(t.getMessage)
case Valid(tp) =>
writeFile(
tp,
generated
)
}
}
)
}).foldLeft(
@ -140,7 +183,7 @@ object AquaCompiler {
w <- writeET.value
} yield (a, w) match {
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(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[F, String, String](
EitherT[F, String, Unit](
fs2.Stream
.emit(
content
@ -165,7 +208,7 @@ object AquaCompiler {
}
.compile
.drain
.map(_ => Right(s"Compiled $file"))
.map(_ => Right(()))
)
}

View File

@ -1,20 +1,18 @@
package aqua
import aqua.parser.lift.Span
import cats.Eval
import aqua.parser.lift.FileSpan
import cats.data.NonEmptyList
import cats.parse.LocationMap
import cats.parse.Parser.Expectation
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
.focus(Eval.later(LocationMap(script)), 2)
.focus(3)
.map(
_.toConsoleStr(
message,
@ -27,13 +25,13 @@ case class CustomSyntaxError(span: Span, message: String) extends AquaError {
) + 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 =
Span(offset, offset + 1)
.focus(Eval.later(LocationMap(script)), 2)
.map(
_.toConsoleStr(
override def showForConsole: String =
span
.focus(3)
.map(spanFocus =>
spanFocus.toConsoleStr(
s"Syntax error, expected: ${expectations.toList.mkString(", ")}",
Console.RED
)
@ -43,12 +41,3 @@ case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) ext
.mkString(", ")
) + 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 {
case Validated.Invalid(errs) =>
errs.map(println)
case Validated.Valid(res) =>
res.map(println)
case Validated.Valid(_) =>
}
}

View File

@ -7,10 +7,10 @@ import aqua.parser.head.ImportExpr
import aqua.parser.lift.FileSpan
import cats.data.{EitherT, NonEmptyChain}
import cats.effect.Concurrent
import cats.syntax.apply._
import cats.syntax.functor._
import fs2.io.file.Files
import fs2.text
import cats.syntax.functor._
import cats.syntax.apply._
import java.nio.file.{Path, Paths}
@ -78,7 +78,7 @@ object AquaFile {
.map(source -> _)
.toEither
.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
case class AquaScriptErrors(name: String, script: String, errors: NonEmptyChain[AquaError])
extends AquaFileError {
case class AquaScriptErrors(errors: NonEmptyChain[AquaError]) extends AquaFileError {
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
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")
}
}
case class FileModuleId(file: Path) {}
object FileModuleId {

View File

@ -1,10 +1,11 @@
package aqua.parser.lift
import cats.{Comonad, Eval}
import cats.parse.{LocationMap, Parser => P}
import cats.{Comonad, Eval}
import scala.language.implicitConversions
// TODO: rewrite FileSpan and Span under one trait
case class FileSpan(name: String, source: String, locationMap: Eval[LocationMap], span: Span) {
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) {
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String =
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" + spanFocus.toConsoleStr(
msg,
onLeft,
onRight
)
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" +
spanFocus.toConsoleStr(
msg,
onLeft,
onRight
)
}
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
.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(
(Math
.max(0, line - ctx) until line).map(i => map.getLine(i).map(i -> _)).toList.flatten, {
val (l1, l2) = l.splitAt(column)
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,
pre,
linePos,
post,
column
)
)
}
}
}
}
@ -48,7 +53,11 @@ object Span {
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 line3Mult = if (line3Length == 0) 1 else line3Length
pre.map(formatLine(_, onLeft, onRight)).mkString("\n") +