Handles disable and doc comments (#780)

This commit is contained in:
Marcin Kostrzewa 2020-05-27 14:59:23 +02:00 committed by GitHub
parent 2fdd053fd3
commit e2bac23e26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 480 additions and 108 deletions

View File

@ -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

View File

@ -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.
*

View File

@ -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,

View File

@ -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 _ =>

View File

@ -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

View File

@ -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()
}
}

View File

@ -110,6 +110,8 @@ class MetadataStorage(
res.metadata = this.metadata
res
}
override def toString: String = metadata.toString()
}
object MetadataStorage extends MetadataStorageSyntax {

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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"
}
}

View File

@ -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,

View File

@ -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"
}
}
}

View File

@ -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 =

View File

@ -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"
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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]

View File

@ -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()