mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
120 improve output (#137)
This commit is contained in:
parent
9990eb0a66
commit
f34cd3a4e2
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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(()))
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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(_) =>
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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(_))
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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") +
|
||||
|
Loading…
Reference in New Issue
Block a user