Skip to content

Commit cf999e9

Browse files
authored
Merge pull request #249 from LaurenzV/format-rnode
Add reformatter capabilities
2 parents 998bc63 + 84f035b commit cf999e9

File tree

15 files changed

+244
-51
lines changed

15 files changed

+244
-51
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ For more details about using Viper, please visit: http://viper.ethz.ch/downloads
3737
### Running Tests ###
3838

3939
* Set the environment variable ```Z3_EXE``` to an executable of a recent version of [Z3](https://github.com/Z3Prover/z3).
40+
* Set the environment variable ```BOOGIE_EXE``` to an executable of a recent version of [Boogie](https://github.com/viperproject/boogie-builder)
4041

4142
* Run the following command: ```sbt test```.
4243

src/main/scala/viper/server/core/AstWorker.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package viper.server.core
88

99
import ch.qos.logback.classic.Logger
1010
import viper.server.ViperConfig
11-
import viper.server.utility.AstGenerator
11+
import viper.server.utility.ViperAstGenerator
1212
import viper.server.vsi.AstConstructionException
1313
import viper.silver.ast.Program
1414
import viper.silver.ast.utility.FileLoader
@@ -35,7 +35,7 @@ class AstWorker(val file: String,
3535
private def constructAst(args: Seq[String]): Option[Program] = {
3636

3737
val reporter = new ActorReporter("AstGenerationReporter")
38-
val astGen = new AstGenerator(logger, reporter, args, disablePlugins = config.disablePlugins())
38+
val astGen = new ViperAstGenerator(logger, reporter, args, disablePlugins = config.disablePlugins())
3939

4040
val ast_option: Option[Program] = try {
4141
astGen.generateViperAst(file, loader)
@@ -50,7 +50,7 @@ class AstWorker(val file: String,
5050
throw AstConstructionInterrupted
5151
case e: Throwable =>
5252
reporter report ExceptionReport(e)
53-
logger.error(s"Creation/Execution of an AstGenerator instance ($file) resulted in $e.")
53+
logger.error(s"Creation/Execution of a ViperAstGenerator instance ($file) resulted in $e.")
5454
registerTaskEnd(false)
5555
throw ServerCrashException(e)
5656
}

src/main/scala/viper/server/frontends/lsp/ClientCoordinator.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class ClientCoordinator(val server: ViperServerService)(implicit executor: Verif
150150
Future.sequence(tasks).map(_ => {
151151
logger.debug("all running verifications have been stopped")
152152
})
153-
}
153+
}
154154

155155
/** returns true if verification was started */
156156
def startVerification(backendClassName: String, customArgs: String, uri: String, manuallyTriggered: Boolean): Future[Boolean] = {

src/main/scala/viper/server/frontends/lsp/DataProtocol.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ case class VerifyParams (
8787
backend: String,
8888
customArgs: String) // contains the path of the file that should be verified
8989

90+
case class ReformatParams (uri: String)
91+
9092
case class SettingsError (errorType: SettingsErrorType, msg: String)
9193

9294
case class Stage(

src/main/scala/viper/server/frontends/lsp/Receiver.scala

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import scala.language.postfixOps
2323

2424
trait StandardReceiver {
2525
val coordinator: ClientCoordinator
26+
2627
implicit def executor: VerificationExecutionContext
2728
}
2829

@@ -41,11 +42,11 @@ trait LanguageReceiver extends StandardReceiver with LanguageServer {
4142
// Find References:
4243
capabilities.setReferencesProvider(true)
4344
// Prepare Call Hierarchy: [N/A]
44-
// Incoming Calls: [N/A]
45-
// Outgoing Calls: [N/A]
45+
// Incoming Calls: [N/A]
46+
// Outgoing Calls: [N/A]
4647
// Prepare Type Hierarchy: [N/A]
47-
// Super Types: [N/A]
48-
// Sub Types: [N/A]
48+
// Super Types: [N/A]
49+
// Sub Types: [N/A]
4950
// Document Highlight:
5051
capabilities.setDocumentHighlightProvider(true)
5152
// Document Link (disabled Resolve):
@@ -57,21 +58,21 @@ trait LanguageReceiver extends StandardReceiver with LanguageServer {
5758
// Folding Range:
5859
capabilities.setFoldingRangeProvider(true)
5960
// Selection Range: [N/A]
60-
// could try extracting from document symbols,
61-
// but this req doesn't seem to ever be sent
61+
// could try extracting from document symbols,
62+
// but this req doesn't seem to ever be sent
6263
// Document Symbols:
6364
capabilities.setDocumentSymbolProvider(true)
6465
// Semantic Tokens:
6566
val legend = new SemanticTokensLegend(
6667
Seq(SemanticTokenTypes.Namespace, SemanticTokenTypes.Type, SemanticTokenTypes.Class, SemanticTokenTypes.Enum,
67-
SemanticTokenTypes.Interface, SemanticTokenTypes.Struct, SemanticTokenTypes.TypeParameter, SemanticTokenTypes.Parameter,
68-
SemanticTokenTypes.Variable, SemanticTokenTypes.Property, SemanticTokenTypes.EnumMember, SemanticTokenTypes.Event,
69-
SemanticTokenTypes.Function, SemanticTokenTypes.Method, SemanticTokenTypes.Macro, SemanticTokenTypes.Keyword,
70-
SemanticTokenTypes.Modifier, SemanticTokenTypes.Comment, SemanticTokenTypes.String, SemanticTokenTypes.Number,
71-
SemanticTokenTypes.Regexp, SemanticTokenTypes.Operator, SemanticTokenTypes.Decorator, "constant").asJava,
68+
SemanticTokenTypes.Interface, SemanticTokenTypes.Struct, SemanticTokenTypes.TypeParameter, SemanticTokenTypes.Parameter,
69+
SemanticTokenTypes.Variable, SemanticTokenTypes.Property, SemanticTokenTypes.EnumMember, SemanticTokenTypes.Event,
70+
SemanticTokenTypes.Function, SemanticTokenTypes.Method, SemanticTokenTypes.Macro, SemanticTokenTypes.Keyword,
71+
SemanticTokenTypes.Modifier, SemanticTokenTypes.Comment, SemanticTokenTypes.String, SemanticTokenTypes.Number,
72+
SemanticTokenTypes.Regexp, SemanticTokenTypes.Operator, SemanticTokenTypes.Decorator, "constant").asJava,
7273
Seq(SemanticTokenModifiers.Declaration, SemanticTokenModifiers.Definition, SemanticTokenModifiers.Readonly, SemanticTokenModifiers.Static,
73-
SemanticTokenModifiers.Deprecated, SemanticTokenModifiers.Abstract, SemanticTokenModifiers.Async, SemanticTokenModifiers.Modification,
74-
SemanticTokenModifiers.Documentation, SemanticTokenModifiers.DefaultLibrary, "controlFlow").asJava
74+
SemanticTokenModifiers.Deprecated, SemanticTokenModifiers.Abstract, SemanticTokenModifiers.Async, SemanticTokenModifiers.Modification,
75+
SemanticTokenModifiers.Documentation, SemanticTokenModifiers.DefaultLibrary, "controlFlow").asJava
7576
)
7677
capabilities.setSemanticTokensProvider(new SemanticTokensWithRegistrationOptions(legend, true))
7778
// Inline Value: [N/A]
@@ -83,7 +84,7 @@ trait LanguageReceiver extends StandardReceiver with LanguageServer {
8384
// Pull Diagnostics: DISABLED (we use `publishDiagnostics` instead)
8485
// capabilities.setDiagnosticProvider(new DiagnosticRegistrationOptions(true, false))
8586
// Signature Help:
86-
// Allow a `,` to try and restart the signature help even after it has ended
87+
// Allow a `,` to try and restart the signature help even after it has ended
8788
capabilities.setSignatureHelpProvider(new SignatureHelpOptions(Seq("(", ",").asJava, Seq().asJava))
8889
// Code Action:
8990
capabilities.setCodeActionProvider(true)
@@ -102,6 +103,8 @@ trait LanguageReceiver extends StandardReceiver with LanguageServer {
102103
// automatically cause all references to be renamed. This would probably be too annoying.
103104
capabilities.setLinkedEditingRangeProvider(false)
104105

106+
capabilities.setDocumentFormattingProvider(true)
107+
105108
CompletableFuture.completedFuture(new InitializeResult(capabilities))
106109
}
107110

@@ -191,7 +194,7 @@ trait TextDocumentReceiver extends StandardReceiver with TextDocumentService {
191194
val uri = params.getTextDocument.getUri
192195
val pos = params.getPosition
193196
coordinator.getRoot(uri).getGotoDefinitions(uri, pos).map(defns => {
194-
Either.forRight[java.util.List[_ <: Location], java.util.List[_ <: LocationLink]](defns.asJava)
197+
Either.forRight[java.util.List[_ <: Location], java.util.List[_ <: LocationLink]](defns.asJava)
195198
}).asJava.toCompletableFuture
196199
}
197200

@@ -315,6 +318,18 @@ trait TextDocumentReceiver extends StandardReceiver with TextDocumentService {
315318
CompletableFuture.completedFuture(Nil.asJava)
316319
}
317320

321+
override def formatting(params: DocumentFormattingParams) = {
322+
coordinator.logger.trace(s"[Req: textDocument/formatting] ${params.toString}")
323+
val uri = params.getTextDocument.getUri
324+
val result = coordinator.getRoot(uri).reformatFile() match {
325+
case None => Seq()
326+
case Some(e) =>
327+
val range = new Range(new Position(0, 0), new Position(Int.MaxValue, Int.MaxValue))
328+
Seq(new TextEdit(range, e))
329+
}
330+
CompletableFuture.completedFuture(result.asJava)
331+
}
332+
318333
// --- DISABLED, see comment in `initialize` ---
319334
override def diagnostic(params: DocumentDiagnosticParams) = {
320335
coordinator.logger.trace(s"[Req: textDocument/diagnostic] ${params.toString()}")
@@ -351,9 +366,10 @@ trait WorkspaceReceiver extends StandardReceiver with WorkspaceService {
351366
}
352367

353368
class CustomReceiver(config: ViperConfig, server: ViperServerService, serverUrl: String)(implicit _executor: VerificationExecutionContext)
354-
extends LanguageClientAware with LanguageReceiver with WorkspaceReceiver with TextDocumentReceiver {
369+
extends LanguageClientAware with LanguageReceiver with WorkspaceReceiver with TextDocumentReceiver {
355370

356371
override val coordinator = new ClientCoordinator(server)
372+
357373
override def executor: VerificationExecutionContext = _executor
358374

359375
override def getTextDocumentService(): TextDocumentService = this
@@ -405,16 +421,22 @@ class CustomReceiver(config: ViperConfig, server: ViperServerService, serverUrl:
405421
if (verificationStarted) {
406422
coordinator.logger.info("Verification Started")
407423
} else {
408-
coordinator.client.map{_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))}
424+
coordinator.client.map {
425+
_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))
426+
}
409427
}
410428
})
411429
}).recover(e => {
412430
coordinator.logger.debug(s"Error handling verify request: $e")
413-
coordinator.client.map{_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))}
431+
coordinator.client.map {
432+
_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))
433+
}
414434
})
415435
} else {
416436
coordinator.logger.info("The verification cannot be started.")
417-
coordinator.client.map{_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))}
437+
coordinator.client.map {
438+
_.notifyVerificationNotStarted(VerificationNotStartedParams(data.uri))
439+
}
418440
}
419441
}
420442

