mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Handles disable and doc comments (#780)
This commit is contained in:
parent
2fdd053fd3
commit
e2bac23e26
@ -52,6 +52,17 @@ Such comments are automatically connected to the language construct, and can be
|
||||
used both for displaying static documentation as well as providing dynamic help
|
||||
to the user in Enso Studio itself.
|
||||
|
||||
A documentation comment in Enso is a _block_, and the block is started with a
|
||||
double `#` character. The block ends when the indentation returns to the
|
||||
baseline indentation for that block (see [blocks](./functions.md#code-blocks)
|
||||
for more information). By way of example:
|
||||
|
||||
```
|
||||
## My documentation comment
|
||||
continues all the way down
|
||||
until I unindent again.
|
||||
```
|
||||
|
||||
The tool that generates this documentation aims to be fairly robust, and tries
|
||||
to assign produce sensible results even if the user makes a mistake. Such
|
||||
mistakes will be highlighted to the user.
|
||||
@ -77,7 +88,7 @@ accompanied by a description. This description directly follows the tag
|
||||
declaration with one space.
|
||||
|
||||
```ruby
|
||||
# DEPRECATED Use `seeFoo` instead
|
||||
## DEPRECATED Use `seeFoo` instead
|
||||
```
|
||||
|
||||
If the user provides an unknown tag the documentation will contain that tag, but
|
||||
|
@ -31,6 +31,7 @@ public class TopLevelScope implements TruffleObject {
|
||||
private final Map<String, Module> modules;
|
||||
private final Scope scope = Scope.newBuilder("top_scope", this).build();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of top scope.
|
||||
*
|
||||
|
@ -1,19 +1,11 @@
|
||||
package org.enso.compiler
|
||||
|
||||
import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse.{
|
||||
AliasAnalysis,
|
||||
DataflowAnalysis,
|
||||
DemandAnalysis,
|
||||
TailCall
|
||||
}
|
||||
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis, DemandAnalysis, TailCall}
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.lint.UnusedBindings
|
||||
import org.enso.compiler.pass.optimise.{
|
||||
ApplicationSaturation,
|
||||
LambdaConsolidate
|
||||
}
|
||||
import org.enso.compiler.pass.resolve.{IgnoredBindings, OverloadsResolution}
|
||||
import org.enso.compiler.pass.optimise.{ApplicationSaturation, LambdaConsolidate}
|
||||
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings, OverloadsResolution}
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
|
||||
class Passes(passes: Option[List[IRPass]] = None) {
|
||||
@ -26,6 +18,7 @@ class Passes(passes: Option[List[IRPass]] = None) {
|
||||
*/
|
||||
private val passOrdering: List[IRPass] = passes.getOrElse(
|
||||
List(
|
||||
DocumentationComments,
|
||||
ComplexType,
|
||||
FunctionBinding,
|
||||
GenerateMethodBodies,
|
||||
|
@ -180,6 +180,7 @@ object AstToIr {
|
||||
translateExpression(body),
|
||||
getIdentifiedLocation(inputAst)
|
||||
)
|
||||
case AST.Comment.any(comment) => translateComment(comment)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(inputAst, "translateModuleSymbol")
|
||||
}
|
||||
@ -220,6 +221,7 @@ object AstToIr {
|
||||
case AST.Ident.Cons.any(include) => translateIdent(include)
|
||||
case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
|
||||
case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs)
|
||||
case AST.Comment.any(inputAST) => translateComment(inputAST)
|
||||
case assignment @ AstView.BasicAssignment(_, _) =>
|
||||
translateExpression(assignment)
|
||||
case _ =>
|
||||
@ -806,17 +808,11 @@ object AstToIr {
|
||||
* @param comment the comment to transform
|
||||
* @return the [[IR]] representation of `comment`
|
||||
*/
|
||||
def translateComment(comment: AST): Expression = {
|
||||
def translateComment(comment: AST): Comment = {
|
||||
comment match {
|
||||
case AST.Comment(_) =>
|
||||
Error.Syntax(
|
||||
comment,
|
||||
Error.Syntax.UnsupportedSyntax("comments")
|
||||
)
|
||||
case AST.Documented(doc, _, ast) =>
|
||||
case AST.Comment(lines) =>
|
||||
Comment.Documentation(
|
||||
translateExpression(ast),
|
||||
doc,
|
||||
lines.mkString("\n"),
|
||||
getIdentifiedLocation(comment)
|
||||
)
|
||||
case _ =>
|
||||
|
@ -351,8 +351,11 @@ class IRToTruffle(
|
||||
case function: IR.Function => processFunction(function)
|
||||
case binding: IR.Expression.Binding => processBinding(binding)
|
||||
case caseExpr: IR.Case => processCase(caseExpr)
|
||||
case comment: IR.Comment => processComment(comment)
|
||||
case err: IR.Error => processError(err)
|
||||
case _: IR.Comment =>
|
||||
throw new CompilerError(
|
||||
"Comments should not be present during codegen."
|
||||
)
|
||||
case err: IR.Error => processError(err)
|
||||
case IR.Foreign.Definition(_, _, _, _, _) =>
|
||||
throw new CompilerError(
|
||||
s"Foreign expressions not yet implemented: $ir."
|
||||
@ -377,18 +380,6 @@ class IRToTruffle(
|
||||
|
||||
// === Processing =========================================================
|
||||
|
||||
/** Performs code generation for any comments left in the Enso [[IR]].
|
||||
*
|
||||
* Comments do not exist at execution time as they cannot be reflected upon,
|
||||
* and so this code generation pass just generetes code for the associated
|
||||
* expression.
|
||||
*
|
||||
* @param comment the comment node to generate code for
|
||||
* @return the truffle nodes corresponding to `comment`
|
||||
*/
|
||||
def processComment(comment: IR.Comment): RuntimeExpression =
|
||||
this.run(comment.commented)
|
||||
|
||||
/** Performs code generation for an Enso block expression.
|
||||
*
|
||||
* @param block the block to generate code for
|
||||
|
@ -11,7 +11,6 @@ import org.enso.compiler.core.ir.MetadataStorage
|
||||
import org.enso.compiler.core.ir.MetadataStorage.MetadataPair
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.IRPass
|
||||
import org.enso.syntax.text.ast.Doc
|
||||
import org.enso.syntax.text.{AST, Debug, Location}
|
||||
|
||||
/** [[IR]] is a temporary and fairly unsophisticated internal representation
|
||||
@ -727,8 +726,8 @@ object IR {
|
||||
arguments: List[IR.DefinitionArgument] = arguments,
|
||||
body: Expression = body,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = MetadataStorage(),
|
||||
diagnostics: DiagnosticStorage = DiagnosticStorage(),
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Binding = {
|
||||
val res = Binding(
|
||||
@ -2985,26 +2984,21 @@ object IR {
|
||||
// === Comments =============================================================
|
||||
|
||||
/** Enso comment entities. */
|
||||
sealed trait Comment extends Expression {
|
||||
sealed trait Comment extends Expression with Module.Scope.Definition {
|
||||
override def mapExpressions(fn: Expression => Expression): Comment
|
||||
override def setLocation(location: Option[IdentifiedLocation]): Comment
|
||||
|
||||
/** The expression being commented. */
|
||||
val commented: Expression
|
||||
}
|
||||
object Comment {
|
||||
|
||||
/** A documentation comment in the Enso source.
|
||||
*
|
||||
* @param commented the expression with which the comment is associated
|
||||
* @param doc the documentation of `commented`
|
||||
* @param doc the documentation entity
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Documentation(
|
||||
override val commented: Expression,
|
||||
doc: Doc,
|
||||
doc: String,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
@ -3014,7 +3008,6 @@ object IR {
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param commented the expression with which the comment is associated
|
||||
* @param doc the documentation of `commented`
|
||||
* @param location the source location that the node corresponds to
|
||||
* @param passData the pass metadata associated with this node
|
||||
@ -3023,14 +3016,13 @@ object IR {
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
commented: Expression = commented,
|
||||
doc: Doc = doc,
|
||||
doc: String = doc,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Documentation = {
|
||||
val res = Documentation(commented, doc, location, passData, diagnostics)
|
||||
val res = Documentation(doc, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
@ -3041,14 +3033,11 @@ object IR {
|
||||
|
||||
override def mapExpressions(
|
||||
fn: Expression => Expression
|
||||
): Documentation = {
|
||||
copy(commented = fn(commented))
|
||||
}
|
||||
): Documentation = this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Comment.Documentation(
|
||||
|commented = $commented,
|
||||
|doc = $doc,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
@ -3057,7 +3046,7 @@ object IR {
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(commented)
|
||||
override def children: List[IR] = List()
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,8 @@ class MetadataStorage(
|
||||
res.metadata = this.metadata
|
||||
res
|
||||
}
|
||||
|
||||
override def toString: String = metadata.toString()
|
||||
}
|
||||
object MetadataStorage extends MetadataStorageSyntax {
|
||||
|
||||
|
@ -166,6 +166,10 @@ case object AliasAnalysis extends IRPass {
|
||||
"Complex type definitions should not be present during " +
|
||||
"alias analysis."
|
||||
)
|
||||
case _: IR.Comment.Documentation =>
|
||||
throw new CompilerError(
|
||||
"Documentation should not exist as an entity during alias analysis."
|
||||
)
|
||||
case err: IR.Error => err
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,11 @@ case object DataflowAnalysis extends IRPass {
|
||||
case _: IR.Module.Scope.Definition.Type =>
|
||||
throw new CompilerError(
|
||||
"Complex type definitions should not be present during " +
|
||||
"alias analysis."
|
||||
"dataflow analysis."
|
||||
)
|
||||
case _: IR.Comment.Documentation =>
|
||||
throw new CompilerError(
|
||||
"Documentation should not exist as an entity during dataflow analysis."
|
||||
)
|
||||
case err: IR.Error => err
|
||||
}
|
||||
@ -143,7 +147,10 @@ case object DataflowAnalysis extends IRPass {
|
||||
case typ: IR.Type => analyseType(typ, info)
|
||||
case name: IR.Name => analyseName(name, info)
|
||||
case cse: IR.Case => analyseCase(cse, info)
|
||||
case comment: IR.Comment => analyseComment(comment, info)
|
||||
case _: IR.Comment =>
|
||||
throw new CompilerError(
|
||||
"Comments should not be present during dataflow analysis."
|
||||
)
|
||||
case literal: IR.Literal =>
|
||||
literal.updateMetadata(this -->> info)
|
||||
case foreign: IR.Foreign =>
|
||||
@ -453,28 +460,6 @@ case object DataflowAnalysis extends IRPass {
|
||||
.updateMetadata(this -->> info)
|
||||
}
|
||||
|
||||
/** Performs dataflow analysis on a comment entity.
|
||||
*
|
||||
* A comment expression is simply dependent on the result of the commented
|
||||
* value.
|
||||
*
|
||||
* @param comment the comment to perform dataflow analysis on
|
||||
* @param info the dependency information for the module
|
||||
* @return `comment`, with attached dependency information
|
||||
*/
|
||||
def analyseComment(comment: IR.Comment, info: DependencyInfo): IR.Comment = {
|
||||
comment match {
|
||||
case doc @ IR.Comment.Documentation(commented, _, _, _, _) =>
|
||||
info.updateAt(asStatic(commented), Set(asStatic(comment)))
|
||||
|
||||
doc
|
||||
.copy(
|
||||
commented = analyseExpression(commented, info)
|
||||
)
|
||||
.updateMetadata(this -->> info)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs dataflow analysis on a function definition argument.
|
||||
*
|
||||
* A function definition argument is dependent purely on its default, if said
|
||||
|
@ -101,7 +101,11 @@ case object TailCall extends IRPass {
|
||||
case _: IR.Module.Scope.Definition.Type =>
|
||||
throw new CompilerError(
|
||||
"Complex type definitions should not be present during " +
|
||||
"alias analysis."
|
||||
"tail call analysis."
|
||||
)
|
||||
case _: IR.Comment.Documentation =>
|
||||
throw new CompilerError(
|
||||
"Documentation should not exist as an entity during tail call analysis."
|
||||
)
|
||||
case err: IR.Error => err
|
||||
}
|
||||
@ -129,7 +133,10 @@ case object TailCall extends IRPass {
|
||||
case foreign: IR.Foreign =>
|
||||
foreign.updateMetadata(this -->> TailPosition.NotTail)
|
||||
case literal: IR.Literal => analyseLiteral(literal, isInTailPosition)
|
||||
case comment: IR.Comment => analyseComment(comment, isInTailPosition)
|
||||
case _: IR.Comment =>
|
||||
throw new CompilerError(
|
||||
"Comments should not be present during tail call analysis."
|
||||
)
|
||||
case block @ IR.Expression.Block(expressions, returnValue, _, _, _, _) =>
|
||||
block
|
||||
.copy(
|
||||
@ -159,24 +166,6 @@ case object TailCall extends IRPass {
|
||||
name.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
|
||||
}
|
||||
|
||||
/** Performs tail call analysis on a comment occurrence.
|
||||
*
|
||||
* @param comment the comment to analyse
|
||||
* @param isInTailPosition whether the comment occurs in tail position or not
|
||||
* @return `comment`, annotated with tail position metadata
|
||||
*/
|
||||
def analyseComment(
|
||||
comment: IR.Comment,
|
||||
isInTailPosition: Boolean
|
||||
): IR.Comment = {
|
||||
comment match {
|
||||
case doc @ IR.Comment.Documentation(expr, _, _, _, _) =>
|
||||
doc
|
||||
.copy(commented = analyseExpression(expr, isInTailPosition))
|
||||
.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs tail call analysis on a literal.
|
||||
*
|
||||
* @param literal the literal to analyse
|
||||
|
@ -107,7 +107,12 @@ case object FunctionBinding extends IRPass {
|
||||
case _: IR.Module.Scope.Definition.Type =>
|
||||
throw new CompilerError(
|
||||
"Complex type definitions should not be present during " +
|
||||
"alias analysis."
|
||||
"function binding desugaring."
|
||||
)
|
||||
case _: IR.Comment.Documentation =>
|
||||
throw new CompilerError(
|
||||
"Documentation should not be present during function binding" +
|
||||
"desugaring."
|
||||
)
|
||||
case e: IR.Error => e
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
package org.enso.compiler.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.ir.MetadataStorage._
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** Associates doc comments with the commented entities as metadata.
|
||||
*
|
||||
* This pass has no configuration.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*/
|
||||
case object DocumentationComments extends IRPass {
|
||||
override type Metadata = Doc
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
override val invalidatedPasses: Seq[IRPass] = Seq()
|
||||
override val precursorPasses: Seq[IRPass] = Seq()
|
||||
|
||||
/** Collects comments for a module and assigns them to the commented
|
||||
* entities as metadata.
|
||||
*
|
||||
* @param ir the Enso IR to process
|
||||
* @param moduleContext a context object that contains the information needed
|
||||
* to process a module
|
||||
* @return `ir`, possibly having made transformations or annotations to that
|
||||
* IR.
|
||||
*/
|
||||
override def runModule(
|
||||
ir: IR.Module,
|
||||
moduleContext: ModuleContext
|
||||
): IR.Module = resolveModule(ir)
|
||||
|
||||
/** Collects comments for an expression.
|
||||
*
|
||||
* @param ir the Enso IR to process
|
||||
* @param inlineContext a context object that contains the information
|
||||
* needed for inline evaluation
|
||||
* @return `ir`, possibly having made transformations or annotations to that
|
||||
* IR.
|
||||
*/
|
||||
override def runExpression(
|
||||
ir: IR.Expression,
|
||||
inlineContext: InlineContext
|
||||
): IR.Expression = resolveExpression(ir)
|
||||
|
||||
// === Pass Internals =======================================================
|
||||
|
||||
private def resolveExpression(ir: IR.Expression): IR.Expression =
|
||||
ir.transformExpressions({
|
||||
case block: IR.Expression.Block =>
|
||||
val newLines = resolveList(block.expressions :+ block.returnValue)
|
||||
val newExpressions = newLines.init.map(resolveExpression)
|
||||
val newReturn = resolveExpression(newLines.last)
|
||||
block.copy(expressions = newExpressions, returnValue = newReturn)
|
||||
})
|
||||
|
||||
private def resolveList[T <: IR](items: List[T]): List[T] = {
|
||||
var lastDoc: Option[IR.Comment.Documentation] = None
|
||||
items.flatMap {
|
||||
case doc: IR.Comment.Documentation =>
|
||||
lastDoc = Some(doc)
|
||||
None
|
||||
case other =>
|
||||
val res = lastDoc match {
|
||||
case Some(doc) => other.updateMetadata(this -->> Doc(doc.doc))
|
||||
case None => other
|
||||
}
|
||||
lastDoc = None
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
private def resolveDefinition(
|
||||
ir: IR.Module.Scope.Definition
|
||||
): IR.Module.Scope.Definition = ir match {
|
||||
case method: IR.Module.Scope.Definition.Method.Binding =>
|
||||
method.copy(body = resolveExpression(method.body))
|
||||
case method: IR.Module.Scope.Definition.Method.Explicit =>
|
||||
method.copy(body = resolveExpression(method.body))
|
||||
case tpe: IR.Module.Scope.Definition.Type =>
|
||||
tpe.copy(body = resolveList(tpe.body).map(resolveIr))
|
||||
case d: IR.Module.Scope.Definition.Atom => d
|
||||
case doc: IR.Comment.Documentation => doc
|
||||
case err: IR.Error => err
|
||||
}
|
||||
|
||||
private def resolveModule(ir: IR.Module): IR.Module = {
|
||||
val newBindings = resolveList(ir.bindings).map(resolveDefinition)
|
||||
ir.copy(bindings = newBindings)
|
||||
}
|
||||
|
||||
private def resolveIr(ir: IR): IR = ir match {
|
||||
case module: IR.Module => resolveModule(module)
|
||||
case expr: IR.Expression => resolveExpression(expr)
|
||||
case df: IR.Module.Scope.Definition => resolveDefinition(df)
|
||||
case imp: IR.Module.Scope.Import => imp
|
||||
case arg: IR.CallArgument => arg
|
||||
case arg: IR.DefinitionArgument => arg
|
||||
}
|
||||
|
||||
// === Metadata =============================================================
|
||||
|
||||
sealed case class Doc(documentation: String) extends IRPass.Metadata {
|
||||
override val metadataName: String = "DocumentationComments.Doc"
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings
|
||||
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings}
|
||||
|
||||
class PassesTest extends CompilerTest {
|
||||
|
||||
@ -38,6 +38,7 @@ class PassesTest extends CompilerTest {
|
||||
"get the precursors of a given pass" in {
|
||||
passes.getPrecursors(AliasAnalysis) shouldEqual Some(
|
||||
List(
|
||||
DocumentationComments,
|
||||
ComplexType,
|
||||
FunctionBinding,
|
||||
GenerateMethodBodies,
|
||||
|
@ -424,4 +424,37 @@ class AstToIrTest extends CompilerTest {
|
||||
.reason shouldBe an[IR.Error.Syntax.InterfaceDefinition.type]
|
||||
}
|
||||
}
|
||||
|
||||
"Documentation comments" should {
|
||||
"work at the top level" in {
|
||||
val ir =
|
||||
"""
|
||||
|## Some documentation for foo
|
||||
|foo a b = a + b
|
||||
|""".stripMargin.toIrModule.bindings.head
|
||||
|
||||
ir shouldBe an[IR.Comment.Documentation]
|
||||
ir.asInstanceOf[IR.Comment.Documentation]
|
||||
.doc shouldEqual " Some documentation for foo"
|
||||
}
|
||||
|
||||
"work within top-level blocks" in {
|
||||
val ir =
|
||||
"""
|
||||
|a ->
|
||||
| ## Some docs for b
|
||||
| b = 1
|
||||
| 10
|
||||
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
val comment = ir.body
|
||||
.asInstanceOf[IR.Expression.Block]
|
||||
.expressions
|
||||
.head
|
||||
comment shouldBe an[IR.Comment.Documentation]
|
||||
comment
|
||||
.asInstanceOf[IR.Comment.Documentation]
|
||||
.doc shouldEqual " Some docs for b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import org.enso.compiler.context.ModuleContext
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.Module.Scope.Definition
|
||||
import org.enso.compiler.pass.desugar.ComplexType
|
||||
import org.enso.compiler.pass.resolve.DocumentationComments
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
@ -15,6 +16,7 @@ class ComplexTypeTest extends CompilerTest {
|
||||
val passes = new Passes
|
||||
|
||||
val precursorPasses: List[IRPass] = passes.getPrecursors(ComplexType).get
|
||||
|
||||
val passConfig: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
|
@ -0,0 +1,182 @@
|
||||
package org.enso.compiler.test.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.resolve.DocumentationComments
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class DocumentationCommentsTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
val precursorPasses: List[IRPass] = List()
|
||||
val passConfig: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(precursorPasses, passConfig)
|
||||
|
||||
/** Resolves documentation comments in a module.
|
||||
*
|
||||
* @param ir the module
|
||||
*/
|
||||
implicit class ResolveModule(ir: IR.Module) {
|
||||
|
||||
/** Resolves documentation comments for [[ir]].
|
||||
*
|
||||
* @param moduleContext the context in which to resolve
|
||||
* @return [[ir]], with documentation resolved
|
||||
*/
|
||||
def resolve(implicit moduleContext: ModuleContext): IR.Module = {
|
||||
DocumentationComments.runModule(ir, moduleContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves documentation comments in an expression.
|
||||
*
|
||||
* @param ir the expression
|
||||
*/
|
||||
implicit class ResolveExpression(ir: IR.Expression) {
|
||||
|
||||
/** Resolves documentation comments for [[ir]].
|
||||
*
|
||||
* @param inlineContext the context in which to resolve
|
||||
* @return [[ir]], with documentation resolved
|
||||
*/
|
||||
def resolve(implicit inlineContext: InlineContext): IR.Expression = {
|
||||
DocumentationComments.runExpression(ir, inlineContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a defaulted module context.
|
||||
*
|
||||
* @return a defaulted module context
|
||||
*/
|
||||
def mkModuleContext: ModuleContext = {
|
||||
ModuleContext()
|
||||
}
|
||||
|
||||
/** Creates a defaulted inline context.
|
||||
*
|
||||
* @return a defaulted inline context
|
||||
*/
|
||||
def mkInlineContext: InlineContext = {
|
||||
InlineContext()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets documentation metadata from a node.
|
||||
* Throws an exception if missing.
|
||||
*
|
||||
* @param ir the ir to get the doc from.
|
||||
* @return the doc assigned to `ir`.
|
||||
*/
|
||||
def getDoc(ir: IR): String = {
|
||||
val meta = ir.getMetadata(DocumentationComments)
|
||||
meta shouldBe defined
|
||||
meta.get.documentation
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Documentation comments in the top scope" should {
|
||||
"be associated with atoms and methods" in {
|
||||
implicit val moduleContext: ModuleContext = mkModuleContext
|
||||
val ir =
|
||||
"""
|
||||
|## This is doc for My_Atom
|
||||
|type My_Atom a b c
|
||||
|
|
||||
|## This is doc for my_method
|
||||
|MyAtom.my_method x = x + this
|
||||
|
|
||||
|""".stripMargin.preprocessModule.resolve
|
||||
|
||||
ir.bindings.length shouldEqual 2
|
||||
ir.bindings(0) shouldBe an[IR.Module.Scope.Definition.Atom]
|
||||
ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Method]
|
||||
|
||||
getDoc(ir.bindings(0)) shouldEqual " This is doc for My_Atom"
|
||||
getDoc(ir.bindings(1)) shouldEqual " This is doc for my_method"
|
||||
}
|
||||
}
|
||||
|
||||
"Documentation comments in blocks" should {
|
||||
"be associated with the documented expression in expression flow" in {
|
||||
implicit val inlineContext: InlineContext = mkInlineContext
|
||||
val ir =
|
||||
"""
|
||||
|x -> y ->
|
||||
| ## Do thing
|
||||
| x + y
|
||||
| ## Do another thing
|
||||
| z = x * y
|
||||
|""".stripMargin.preprocessExpression.get.resolve
|
||||
val body = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Expression.Block]
|
||||
|
||||
body.expressions.length shouldEqual 1
|
||||
getDoc(body.expressions(0)) shouldEqual " Do thing"
|
||||
getDoc(body.returnValue) shouldEqual " Do another thing"
|
||||
}
|
||||
|
||||
"be associated with the documented expression in module flow" in {
|
||||
implicit val moduleContext: ModuleContext = mkModuleContext
|
||||
val ir =
|
||||
"""
|
||||
|method x =
|
||||
| ## Do thing
|
||||
| x + y
|
||||
| ## Do another thing
|
||||
| z = x * y
|
||||
|""".stripMargin.preprocessModule.resolve
|
||||
val body = ir
|
||||
.bindings(0)
|
||||
.asInstanceOf[IR.Module.Scope.Definition.Method.Binding]
|
||||
.body
|
||||
.asInstanceOf[IR.Expression.Block]
|
||||
|
||||
body.expressions.length shouldEqual 1
|
||||
getDoc(body.expressions(0)) shouldEqual " Do thing"
|
||||
getDoc(body.returnValue) shouldEqual " Do another thing"
|
||||
}
|
||||
}
|
||||
|
||||
"Documentation in complex type definitions" should {
|
||||
implicit val moduleContext: ModuleContext = mkModuleContext
|
||||
"assign docs to all entities" in {
|
||||
val ir =
|
||||
"""
|
||||
|## the type Foo
|
||||
|type Foo
|
||||
| ## the constructor Bar
|
||||
| type Bar
|
||||
|
|
||||
| ## the included Unit
|
||||
| Unit
|
||||
|
|
||||
| ## a method
|
||||
| foo x =
|
||||
| ## a statement
|
||||
| IO.println "foo"
|
||||
| ## the return
|
||||
| 0
|
||||
|""".stripMargin.preprocessModule.resolve
|
||||
val tp = ir.bindings(0).asInstanceOf[IR.Module.Scope.Definition.Type]
|
||||
getDoc(tp) shouldEqual " the type Foo"
|
||||
val t1 = tp.body(0)
|
||||
getDoc(t1) shouldEqual " the constructor Bar"
|
||||
val t2 = tp.body(1)
|
||||
getDoc(t2) shouldEqual " the included Unit"
|
||||
val method = tp.body(2).asInstanceOf[IR.Function.Binding]
|
||||
getDoc(method) shouldEqual " a method"
|
||||
val block = method.body.asInstanceOf[IR.Expression.Block]
|
||||
getDoc(block.expressions(0)) shouldEqual " a statement"
|
||||
getDoc(block.returnValue) shouldEqual " the return"
|
||||
}
|
||||
}
|
||||
}
|
@ -239,4 +239,22 @@ class CodeLocationsTest extends InterpreterTest {
|
||||
instrumenter.assertNodeExists(20, 5, classOf[ApplicationNode])
|
||||
eval(code)
|
||||
}
|
||||
|
||||
"Comments" should "not break code locations" in
|
||||
withLocationsInstrumenter { instrumenter =>
|
||||
val code =
|
||||
"""
|
||||
|# this is a comment
|
||||
|#this too
|
||||
|## But this is a doc.
|
||||
|main = # define main
|
||||
| y = 1 # assign one to `y`
|
||||
| x = 2 # assign two to #x
|
||||
| # perform the addition
|
||||
| x + y # the addition is performed here
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
instrumenter.assertNodeExists(82, 1 , classOf[IntegerLiteralNode])
|
||||
instrumenter.assertNodeExists(164, 5, classOf[ApplicationNode])
|
||||
eval(code) shouldEqual 3
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.InterpreterTest
|
||||
|
||||
class CommentsTest extends InterpreterTest {
|
||||
"All Comments" should "be ignored in execution" in {
|
||||
val code =
|
||||
"""
|
||||
|## Documented
|
||||
| Thoroughly.
|
||||
|main =
|
||||
| # commented out line
|
||||
| x = 5 + 7 # commented out piece
|
||||
| y = 564
|
||||
| x + y
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 576
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.enso.syntax.text
|
||||
|
||||
/**
|
||||
* A dirty hack that replaces all disable comments with whitespace.
|
||||
* Documentation comments are left untouched.
|
||||
*
|
||||
* @param input the original input file
|
||||
*/
|
||||
class CommentRemover(input: String) {
|
||||
|
||||
def run: String = {
|
||||
var currentState: CommentRemover.State = CommentRemover.Base
|
||||
val it = input.iterator.buffered
|
||||
val result = new StringBuilder(input.length)
|
||||
|
||||
while (it.hasNext) {
|
||||
val char: Char = it.next
|
||||
if (char != '\n' && currentState == CommentRemover.InComment) {
|
||||
result.addOne(' ')
|
||||
} else if (currentState == CommentRemover.Base && char == '#') {
|
||||
if (it.hasNext && it.head == '#') {
|
||||
result.addOne('#')
|
||||
currentState = CommentRemover.InDocComment
|
||||
} else {
|
||||
result.addOne(' ')
|
||||
currentState = CommentRemover.InComment
|
||||
}
|
||||
} else if (char == '\n') {
|
||||
result.addOne('\n')
|
||||
currentState = CommentRemover.Base
|
||||
} else {
|
||||
result.addOne(char)
|
||||
}
|
||||
}
|
||||
result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
object CommentRemover {
|
||||
sealed trait State
|
||||
case object Base extends State
|
||||
case object InDocComment extends State
|
||||
case object InComment extends State
|
||||
}
|
@ -223,13 +223,14 @@ class Parser {
|
||||
}
|
||||
|
||||
/** Parse simple string with empty IdMap into AST. */
|
||||
def run(input: String): AST.Module = run(new Reader(input), Nil)
|
||||
def run(input: String): AST.Module =
|
||||
run(new Reader(new CommentRemover(input).run), Nil)
|
||||
|
||||
/** Parse input with provided IdMap into AST */
|
||||
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]
|
||||
|
@ -424,10 +424,6 @@ class ParserTest extends AnyFlatSpec with Matchers {
|
||||
""".testIdentity()
|
||||
|
||||
"""
|
||||
# adults: old population
|
||||
# children: new individuals from crossover
|
||||
# mutation: new individuals from mutation
|
||||
Selects the 'fittest' individuals from population and kills the rest!
|
||||
armageddon adults children mutants =
|
||||
log '''
|
||||
keepBest
|
||||
@ -436,7 +432,7 @@ class ParserTest extends AnyFlatSpec with Matchers {
|
||||
`pop3`
|
||||
unique xs
|
||||
= xs.at(0.0) +: [1..length xs -1] . filter (isUnique xs) . map xs.at
|
||||
isUnique xs i ####
|
||||
isUnique xs i
|
||||
= xs.at(i).score != xs.at(i-1).score
|
||||
adults++children++mutants . sorted . unique . take (length pop1) . pure
|
||||
""".testIdentity()
|
||||
|
Loading…
Reference in New Issue
Block a user