Assign a UUID to Each AST Node (#738)

This commit is contained in:
Josef 2020-05-15 13:43:07 +02:00 committed by GitHub
parent a42495a68e
commit 81bde28589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 90 deletions

View File

@ -471,17 +471,21 @@ object AstToIr {
val leftArg = translateCallArgument(left)
val rightArg = translateCallArgument(right)
if (leftArg.name.isDefined) {
IR.Error.Syntax(left, IR.Error.Syntax.NamedArgInOperator)
} else if (rightArg.name.isDefined) {
IR.Error.Syntax(right, IR.Error.Syntax.NamedArgInOperator)
} else {
Application.Operator.Binary(
leftArg,
Name.Literal(fn.name, getIdentifiedLocation(fn)),
rightArg,
getIdentifiedLocation(callable)
)
fn match {
case AST.Ident.Opr.any(fn) =>
if (leftArg.name.isDefined) {
IR.Error.Syntax(left, IR.Error.Syntax.NamedArgInOperator)
} else if (rightArg.name.isDefined) {
IR.Error.Syntax(right, IR.Error.Syntax.NamedArgInOperator)
} else {
Application.Operator.Binary(
leftArg,
Name.Literal(fn.name, getIdentifiedLocation(fn)),
rightArg,
getIdentifiedLocation(callable)
)
}
case _ => IR.Error.Syntax(left, IR.Error.Syntax.InvalidOperatorName)
}
case AST.App.Prefix(_, _) =>
throw new UnhandledEntity(callable, "translateCallable")

View File

@ -2908,6 +2908,10 @@ object IR {
case object NamedArgInOperator extends Reason {
override def explanation: String = "Named argument in operator section."
}
case object InvalidOperatorName extends Reason {
override def explanation: String = "Invalid operator name."
}
}
/** A representation of an invalid piece of IR.

View File

@ -173,6 +173,10 @@ object Shape extends ShapeImplicit {
import HasSpan.implicits._
import AST.StreamOf
/// Utils ///
val newline = R + '\n'
/////////////////
//// Invalid ////
/////////////////
@ -281,7 +285,7 @@ object Shape extends ShapeImplicit {
final case class Infix[T](
larg: T,
loff: Int,
opr: AST.Opr,
opr: T,
roff: Int,
rarg: T
) extends App[T]
@ -544,10 +548,10 @@ object Shape extends ShapeImplicit {
object TextBlock extends IntermediateTrait[TextBlock] {
def lineRepr[T: Repr](off: Int, l: TextBlockLine[SegmentFmt[T]]): Builder =
R + l.empty_lines.map(Block.newline + _) + Block.newline + off + l.text
R + l.empty_lines.map(newline + _) + newline + off + l.text
def lineSpan[T: HasSpan](off: Int, l: TextBlockLine[SegmentFmt[T]]): Int = {
val emptyLinesSpan = l.empty_lines.map(Block.newline.span + _).sum
emptyLinesSpan + Block.newline.span + off + l.text.span()
val emptyLinesSpan = l.empty_lines.map(newline.span + _).sum
emptyLinesSpan + newline.span + off + l.text.span()
}
implicit def ftor: Functor[TextBlock] = semi.functor
@ -724,8 +728,10 @@ object Shape extends ShapeImplicit {
implicit def repr[T: Repr]: Repr[Infix[T]] =
t => R + t.larg + t.loff + t.opr + t.roff + t.rarg
implicit def ozip[T: HasSpan]: OffsetZip[Infix, T] = t => {
val rargIndex = Index(t.larg.span + t.loff + t.opr.span + t.roff)
t.copy(larg = (Index.Start, t.larg), rarg = (rargIndex, t.rarg))
val larg = Index.Start -> t.larg
val opr = Index(t.larg.span + t.loff) -> t.opr
val rarg = Index(t.larg.span + t.loff + t.opr.span + t.roff) -> t.rarg
t.copy(larg = larg, opr = opr, rarg = rarg)
}
implicit def span[T: HasSpan]: HasSpan[Infix[T]] =
t => t.larg.span + t.loff + t.opr.span + t.roff + t.rarg.span
@ -805,8 +811,6 @@ object Shape extends ShapeImplicit {
headSpan + emptyLinesSpan + firstLineSpan + linesSpan
}
/// Utils ///
val newline = R + '\n'
/// Block type ///
sealed trait Type
@ -833,11 +837,20 @@ object Shape extends ShapeImplicit {
object Module {
implicit def ftor: Functor[Module] = semi.functor
implicit def fold: Foldable[Module] = semi.foldable
implicit def ozip[T]: OffsetZip[Module, T] = _.map(Index.Start -> _)
implicit def ozip[T: HasSpan]: OffsetZip[Module, T] = t => {
var index = 0
val lines = t.lines.map { line =>
val elem = line.elem.map((Index(index), _))
index += line.span + newline.span
line.copy(elem = elem)
}
t.copy(lines = lines)
}
implicit def repr[T: Repr]: Repr[Module[T]] =
t => R + t.lines.head + t.lines.tail.map(Block.newline + _)
t => R + t.lines.head + t.lines.tail.map(newline + _)
implicit def span[T: HasSpan]: HasSpan[Module[T]] =
t => t.lines.span() + (t.lines.size - 1) * Block.newline.span
t => t.lines.span() + (t.lines.size - 1) * newline.span
}
object Macro extends IntermediateTrait[Macro] {
@ -935,9 +948,8 @@ object Shape extends ShapeImplicit {
implicit def ftor[T]: Functor[Documented] = semi.functor
implicit def fold[T]: Foldable[Documented] = semi.foldable
implicit def repr[T: Repr]: Repr[Documented[T]] = t => {
val symbolRepr = R + symbol + symbol
val betweenDocAstRepr = R + Block.newline +
Block.newline.build * t.emptyLinesBetween
val symbolRepr = R + symbol + symbol
val betweenDocAstRepr = R + newline + newline.build * t.emptyLinesBetween
R + symbolRepr + t.doc + betweenDocAstRepr + t.ast
}
implicit def offsetZip[T]: OffsetZip[Documented, T] =

View File

@ -11,10 +11,10 @@ object Parse {
@JSExportTopLevel("parse")
def parse(program: String, idsJson: String): String = {
try {
val ids = Parser.idMapFromJson(idsJson).left.map { error =>
val idmap = Parser.idMapFromJson(idsJson).left.map { error =>
throw new ParserError("Could not deserialize idmap.", error)
}.merge
new Parser().run(new Reader(program), ids).toJson().noSpacesSortKeys
new Parser().run(new Reader(program), idmap).toJson().noSpacesSortKeys
} catch {
// FIXME We wrap the error message in JavaScriptException, so that javascript
// can display it. This is no longer needed in scalajs 1.0

View File

@ -2,7 +2,7 @@ package org.enso.syntax.text
import java.util.UUID
import cats.{Foldable, Functor}
import cats.Foldable
import org.enso.data.List1
import org.enso.data.Span
import org.enso.flexer
@ -12,7 +12,6 @@ import org.enso.syntax.text.AST.Block.OptLine
import org.enso.syntax.text.AST.Macro.Match.SegmentOps
import org.enso.syntax.text.AST.App
import org.enso.syntax.text.ast.meta.Builtin
import org.enso.syntax.text.ast.Repr
import org.enso.syntax.text.prec.Macro
import org.enso.syntax.text.spec.ParserDef
import cats.implicits._
@ -230,14 +229,13 @@ class Parser {
def run(input: Reader, idMap: IDMap): AST.Module = {
val tokenStream = engine.run(input)
val spanned = tokenStream.map(attachModuleLocations)
val resolved = spanned.map(Macro.run) match {
val resolved = spanned.map(Macro.run) match {
case flexer.Parser.Result(_, flexer.Parser.Result.Success(mod)) =>
val mod2 = annotateModule(idMap, mod)
resolveMacros(mod2).asInstanceOf[AST.Module]
case _ => throw ParsingFailed
}
val withExpressionIds = fillExpressionIds(resolved)
withExpressionIds
resolved
}
/**
@ -335,10 +333,7 @@ class Parser {
ids = ids.tail
ast.setID(id)
case _ =>
ast match {
case AST.Macro.Match.any(_) => ast.withNewID()
case _ => ast
}
ast.withNewID()
}
}
}
@ -364,60 +359,6 @@ class Parser {
case _ => ast.map(resolveMacros)
}
/** All [[AST]] elements that are top-level expressions (i.e. can be nodes in
* IDE and are potentially worth of visualizing.
*/
def fillExpressionIds(module: AST.Module): AST.Module = {
sealed trait Context
/** A module block or nested block (e.g. method body). */
final case object Block extends Context
/** Any non-block context */
final case object Other extends Context
/** Goes over AST and for Assignments (either infix or section right) that
* are within [[AST.Block]] or [[AST.Module]] node, assigns IDs to the
* right hand side. Any other expression in the block gets ID on its root
* AST. All other nodes are returned as-is.
*/
implicit class Processor[T[S] <: Shape[S]](ast: AST.ASTOf[T])(
implicit
functor: Functor[T],
fold: Foldable[T],
repr: Repr[T[AST]],
ozip: OffsetZip[T, AST]
) {
def process(context: Context): AST.ASTOf[T] = {
def assignmentInBlock(opr: AST.Opr): Boolean =
context == Block && opr.name == "="
ast match {
case AST.Module.any(block) => block.map(_.process(Block))
case AST.Block.any(block) => block.map(_.process(Block))
case AST.App.Infix.any(infix) if assignmentInBlock(infix.opr) =>
val newShape = infix.shape.copy(
larg = infix.larg.process(Other),
opr = infix.opr.process(Other),
rarg = infix.rarg.process(Other).withNewIDIfMissing()
)
infix.copy(shape = newShape)
case AST.App.Section.Right.any(right)
if assignmentInBlock(right.opr) =>
val newShape = right.shape.copy(
arg = right.arg.process(Other),
opr = right.opr.process(Other)
)
right.copy(shape = newShape)
case otherAst if context == Block =>
otherAst.process(Other).withNewIDIfMissing()
case _ => ast.map(_.process(Other))
}
}.asInstanceOf[AST.ASTOf[T]] // Note: [Type safety]
}
module.process(Other)
}
/* Note: [Type safety]
* ~~~~~~~~~~~~~~~~~~~
* This function promises to return AST with the same shape as it

View File

@ -48,8 +48,12 @@ class ParserTest extends AnyFlatSpec with Matchers {
def assertIdentity(input: String): Assertion = {
val module = Parser().run(input)
val idmap1 = module.idMap
val idmap2 = Parser().run(new Reader(input), idmap1).idMap
println(module.zipWithOffset)
assertSpan(input, module)
assert(module.show() == new Reader(input).toString())
assert(idmap1 == idmap2)
}
implicit class TestString(input: String) {