src/main/scala/viper/server/frontends/lsp/ViperServerService.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import akka.util.Timeout
1313
import ch.qos.logback.classic.Logger
1414
import viper.server.ViperConfig
1515
import viper.server.core.{VerificationExecutionContext, ViperBackendConfig, ViperCoreServer}
16+
import viper.server.utility.ReformatterAstGenerator
17+
import viper.server.utility.Helpers.{getArgListFromArgString, validateViperFile}
1618
import viper.server.utility.Helpers.validateViperFile
1719
import viper.server.vsi.VerificationProtocol.{StopAstConstruction, StopVerification}
1820
import viper.server.vsi.{AstJobId, DefaultVerificationServerStart, VerHandle, VerJobId}
21+
import viper.silver.parser.ReformatPrettyPrinter
1922
import viper.silver.ast.utility.FileLoader
2023

2124
import scala.concurrent.Future
@@ -52,6 +55,21 @@ class ViperServerService(config: ViperConfig)(override implicit val executor: Ve
5255
ver_id
5356
}
5457

58+
def reformatFile(file: String, localLogger: Option[Logger] = None): Option[String] = {
59+
val logger = combineLoggers(localLogger)
60+
logger.debug("Requesting ViperServer to create a reformatted file.");
61+
62+
val ast_generator = new ReformatterAstGenerator(logger);
63+
val parse_ast = ast_generator.generateViperParseAst(file);
64+
parse_ast match {
65+
case Some(p) => Some(ReformatPrettyPrinter.showProgram(p))
66+
case _ => {
67+
logger.error("Failed to generate parse AST for reformatting the program.")
68+
None
69+
}
70+
}
71+
}
72+
5573
def startStreaming(jid: VerJobId, relayActor_props: Props, localLogger: Option[Logger] = None): Option[Future[Unit]] = {
5674
val logger = combineLoggers(localLogger)
5775
logger.debug("Sending verification request to ViperServer...")

src/main/scala/viper/server/frontends/lsp/file/Verification.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ trait VerificationManager extends ManagesLeaf {
204204
}
205205
}
206206

