-
Notifications
You must be signed in to change notification settings - Fork 38
Replace !<...!> predicate-constructor delimiters with {...}
#1024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
f7ca4ed
dc8722f
c2011b4
eda78b4
c3c890e
67615b3
36abfba
11036d8
af082e0
bff0e89
cecf6c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -239,6 +239,7 @@ object Parser extends LazyLogging { | |
| postprocessors = Seq( | ||
| new ImportPostprocessor(parseAst.positions.positions), | ||
| new TerminationMeasurePostprocessor(parseAst.positions.positions, specOnly = specOnly), | ||
| new PredicateConstructorPostprocessor(parseAst.positions.positions), | ||
| ) | ||
| postprocessedAst <- postprocessors.foldLeft[Either[Vector[ParserError], PPackage]](Right(parseAst)) { | ||
| case (Right(ast), postprocessor) => postprocessor.postprocess(ast)(config) | ||
|
|
@@ -515,6 +516,108 @@ object Parser extends LazyLogging { | |
| } | ||
| } | ||
|
|
||
| private class PredicateConstructorPostprocessor(override val positions: Positions) extends Postprocessor { | ||
| /** | ||
| * Predicate constructors share their `name { args }` surface syntax with composite literals, | ||
| * so the parser conservatively builds a `PCompositeLit` for `IDENT { ... }` and | ||
| * `IDENT.IDENT { ... }` shapes. This postprocessor rewrites such literals into | ||
| * `PPredConstructor` when they are unambiguously predicate constructors: | ||
| * 1. They contain at least one positional `_` element (illegal in composite literals), or | ||
| * 2. The named/dotted base resolves syntactically to a top-level predicate declared in | ||
| * the current package or to a built-in predicate. | ||
| * | ||
| * Cross-package cases (e.g. `pkg.P{1}()`) are deferred to a second pass driven by the | ||
| * type checker -- see [[PredicateConstructorRewriter.rewriteCrossPackage]]. | ||
| */ | ||
| def postprocess(pkg: PPackage)(config: Config): Either[Vector[ParserError], PPackage] = { | ||
| // collect names of all top-level (function and method) predicate declarations | ||
| val localPredicateNames: Set[String] = pkg.programs.flatMap(_.declarations.collect { | ||
| case d: PFPredicateDecl => d.id.name | ||
| case d: PMPredicateDecl => d.id.name | ||
| }).toSet | ||
| Right(PredicateConstructorRewriter.rewrite(pkg, localPredicateNames, _ => Set.empty)(positions)) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Shared rewrite logic that turns `PCompositeLit` nodes whose name resolves (syntactically) to | ||
| * a predicate into the equivalent `PPredConstructor`. Used both by the parse-time | ||
| * [[PredicateConstructorPostprocessor]] (which only knows the current package's predicates) | ||
| * and by [[viper.gobra.frontend.info.Info]] (which additionally has access to imported | ||
| * packages' predicate names via `dependentTypeInfo`). | ||
| */ | ||
| object PredicateConstructorRewriter { | ||
| private lazy val builtInPredicateNames: Set[String] = | ||
| viper.gobra.frontend.info.base.BuiltInMemberTag.builtInMembers().collect { | ||
| case t: viper.gobra.frontend.info.base.BuiltInMemberTag.BuiltInPredicateTag => t.identifier | ||
| }.toSet | ||
|
|
||
| private class Impl(override val positions: Positions) extends PositionedRewriter { | ||
| def at[N <: AnyRef](node: N, source: PNode): N = { positions.dupPos(source, node); node } | ||
|
|
||
| def run( | ||
| pkg: PPackage, | ||
| localPredicateNames: Set[String], | ||
| importedPredicateNames: String => Set[String], | ||
| ): PPackage = { | ||
| def hasKey(lit: PLiteralValue): Boolean = lit.elems.exists(_.key.isDefined) | ||
| def hasBlank(lit: PLiteralValue): Boolean = lit.elems.exists { | ||
| case PKeyedElement(None, PExpCompositeVal(_: PBlankIdentifier)) => true | ||
| case _ => false | ||
| } | ||
| def isLocalOrBuiltInPredName(name: String): Boolean = | ||
| localPredicateNames.contains(name) || builtInPredicateNames.contains(name) | ||
|
|
||
| def shouldRewrite(typ: PLiteralType, lit: PLiteralValue): Boolean = { | ||
| if (hasKey(lit)) false | ||
| else typ match { | ||
| case PNamedOperand(id) => hasBlank(lit) || isLocalOrBuiltInPredName(id.name) | ||
| case PDot(qual: PNamedOperand, id) => | ||
| hasBlank(lit) || isLocalOrBuiltInPredName(id.name) || importedPredicateNames(qual.id.name).contains(id.name) | ||
|
ArquintL marked this conversation as resolved.
Outdated
|
||
| case PDot(_, id) => hasBlank(lit) || isLocalOrBuiltInPredName(id.name) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same answer — covered by the comment added in 36abfba on the Generated by Claude Code
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be more defensive to throw an exception? |
||
| case _ => false | ||
| } | ||
| } | ||
|
|
||
| def convertArgs(lit: PLiteralValue): Vector[Option[PExpression]] = lit.elems.map { | ||
| case PKeyedElement(None, PExpCompositeVal(_: PBlankIdentifier)) => None | ||
| case PKeyedElement(None, PExpCompositeVal(e)) => Some(e) | ||
| case e => Violation.violation(s"unexpected element form in predicate constructor candidate: $e") | ||
| } | ||
|
|
||
| def buildBase(typ: PLiteralType): PPredConstructorBase = typ match { | ||
| case op@PNamedOperand(id) => at(PFPredBase(id), op) | ||
| case d: PDot => at(PDottedBase(d), d) | ||
| case t => Violation.violation(s"unexpected base for predicate constructor: $t") | ||
| } | ||
|
|
||
| val rewritePredConstructors: Strategy = | ||
| strategyWithName[Any]("rewritePredConstructors", { | ||
| case n@PCompositeLit(typ, lit) if shouldRewrite(typ, lit) => | ||
| Some(at(PPredConstructor(buildBase(typ), convertArgs(lit)), n)) | ||
| case n => Some(n) | ||
| }) | ||
|
|
||
| val updatedProgs = pkg.programs.map { prog => | ||
| val updatedDecls = rewrite(topdown(attempt(rewritePredConstructors)))(prog.declarations) | ||
| at(PProgram(prog.packageClause, prog.pkgInvariants, prog.imports, prog.friends, updatedDecls), prog) | ||
| } | ||
| at(PPackage(pkg.packageClause, updatedProgs, pkg.positions, pkg.info), pkg) | ||
| } | ||
| } | ||
|
|
||
| /** Runs the rewrite over `pkg`. `localPredicateNames` is the set of top-level predicate | ||
| * names (function and method) declared in `pkg`. `importedPredicateNames(qual)` returns the | ||
| * set of predicate names exposed by the package imported under qualifier `qual`, or an | ||
| * empty set if `qual` is not an import qualifier or its imports aren't yet known. */ | ||
| def rewrite( | ||
| pkg: PPackage, | ||
| localPredicateNames: Set[String], | ||
| importedPredicateNames: String => Set[String], | ||
| )(positions: Positions): PPackage = | ||
| new Impl(positions).run(pkg, localPredicateNames, importedPredicateNames) | ||
| } | ||
|
|
||
| private class SyntaxAnalyzer[Rule <: ParserRuleContext, Node <: AnyRef](tokens: CommonTokenStream, source: Source, errors: ListBuffer[ParserError], pom: PositionManager, specOnly: Boolean = false) extends GobraParser(tokens){ | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.