207+
def reformatFile(): Option[String] = {
208+
coordinator.logger.info(s"reformatting the file $filename")
209+
coordinator.server.reformatFile(path.toString, Some(coordinator.localLogger))
210+
}
211+
207212
/** Do full parsing, type checking and verification */
208213
def startVerification(backendClassName: String, customArgs: String, loader: FileContent, mt: Boolean): Future[Boolean] = {
209214
lastBackendClassName = Some(backendClassName)

src/main/scala/viper/server/utility/AstGenerator.scala renamed to src/main/scala/viper/server/utility/AstGeneratorBase.scala

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,44 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44
//
5-
// Copyright (c) 2011-2020 ETH Zurich.
5+
// Copyright (c) 2011-2025 ETH Zurich.
66

77
package viper.server.utility
88

99
import ch.qos.logback.classic.Logger
1010

1111
import java.nio.file.NoSuchFileException
1212
import viper.server.utility.Helpers.validateViperFile
13-
import viper.silver.ast.Program
1413
import viper.silver.ast.utility.FileLoader
15-
import viper.silver.frontend.{SilFrontend, ViperAstProvider}
14+
import viper.silver.frontend.SilFrontend
1615
import viper.silver.reporter.{NoopReporter, Reporter, PProgramReport}
1716

18-
class AstGenerator(private val _logger: Logger,
19-
private val _reporter: Reporter = NoopReporter,
20-
private val argList: Seq[String] = Seq(),
21-
private val disablePlugins: Boolean = false) extends ProgramDefinitionsProvider {
17+
/** Base class for AST generators. The parameter T determines the type of AST returned, currently either PProgram or Program
18+
*/
19+
abstract class AstGeneratorBase[T](private val _logger: Logger,
20+
private val _reporter: Reporter = NoopReporter,
21+
private val argList: Seq[String] = Seq(),
22+
private val disablePlugins: Boolean = false) extends ProgramDefinitionsProvider {
2223

24+
/** Creates a backend that reads and parses the file
25+
*/
26+
protected val _frontend: SilFrontend
2327

28+
/** Extracts the result of type T from the frontend
29+
*/
30+
protected def getResult: T
2431

25-
/** Creates a backend that reads and parses the file
26-
*/
27-
protected override val _frontend: SilFrontend = {
28-
_logger.info(s"Creating new AstGenerator instance.")
29-
new ViperAstProvider(_reporter, disablePlugins = disablePlugins)
30-
}
31-
/** Parses and translates a Viper file into a Viper AST.
32-
*
33-
* Throws an exception when passed an non-existent file!
34-
*/
35-
def generateViperAst(vpr_file_path: String, loader: Option[FileLoader] = None): Option[Program] = {
32+
/** Parses and translates a Viper file into the appropriate AST type.
33+
*
34+
* Throws an exception when passed a non-existent file!
35+
*/
36+
def generateViperAstImpl(vpr_file_path: String, loader: Option[FileLoader] = None): Option[T] = {
3637

3738
if (!validateViperFile(vpr_file_path)) {
3839
_logger.error(s"No such file: `$vpr_file_path`")
3940
throw new NoSuchFileException(vpr_file_path)
4041
}
41-
42+
4243
_logger.info(s"Parsing `$vpr_file_path` ...")
4344

4445
// We need to pass all arguments relevant to AST creation to the frontend (e.g. everything plugin-related), but we
@@ -52,7 +53,7 @@ class AstGenerator(private val _logger: Logger,
5253
// argument is the next item in the argList
5354
filteredArgs = argList.slice(optionArgIndex, optionArgIndex + 2) ++ filteredArgs
5455
} else {
55-
argList.find(_.startsWith(s"${option}=")) match {
56+
argList.find(_.startsWith(s"$option=")) match {
5657
case Some(arg) =>
5758
filteredArgs = Seq(arg) ++ filteredArgs
5859
case _ =>
@@ -74,7 +75,7 @@ class AstGenerator(private val _logger: Logger,
7475
reportProgramStats()
7576
}
7677
if (_frontend.errors.isEmpty) {
77-
Some(_frontend.translationResult)
78+
Some(getResult)
7879
} else {
7980
_logger.info(s"Errors occurred while translating `$vpr_file_path`: ${_frontend.errors}")
8081
None
@@ -85,4 +86,4 @@ class AstGenerator(private val _logger: Logger,
8586
val flagWhiteList: Seq[String] = Seq("--disableDefaultPlugins", "--disableAdtPlugin", "--disableTerminationPlugin")
8687
// Parameters that are relevant for AST creation and are passed additional values
8788
val optionWhiteList: Seq[String] = Seq("--plugin")
88-
}
89+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) 2011-2025 ETH Zurich.
6+
7+
package viper.server.utility
8+
9+
import ch.qos.logback.classic.Logger
10+
import viper.silver.ast.utility.FileLoader
11+
import viper.silver.frontend.{SilFrontend, ReformatterAstProvider}
12+
import viper.silver.parser.PProgram
13+
import viper.silver.reporter.{NoopReporter, Reporter}
14+
15+
class ReformatterAstGenerator(private val _logger: Logger,
16+
private val _reporter: Reporter = NoopReporter,
17+
private val argList: Seq[String] = Seq(),
18+
private val disablePlugins: Boolean = false) extends AstGeneratorBase[PProgram](_logger, _reporter, argList, disablePlugins) {
19+
20+
protected override val _frontend: SilFrontend = {
21+
_logger.info(s"Creating new ReformatterAstGenerator instance.")
22+
new ReformatterAstProvider(_reporter)
23+
}
24+
25+
/** Extracts the parse AST (PProgram) from the frontend
26+
*/
27+
protected override def getResult: PProgram = _frontend.parsingResult
28+
29+
/** Parses a Viper file into a parse AST (PProgram) without macro expansion.
30+
*
31+
* Throws an exception when passed a non-existent file!
32+
*/
33+
def generateViperParseAst(vpr_file_path: String, loader: Option[FileLoader] = None): Option[PProgram] = {
34+
generateViperAstImpl(vpr_file_path, loader)
35+
}
36+
}

0 commit comments

Comments
 (0)