From 04d821e7bd511dc247343c47bd4c908b9015eca2 Mon Sep 17 00:00:00 2001 From: Yasmine Sharoda Date: Wed, 13 Feb 2019 16:16:02 -0500 Subject: [PATCH 01/63] fixing bugs in the guard of combine. --- .DS_Store | Bin 8196 -> 0 bytes .../mmt/api/objects/AnonymousModules.scala | 6 ++-- .../mmt/moduleexpressions/Combinators.scala | 27 ++++-------------- 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4b8ebe687bae1e7753ed4609d98f1093c39e779a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMO>YxN7=Fh#iJg>s6A}`rDp|FVkcd#sheU-U%qD?a6^$FaY0Q^d?@r8?^^Uc> zPH;kyFNkw*ZKeKzs)zo7N%|z~lUCA!h{)(Cj!fbVg~W*m$q6`+qO`iz3TOpJ z72vsh3eH0lT!@tX{VJ}V{a+M8oao~a+XNpN1V9zePYUN4a$InLf;EgJa|$wpNbd6+ z#6E&9c;G3FX9{By84>Qmf^Ccu;;0L)5!OqEm4#(kgD;>8>res{zQSG!$2XDR0%tg8 z!cy3if?bB%fD^%ERk_5U3TsR#(8e<{=nh9nE-BavSV5GH8c;mj7M{#gRZjk_aFWRA zsJlp=Qc%;-Mvqcq876a(ji#mp)y{U0E#b@2AsqX9`QD2d(bz-jn^O-{VoR zkoXA(SvelGKVUBw!L_&J a.morphism.decls) + val distTo : List[DiagramArrow] = getDistArrowsTo(to.label) + val rens: List[OML] = distTo.flatMap(a => a.morphism.decls) val new_decls = from.theory.decls.map { curr => - var curr_ren = rens.find(oml => (oml.name == curr.name)) + var curr_ren = rens.find(_.name == curr.name) var new_decl = curr while (curr_ren != None) { var df = curr_ren.get.df // TODO: Do we want to have getOrElse here? diff --git a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala index 61985ac441..46b0a2c69e 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala @@ -354,7 +354,6 @@ object PushoutUtils { object Combine extends Pushout { val name = "combine" - val nodeLabel = LocalName("pres") val arrowLabel1 = LocalName("extend1") val arrowLabel2 = LocalName("extend2") @@ -374,7 +373,7 @@ object ComputeCombine extends ComputationRule(Combine.path) { val extArrows1 = b1.distTo.filter(_.label.toString.contains("extend")) val extArrows2 = b2.distTo.filter(_.label.toString.contains("extend")) val List(sN1,sN2) = List(b1,b2).map(b => b.distTo.filter(_.label.toString.contains("extend")).map(a => a.from)) - val sourceName : LocalName = (sN1 intersect sN2).last + val sourceName : LocalName = (sN1 intersect sN2).head val source : DiagramNode = sourceName match { case Common.ExistingName(s) => b1.anondiag.getNode(sourceName).get case _ => return Recurse @@ -386,8 +385,10 @@ object ComputeCombine extends ComputationRule(Combine.path) { /* Checking for the guard: * If two decls are renamed to the same name, they should be the same in the source node */ // Going through the rename arrows: - val List(view_ad1,view_ad2) = List(b1,b2).map(b => b.anondiag.viewOf(source,b.distNode)) - if(view_ad1 != view_ad2) new GeneralError("Wrong renames") + // val List(view_ad1,view_ad2) = List(b1,b2).map(b => b.anondiag.viewOf(source,b.distNode)) + val view_ad1 = b1.anondiag.viewOf(source,new DiagramNode(LocalName("n1"),renamedThry1)) + val view_ad2 = b2.anondiag.viewOf(source,new DiagramNode(LocalName("n2"),renamedThry2)) + if(view_ad1 != view_ad2) throw (new GeneralError("Wrong renames")) /* Theory with the new declarations */ val new_thy = renamedThry1 union renamedThry2 @@ -459,27 +460,11 @@ object ComputeMixin extends ComputationRule(Mixin.path) { val new_decls : List[OML] = Common.applySubstitution(d1_dN_renamed.decls,mor) val pushout = new AnonymousTheory(d1_dN.theory.mt,new_decls) - /* - var pushout : AnonymousTheory = new AnonymousTheory(d1_dN_renamed.mt,Nil) - d1_dN_renamed.decls.foreach {oml => - // TODO: I Am here, check is this is correct - if (view_renamed.decls.contains()) { - val omlT = (view_renamed.decls.dropWhile(_.name == oml.name).head.df).getOrElse(return Recurse).asInstanceOf[OML] - pushout = pushout.add(omlT) - // if (d1_dN_renamed.isDeclared(oml.name)) { - // solver.error("pushout not defined because of name clash: " + oml.name) - // return Recurse - }else { - val omlT = translator(oml, stack.context).asInstanceOf[OML] - pushout = pushout.add(omlT) - } - }*/ - val node = DiagramNode(Mixin.nodeLabel,pushout) // TODO: The morphisms needs more thinking val arrow1 = DiagramArrow(Mixin.arrowLabel1,d1_dN.label,node.label,view.morphism,false) val arrow2 = DiagramArrow(Mixin.arrowLabel2,d2_dN.label,node.label,new AnonymousMorphism(Nil),false) - val dA = DiagramArrow(Mixin.arrowLabel,ad2.getDistArrow.getOrElse(return Recurse).from,node.label,view.morphism,true) + val dA = DiagramArrow(Mixin.arrowLabel,view.from,node.label,view.morphism,true) val result = new AnonymousDiagram(ad1.nodes:::ad2.nodes:::List(node),ad1.arrows:::ad2.arrows:::List(arrow1,arrow2,dA),Some(node.label)) Simplify(result.toTerm) } From c305bf5f50756c6f6d9093111f82e3ab5088f492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Thu, 21 Mar 2019 19:25:36 +0100 Subject: [PATCH 02/63] Update CodeGen in sql --- .../kwarc/mmt/sql/codegen/CodeGenerator.scala | 67 +++++++++---------- .../kwarc/mmt/sql/codegen/DatabaseCode.scala | 19 ++++++ .../kwarc/mmt/sql/codegen/TableCode.scala | 12 ++-- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala index bc0763bdd9..bf83426088 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala @@ -31,7 +31,7 @@ object CodeGenerator { val exJoe = SchemaLang._base ? "MatrixS" val exJane = SchemaLang._base ? "MatrixWithCharacteristicS" val mPath = SchemaLang._base ? "Matrices" - val sg = "Mathilde" + val sg = "Joe" // remove later controller.handleLine("build ODK/DiscreteZOO mmt-omdoc") @@ -45,44 +45,43 @@ object CodeGenerator { val theories = controller.backend.getArchive("ODK/DiscreteZOO").get.allContent.map(controller.getO).collect({ case Some(t : Theory) if isInputTheory(t) => t }) -// collect { -// case Some(t : Theory) if t.meta.contains(SchemaLang._base.toMPath) => t.path -// } - theories.map(t => t).foreach(println) - println(" - - - - - ") + theories.headOption.foreach(theory => { - val maybeTable: Option[Table] = SQLBridge.test(mPath) match { - case t: Table => Some(t) - case _ => None - } - - maybeTable.foreach(t => { - - val generate = true - val prefix = "TEST" - val tableCode = TableCode(prefix, t) - val dbCode = DatabaseCode(dirPaths, prefix, Seq(tableCode), jdbcInfo) // TODO: one table only - val name = {t.name} - -// t.columns.map(_.collection).collect({ -// case Some(info) => info -// }).map(_.metadata).foreach(println) - - if (generate) { - // backend - dbCode.createTablePackageDirectories() - writeToFile(s"${dirPaths.backendPackagePath}/JsonSupport.scala", dbCode.jsonSupportCode) - writeToFile(s"${dirPaths.backendPackagePath}/Create.scala", dbCode.mainCreateCode) - writeToFile(s"${dirPaths.dbPackagePath}/ZooDb.scala", dbCode.fileDbCode) - writeToFile(s"${dbCode.tablePackagePath(name)}/$name.scala", tableCode.codeCaseClass) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}PlainQuery.scala", tableCode.codePlainQueryObject) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}Table.scala", tableCode.codeTableClass) - // frontend - writeToFile(s"${dirPaths.frontendPath}/objectProperties.json", dbCode.objectPropertiesJSON) + val maybeTable: Option[Table] = SQLBridge.test(theory.path) match { + case t: Table => Some(t) + case _ => None } + maybeTable.foreach(t => { + + val description: String = theory.metadata.get(SchemaLang.datasetName).headOption.map(_.value.toString).getOrElse("") + + val generate = true + val prefix = "TEST" + val tableCode = TableCode(prefix, description, t) + val dbCode = DatabaseCode(dirPaths, prefix, Seq(tableCode), jdbcInfo) // TODO: one table only + val name = {t.name} + + if (generate) { + // backend + dbCode.createTablePackageDirectories() + writeToFile(s"${dirPaths.backendPackagePath}/JsonSupport.scala", dbCode.jsonSupportCode) + writeToFile(s"${dirPaths.backendPackagePath}/Create.scala", dbCode.mainCreateCode) + writeToFile(s"${dirPaths.dbPackagePath}/ZooDb.scala", dbCode.fileDbCode) + writeToFile(s"${dbCode.tablePackagePath(name)}/$name.scala", tableCode.codeCaseClass) + writeToFile(s"${dbCode.tablePackagePath(name)}/${name}PlainQuery.scala", tableCode.codePlainQueryObject) + writeToFile(s"${dbCode.tablePackagePath(name)}/${name}Table.scala", tableCode.codeTableClass) + // frontend + writeToFile(s"${dirPaths.frontendPath}/objectProperties.json", dbCode.objectPropertiesJSON) + writeToFile(s"${dirPaths.frontendPath}/collectionsData.json", dbCode.collectionDataJSON) + writeToFile(s"${dirPaths.frontendPath}/settings.json", dbCode.settingsJSON) + } + + }) + }) + } } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala index 30a017e180..5ed26c1d93 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala @@ -68,4 +68,23 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode """.stripMargin } + def collectionDataJSON: String = { + val code = tables.map(_.collectionsData).mkString(",\n") + s"""{ + |$code + |} + """.stripMargin + } + + def settingsJSON: String = { + val code = tables.map(_.defaultColumns).mkString(",\n") + s"""{ + | "title": "Search", + | "defaultColumns": { + |$code + | } + |} + """.stripMargin + } + } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala index e92c287799..717d889078 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala @@ -2,7 +2,7 @@ package info.kwarc.mmt.sql.codegen import info.kwarc.mmt.sql.{Column, Table} -case class TableCode(prefix: String, table: Table) { +case class TableCode(prefix: String, description: String, table: Table) { def tableName: String = table.name def tableObject: String = s"tb$tableName" @@ -120,14 +120,18 @@ case class TableCode(prefix: String, table: Table) { } def collectionsData: String = { - val columns = columnCodeList.map(_.jsonObjectProperties).mkString(",\n") s""""$tableObject": { - | $tableName: { + | "$tableName": { | "id": "$tableName", - | "name": "Table description" + | "name": "$description" | } |} """.stripMargin } + def defaultColumns: String = { + val columns = table.columns.filter(_.isDisplayedByDefault).map(c => s""""${c.name}"""").mkString(", ") + s""" "$tableObject": [$columns]""".stripMargin + } + } From 88ce3af47cdd2540ef821316228d6df3e582ca2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Fri, 22 Mar 2019 12:40:10 +0100 Subject: [PATCH 03/63] Update MBGen code generation --- .../kwarc/mmt/sql/codegen/CodeGenerator.scala | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala index bf83426088..578350a5ed 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala @@ -16,9 +16,12 @@ object CodeGenerator { } def main(args: Array[String]): Unit = { - args.foreach(println) + val outputDir = args(0) + val archiveId = args(1) + val schemaGroup = if (args.length > 2) Some(args(2)) else None + val dirPaths = ProjectPaths( - args.headOption.getOrElse("~/DiscreteZooOutput"), + outputDir, "backend/src/main/scala/xyz/discretezoo/web", "frontend/src", "db" @@ -26,61 +29,59 @@ object CodeGenerator { val jdbcInfo = JDBCInfo("jdbc:postgresql://localhost:5432/discretezoo2", "discretezoo", "D!screteZ00") val controller = Controller.make(true, true, List()) - val graphPath = SchemaLang._base ? "Graph" - val mxPath = SchemaLang._base ? "Maniplex" - val exJoe = SchemaLang._base ? "MatrixS" - val exJane = SchemaLang._base ? "MatrixWithCharacteristicS" - val mPath = SchemaLang._base ? "Matrices" - val sg = "Joe" +// val graphPath = SchemaLang._base ? "Graph" +// val mxPath = SchemaLang._base ? "Maniplex" +// val exJoe = SchemaLang._base ? "MatrixS" +// val exJane = SchemaLang._base ? "MatrixWithCharacteristicS" +// val mPath = SchemaLang._base ? "Matrices" // remove later controller.handleLine("build ODK/DiscreteZOO mmt-omdoc") def isInputTheory(t: Theory) = { val schemaLangIsMetaTheory = t.meta.contains(SchemaLang._path) - val isInSchemaGroup = t.metadata.get(SchemaLang.schemaGroup).headOption.exists(_.value.toString == sg) + val isInSchemaGroup = schemaGroup.forall(sg => { + t.metadata.get(SchemaLang.schemaGroup).headOption.exists(_.value.toString == sg) + }) schemaLangIsMetaTheory && isInSchemaGroup } - val theories = controller.backend.getArchive("ODK/DiscreteZOO").get.allContent.map(controller.getO).collect({ + val theories = controller.backend.getArchive(archiveId).get.allContent.map(controller.getO).collect({ case Some(t : Theory) if isInputTheory(t) => t }) - theories.headOption.foreach(theory => { + val prefix = "MBGEN" + val generate = true - val maybeTable: Option[Table] = SQLBridge.test(theory.path) match { - case t: Table => Some(t) + val tableCodes = theories.map(theory => { + SQLBridge.test(theory.path) match { + case t: Table => { + val description: String = theory.metadata.get(SchemaLang.datasetName).headOption.map(_.value.toString).getOrElse("") + Some(TableCode(prefix, description, t)) + } case _ => None } - - maybeTable.foreach(t => { - - val description: String = theory.metadata.get(SchemaLang.datasetName).headOption.map(_.value.toString).getOrElse("") - - val generate = true - val prefix = "TEST" - val tableCode = TableCode(prefix, description, t) - val dbCode = DatabaseCode(dirPaths, prefix, Seq(tableCode), jdbcInfo) // TODO: one table only - val name = {t.name} - - if (generate) { - // backend - dbCode.createTablePackageDirectories() - writeToFile(s"${dirPaths.backendPackagePath}/JsonSupport.scala", dbCode.jsonSupportCode) - writeToFile(s"${dirPaths.backendPackagePath}/Create.scala", dbCode.mainCreateCode) - writeToFile(s"${dirPaths.dbPackagePath}/ZooDb.scala", dbCode.fileDbCode) - writeToFile(s"${dbCode.tablePackagePath(name)}/$name.scala", tableCode.codeCaseClass) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}PlainQuery.scala", tableCode.codePlainQueryObject) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}Table.scala", tableCode.codeTableClass) - // frontend - writeToFile(s"${dirPaths.frontendPath}/objectProperties.json", dbCode.objectPropertiesJSON) - writeToFile(s"${dirPaths.frontendPath}/collectionsData.json", dbCode.collectionDataJSON) - writeToFile(s"${dirPaths.frontendPath}/settings.json", dbCode.settingsJSON) - } - + }).collect({ case Some(t: TableCode) => t }) + + val dbCode = DatabaseCode(dirPaths, prefix, tableCodes, jdbcInfo) + + if (generate) { + // backend + dbCode.createTablePackageDirectories() + writeToFile(s"${dirPaths.backendPackagePath}/JsonSupport.scala", dbCode.jsonSupportCode) + writeToFile(s"${dirPaths.backendPackagePath}/Create.scala", dbCode.mainCreateCode) + writeToFile(s"${dirPaths.dbPackagePath}/ZooDb.scala", dbCode.fileDbCode) + tableCodes.foreach(tableCode => { + val name = tableCode.table.name + writeToFile(s"${dbCode.tablePackagePath(name)}/$name.scala", tableCode.codeCaseClass) + writeToFile(s"${dbCode.tablePackagePath(name)}/${name}PlainQuery.scala", tableCode.codePlainQueryObject) + writeToFile(s"${dbCode.tablePackagePath(name)}/${name}Table.scala", tableCode.codeTableClass) }) - - }) + // frontend + writeToFile(s"${dirPaths.frontendPath}/objectProperties.json", dbCode.objectPropertiesJSON) + writeToFile(s"${dirPaths.frontendPath}/collectionsData.json", dbCode.collectionDataJSON) + writeToFile(s"${dirPaths.frontendPath}/settings.json", dbCode.settingsJSON) + } } From 38356a4367d733c05a9b631dc171585e151c7d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Mon, 25 Mar 2019 09:50:25 +0100 Subject: [PATCH 04/63] Metadata for sql, clean up CodeGen --- src/mmt-lf/src/info/kwarc/mmt/sql/Names.scala | 14 +- .../src/info/kwarc/mmt/sql/SQLBridge.scala | 22 ++- .../src/info/kwarc/mmt/sql/Syntax.scala | 8 +- .../info/kwarc/mmt/sql/codegen/CodeFile.scala | 21 +++ .../kwarc/mmt/sql/codegen/CodeGenerator.scala | 70 ++------- .../kwarc/mmt/sql/codegen/ColumnCode.scala | 36 +++-- .../kwarc/mmt/sql/codegen/DatabaseCode.scala | 112 +++++++------- .../kwarc/mmt/sql/codegen/TableCode.scala | 144 +++++++----------- 8 files changed, 204 insertions(+), 223 deletions(-) create mode 100644 src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/Names.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/Names.scala index 158167802b..6dc39b83a3 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/Names.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/Names.scala @@ -43,10 +43,14 @@ object Codecs extends TheoryScala { object SchemaLang extends TheoryScala { val _base = MathData._base val _name = LocalName("MDDL") - val foreignKey = new Tagger(_path ? "foreignKey") - val opaque = new Tagger(_path ? "opaque") - val hidden = new Tagger(_path ? "hidden") - val collection = new Tagger(_path ? "collection") - val schemaGroup = _path ? "schemaGroup" val datasetName = _path ? "datasetName" + val schemaGroup = _path ? "schemaGroup" + object foreignKey extends BinaryLFConstantScala(_path, "foreignKey") + val opaque = new Tagger(_path ? "opaque") + val hidden = new Tagger(_path ? "hidden") + val collection = new Tagger(_path ? "collection") + + val datasetNameAnnotator = new StringAnnotator(datasetName) + val schemaGroupAnnotator = new StringAnnotator(schemaGroup) + val foreignKeyAnnotator = new TermAnnotator(foreignKey.path) } \ No newline at end of file diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala index d110b9c1e2..d9351ba598 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala @@ -22,14 +22,17 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin val logPrefix = "sql" def theoryToTable(t: Theory): Table = { + val datasetName: Option[String] = SchemaLang.datasetNameAnnotator.get(t) + val schemaGroup: Option[String] = SchemaLang.schemaGroupAnnotator.get(t) val cols = t.getConstants flatMap { case c: Constant => constantToColumn(c).toList } - Table(t.path, cols) + Table(t.path, datasetName, schemaGroup, cols) } def constantToColumn(c: Constant): Option[Column] = { val codecTerm = Codecs.codecAnnotator.get(c).getOrElse(return None) // no codec given + val foreignKeyTerm: Option[MPath] = SchemaLang.foreignKeyAnnotator.get(c).map(_.toMPath) val context = Context(c.home.toMPath) val mathType = c.tp.getOrElse { log("no mathematical type given " + c.name) @@ -56,15 +59,14 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin return None } - val isNullable = false //TODO - val isForeignKey = SchemaLang.foreignKey.get(c) val isOpaque = SchemaLang.opaque.get(c) val isHidden = SchemaLang.hidden.get(c) val collection = if (SchemaLang.collection.get(c)) { Some(CollectionInfo(c.path,c.metadata)) } else None - val col = Column(c.path, mathType, codecTermChecked, dbtype, isForeignKey, isOpaque, !isHidden, collection) + // TODO foreign key + val col = Column(c.path, mathType, codecTermChecked, dbtype, foreignKeyTerm, isOpaque, !isHidden, collection) Some(col) } @@ -160,6 +162,18 @@ object SQLBridge { case e:Error => println(e.toStringLong) } } + def test2(thyP: MPath, controller: Controller) = { + try { + val rules = RuleSet.collectRules(controller, Context(thyP)) + val bridge = new SQLBridge(controller, rules, Nil) + controller.handleLine("log+ " + bridge.logPrefix) + val thy = controller.globalLookup.getAs(classOf[Theory], thyP) + val table = bridge.theoryToTable(thy) + table + } catch { + case e:Error => println(e.toStringLong) + } + } } case class CannotTranslate(tm: Term) extends Throwable diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala index 39a390082c..a5172bedf1 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala @@ -7,10 +7,12 @@ import scala.language.existentials /** * @param path the MMT name of the table + * @param datasetName table description metadatum + * @param schemaGroup metadatum * @param columns sequence of all columns * @param collections sequence of all collections */ -case class Table(path: MPath, columns: Seq[Column]) { +case class Table(path: MPath, datasetName: Option[String], schemaGroup: Option[String], columns: Seq[Column]) { /** db name of the table, underscore style */ def name = path.name.toString /** retrieve all columns that are collections */ @@ -22,7 +24,7 @@ case class Table(path: MPath, columns: Seq[Column]) { * @param mathType the mathematical type of the column * @param codec the codec expression for en/de-coding functions between them * @param dbtype database type - * @param isForeignKey + * @param foreignKey * @param opaque no meaningful operations on column except for (in)equality (annotated in schema) * @param isDisplayedByDefault (annotated in schema) whether the column gets displayed in the default view of the result set * later we could add: displayName, description @@ -31,7 +33,7 @@ case class Table(path: MPath, columns: Seq[Column]) { * The column is anullable if it is not the primaryKey */ case class Column(path: GlobalName, mathType: Term, codec: Term, dbtype: SQLSyntax.Type[_], - isForeignKey: Boolean, opaque: Boolean, isDisplayedByDefault: Boolean, collection: Option[CollectionInfo]) { + foreignKey: Option[MPath], opaque: Boolean, isDisplayedByDefault: Boolean, collection: Option[CollectionInfo]) { /** the db name of the column */ def name = path.name.toString } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala new file mode 100644 index 0000000000..1185d1faab --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala @@ -0,0 +1,21 @@ +package info.kwarc.mmt.sql.codegen + +import java.io.PrintWriter + +case class CodeFile(replacements: Map[String, String], templatePath: String, destinationPath: Option[String] = None) { + + def writeToFile(write: Boolean): Unit = { + // TODO should it be line by line? + if (write) { + val template = scala.io.Source.fromFile(templatePath).mkString + val result = replacements.foldLeft(template)((str, mapItem) => { + str.replaceAllLiterally(mapItem._1, mapItem._2) + }) + + val actualDestination = destinationPath.getOrElse(templatePath) + val pw = new PrintWriter(new java.io.File(actualDestination)) + try pw.write(result) finally pw.close() + } + } + +} \ No newline at end of file diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala index 578350a5ed..f35204de6e 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala @@ -1,18 +1,17 @@ package info.kwarc.mmt.sql.codegen -import java.io.PrintWriter - -import info.kwarc.mmt.api.GlobalName import info.kwarc.mmt.api.frontend.Controller import info.kwarc.mmt.api.modules.Theory -import info.kwarc.mmt.api.objects.{OMA, OMS} -import info.kwarc.mmt.sql.{Column, SQLBridge, SchemaLang, Table} +import info.kwarc.mmt.sql.{SQLBridge, SchemaLang, Table} object CodeGenerator { - def writeToFile(p: String, s: String): Unit = { - val pw = new PrintWriter(new java.io.File(p)) - try pw.write(s) finally pw.close() + private def isInputTheory(t: Theory, schemaGroup: Option[String]): Boolean = { + val schemaLangIsMetaTheory = t.meta.contains(SchemaLang._path) + val isInSchemaGroup = schemaGroup.forall(sg => { + t.metadata.get(SchemaLang.schemaGroup).headOption.exists(_.value.toString == sg) + }) + schemaLangIsMetaTheory && isInSchemaGroup } def main(args: Array[String]): Unit = { @@ -27,61 +26,24 @@ object CodeGenerator { "db" ) val jdbcInfo = JDBCInfo("jdbc:postgresql://localhost:5432/discretezoo2", "discretezoo", "D!screteZ00") + val prefix = "MBGEN" + val generate = true val controller = Controller.make(true, true, List()) -// val graphPath = SchemaLang._base ? "Graph" -// val mxPath = SchemaLang._base ? "Maniplex" -// val exJoe = SchemaLang._base ? "MatrixS" -// val exJane = SchemaLang._base ? "MatrixWithCharacteristicS" -// val mPath = SchemaLang._base ? "Matrices" - // remove later - controller.handleLine("build ODK/DiscreteZOO mmt-omdoc") - - def isInputTheory(t: Theory) = { - val schemaLangIsMetaTheory = t.meta.contains(SchemaLang._path) - val isInSchemaGroup = schemaGroup.forall(sg => { - t.metadata.get(SchemaLang.schemaGroup).headOption.exists(_.value.toString == sg) - }) - schemaLangIsMetaTheory && isInSchemaGroup - } + controller.handleLine(s"build $archiveId mmt-omdoc") - val theories = controller.backend.getArchive(archiveId).get.allContent.map(controller.getO).collect({ - case Some(t : Theory) if isInputTheory(t) => t - }) - - val prefix = "MBGEN" - val generate = true - - val tableCodes = theories.map(theory => { - SQLBridge.test(theory.path) match { - case t: Table => { - val description: String = theory.metadata.get(SchemaLang.datasetName).headOption.map(_.value.toString).getOrElse("") - Some(TableCode(prefix, description, t)) + val tableCodes = controller.backend.getArchive(archiveId).get.allContent.map(controller.getO).collect({ + case Some(theory : Theory) if isInputTheory(theory, schemaGroup) => { + SQLBridge.test2(theory.path, controller) match { + case table: Table => Some(TableCode(prefix, dirPaths.dbPackagePath, table)) + case _ => None } - case _ => None } }).collect({ case Some(t: TableCode) => t }) val dbCode = DatabaseCode(dirPaths, prefix, tableCodes, jdbcInfo) - - if (generate) { - // backend - dbCode.createTablePackageDirectories() - writeToFile(s"${dirPaths.backendPackagePath}/JsonSupport.scala", dbCode.jsonSupportCode) - writeToFile(s"${dirPaths.backendPackagePath}/Create.scala", dbCode.mainCreateCode) - writeToFile(s"${dirPaths.dbPackagePath}/ZooDb.scala", dbCode.fileDbCode) - tableCodes.foreach(tableCode => { - val name = tableCode.table.name - writeToFile(s"${dbCode.tablePackagePath(name)}/$name.scala", tableCode.codeCaseClass) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}PlainQuery.scala", tableCode.codePlainQueryObject) - writeToFile(s"${dbCode.tablePackagePath(name)}/${name}Table.scala", tableCode.codeTableClass) - }) - // frontend - writeToFile(s"${dirPaths.frontendPath}/objectProperties.json", dbCode.objectPropertiesJSON) - writeToFile(s"${dirPaths.frontendPath}/collectionsData.json", dbCode.collectionDataJSON) - writeToFile(s"${dirPaths.frontendPath}/settings.json", dbCode.settingsJSON) - } + dbCode.writeAll(generate) } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala index 1577f696c3..62a8d20188 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala @@ -5,24 +5,39 @@ import info.kwarc.mmt.sql.Column case class ColumnCode(column: Column) { - def nameQuotes = s""""${column.name}"""" - def nameDb: String = column.name.toUpperCase - def nameCamelCase: String = "_([a-z\\d])".r.replaceAllIn(column.name, _.group(1).toUpperCase()) + private def nameQuotes = s""""${column.name}"""" + private def nameDb: String = column.name.toUpperCase - def typeString: String = { + private def typeString: String = { if (column.dbtype.toString == "List[List[Int]]" || column.dbtype.toString == "List[Int]") s"List[Int]" else s"Option[${column.dbtype.toString}]" } - def caseClassField: String = s"$nameCamelCase: $typeString" + private def codecName: String = column.codec match { + case OMS(x) => x.name.toString + case OMA(_, codecArgs) => codecArgs.head.toMPath.name.last.toString + } + + // JsonSupport def jsonWriterMapItem: String = s"""Some($nameQuotes -> o.$nameCamelCase.toJson)""" - def accessorMethod: String = { - s"""def $nameCamelCase: Rep[$typeString] = column[$typeString]("$nameDb")""" + // CaseClass + def caseClassField: String = s" $nameCamelCase: $typeString" + def selectMapCaseClass: String = s""" "${column.name}" -> $nameCamelCase""" + + // PlainQueryObject + def getResultItem: String = typeString match { + case "UUID" => "r.nextObject.asInstanceOf[UUID]" + case "List[Int]" => "r.<<[Seq[Int]].toList" + case _ => "r.<<" } - def selectMapItem: String = s""""${column.name}" -> this.$nameCamelCase""" + // TableClass + def nameCamelCase: String = "_([a-z\\d])".r.replaceAllIn(column.name, _.group(1).toUpperCase()) + def accessorMethod: String = s"""def $nameCamelCase: Rep[$typeString] = column[$typeString]("$nameDb")""" + def selectMapTableClass: String = s""" "${column.name}" -> this.$nameCamelCase""" + // Frontend def jsonObjectProperties: String = { val colType = column.dbtype.toString match { case "Int" => "numeric" @@ -32,9 +47,4 @@ case class ColumnCode(column: Column) { s"""$nameQuotes: {"isFilter": ${!column.opaque}, "display": $nameQuotes, "type": "$codecName"}""" } - private def codecName: String = column.codec match { - case OMS(x) => x.name.toString - case OMA(_, codecArgs) => codecArgs.head.toMPath.name.last.toString - } - } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala index 5ed26c1d93..ae8442f4dd 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala @@ -1,66 +1,74 @@ package info.kwarc.mmt.sql.codegen +import java.io.PrintWriter + case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode], dbInfo: JDBCInfo) { - def tablePackagePrefix: String = name - def tablePackagePath(name: String) = s"${paths.dbPackagePath}/$tablePackagePrefix$name" + private def writeToFile(f: String, s: String, write: Boolean): Unit = { + if (write) { + val pw = new PrintWriter(new java.io.File(f)) + try pw.write(s) finally pw.close() + } + } - def createTablePackageDirectories(): Unit = { + def writeAll(generate: Boolean = true): Unit = { + // backend + CodeFile(zooCreateRepl, s"${paths.backendPackagePath}/Create.scala").writeToFile(generate) + CodeFile(jsonSupportRepl, s"${paths.backendPackagePath}/JsonSupport.scala").writeToFile(generate) + CodeFile(zooDbRepl, s"${paths.dbPackagePath}/ZooDb.scala").writeToFile(generate) tables.foreach(t => { - val dir = new java.io.File(tablePackagePath(t.tableName)) - if (!dir.exists()) dir.mkdir() + val tPath = tablePackagePath(t.tableName) + val dir = new java.io.File(tPath) + def tableTempPath(s: String) = s"${paths.dbPackagePath}/temp/$s.scala" + def tableFilePath(s: String) = Some(s"$tPath/${t.tableName}$s.scala") + if (generate) { + if (!dir.exists()) dir.mkdir() + } + val name = t.table.name + CodeFile(t.caseClassRepl, tableTempPath("CaseClass"), tableFilePath("")).writeToFile(generate) + CodeFile(t.plainQueryRepl, tableTempPath("PlainQueryObject"), tableFilePath("PlainQuery")).writeToFile(generate) + CodeFile(t.tableClassRepl, tableTempPath("TableClass"), tableFilePath("Table")).writeToFile(generate) }) + // frontend + writeToFile(s"${paths.frontendPath}/objectProperties.json", objectPropertiesJSON, generate) + writeToFile(s"${paths.frontendPath}/collectionsData.json", collectionDataJSON, generate) + writeToFile(s"${paths.frontendPath}/settings.json", settingsJSON, generate) } - private val tableObjects = tables - .map(t => s"object ${t.tableObject} extends TableQuery(new ${t.tableClass}(_))").mkString(",\n") - - def mainCreateCode: String = { - val importTablePackages = tables - .map(t => s"import ${t.packageString}.${t.tableClass}").mkString(",\n") - val schemaCreateList = tables.map(t => s"${t.tableObject}.schema.create").mkString(", ") - scala.io.Source.fromFile(s"${paths.backendPackagePath}/Create.scala").mkString - .replaceFirst("//importTablePackages", importTablePackages) - .replaceFirst("//tableObjects", tableObjects) - .replaceFirst("//schemaCreateList", schemaCreateList) - } + // helpers - def jsonSupportCode: String = { - val importTablePackages = tables - .map(t => s"import ${t.packageString}.${t.caseClass}").mkString(",\n") - val casesToJson = tables - .map(_.jsonSupportMap).mkString("\n") - scala.io.Source.fromFile(s"${paths.backendPackagePath}/JsonSupport.scala").mkString - .replaceFirst("//importTablePackages", importTablePackages) - .replaceFirst("//casesToJson", casesToJson) - } + private def tablePackagePrefix: String = name + private def tablePackagePath(name: String) = s"${paths.dbPackagePath}/$tablePackagePrefix$name" - def fileDbCode: String = { - val importTablePackages = tables - .map(t => s"import ${t.packageString}.{${t.plainQueryObject}, ${t.tableClass}}").mkString(",\n") - val getQueryMatches = tables - .map(t => - s""" case ("${t.tableObject}", true) => ${t.plainQueryObject}.get(rp) - | case ("${t.tableObject}", false) => tb${t.tableName}.dynamicQueryResults(rp).result""" - .stripMargin).mkString("\n") - val countQueryMatches = tables - .map(t => - s""" case ("${t.tableObject}", true) => ${t.plainQueryObject}.count(qp) - | case ("${t.tableObject}", false) => tb${t.tableName}.dynamicQueryCount(qp).length.result""" - .stripMargin).mkString("\n") - scala.io.Source.fromFile(s"${paths.dbPackagePath}/ZooDb.scala").mkString - .replaceFirst("//importTablePackages", importTablePackages) - .replaceFirst("//tableObjects", tableObjects) - .replaceFirst("//getQueryMatches", getQueryMatches) - .replaceFirst("//countQueryMatches", countQueryMatches) - .replaceFirst("%jdbc%", dbInfo.jdbc) - .replaceFirst("%user%", dbInfo.user) - .replaceFirst("%pass%", dbInfo.pass) - } + private val tableObjects = tables.map(_.zooDbObject).mkString(",\n") + + // backend code + + private def zooCreateRepl: Map[String, String] = Map( + "//tableObjects" -> tableObjects, + "//importTablePackages" -> tables.map(_.zooCreateImport).mkString(",\n"), + "//schemaCreateList" -> tables.map(_.zooSchemaCreate).mkString(", ") + ) + + private def jsonSupportRepl: Map[String, String] = Map( + "//importTablePackages" -> tables.map(_.jsonSupportImport).mkString(",\n"), + "//casesToJson" -> tables.map(_.jsonSupportMap).mkString("\n") + ) + + + private def zooDbRepl: Map[String, String] = Map( + "//tableObjects" -> tableObjects, + "%jdbc%" -> dbInfo.jdbc, + "%user%" -> dbInfo.user, + "%pass%" -> dbInfo.pass, + "//importTablePackages" -> tables.map(_.zooDbImport).mkString(",\n"), + "//getQueryMatches" -> tables.map(_.dbGetQueryMatches).mkString("\n"), + "//countQueryMatches" -> tables.map(_.dbCountQueryMatches).mkString("\n") + ) - // react + // frontend jsons - def objectPropertiesJSON: String = { + private def objectPropertiesJSON: String = { val code = tables.map(_.jsonObjectProperties).mkString(",\n") s"""{ |$code @@ -68,7 +76,7 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode """.stripMargin } - def collectionDataJSON: String = { + private def collectionDataJSON: String = { val code = tables.map(_.collectionsData).mkString(",\n") s"""{ |$code @@ -76,7 +84,7 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode """.stripMargin } - def settingsJSON: String = { + private def settingsJSON: String = { val code = tables.map(_.defaultColumns).mkString(",\n") s"""{ | "title": "Search", diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala index 717d889078..869e298ff0 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala @@ -2,104 +2,64 @@ package info.kwarc.mmt.sql.codegen import info.kwarc.mmt.sql.{Column, Table} -case class TableCode(prefix: String, description: String, table: Table) { +case class TableCode(prefix: String, dbPackagePath: String, table: Table) { def tableName: String = table.name - def tableObject: String = s"tb$tableName" - def packageString = s"xyz.discretezoo.web.db.$prefix$tableName" // package for the table specific files - // class names - def plainQueryObject = s"${tableName}PlainQuery" - def tableClass = s"${tableName}Table" - def caseClass: String = table.name - - private def dbTableName = s"${prefix}_${tableName.toUpperCase}" // table name in the database + def tablePackageName : String = prefix + tableName + private def packageString = s"xyz.discretezoo.web.db.$tablePackageName" // package for the table specific files private def columnCodeList: Seq[ColumnCode] = table.columns.map(ColumnCode) + private def tableObject: String = s"tb$tableName" + + private val baseRepl: Map[String, String] = Map( + "//package" -> s"package $packageString", + "%caseClass%" -> caseClass, + "%dbTableName%" -> s"${prefix}_${tableName.toUpperCase}" // table name in the database + ) + + // CaseClass + private def caseClass: String = table.name + def caseClassRepl: Map[String, String] = baseRepl ++ Map( + "//cols" -> columnCodeList.map(_.caseClassField).mkString(",\n"), + "//selectMap" -> columnCodeList.map(_.selectMapCaseClass).mkString(",\n") + ) + + // PlainQueryObject + private def plainQueryObject: String = s"${tableName}PlainQuery" + def plainQueryRepl: Map[String, String] = baseRepl ++ Map( + "%plainQueryObject%" -> plainQueryObject, + "%tableName%" -> tableName, + "%getResultParameters%" -> table.columns.map(c => ColumnCode(c).getResultItem).mkString(", ") + ) + + // TableClass + private def tableClass = s"${tableName}Table" + def tableClassRepl: Map[String, String] = baseRepl ++ Map( + "//accessorMethods" -> columnCodeList.map(_.accessorMethod).mkString("\n"), + "//caseClassMapParameters" -> columnCodeList.map(_.nameCamelCase).mkString(" ::\n"), + "//selectMap" -> columnCodeList.map(_.selectMapTableClass).mkString(",\n"), + "%tableClass%" -> tableClass + ) + + // Create + def zooCreateImport: String = s"import $packageString.$tableClass" + def zooSchemaCreate: String = s"$tableObject.schema.create" + + // ZooDb + def zooDbImport: String = s"import $packageString.{$plainQueryObject, $tableClass}" + def zooDbObject: String = s"object $tableObject extends TableQuery(new $tableClass(_))" + def dbCountQueryMatches: String = + s""" case ("$tableObject", true) => $plainQueryObject.count(qp) + | case ("$tableObject", false) => tb$tableName.dynamicQueryCount(qp).length.result""".stripMargin + + def dbGetQueryMatches: String = + s""" case ("$tableObject", true) => $plainQueryObject.get(rp) + | case ("$tableObject", false) => tb$tableName.dynamicQueryResults(rp).result""".stripMargin // TODO def inCollectionMap: String = collections.map(_.inCollectionItem).mkString(",\n") - def codeTableClass: String = { - val accessorMethods = columnCodeList.map(_.accessorMethod).mkString("\n") - val caseClassMapParameters = columnCodeList.map(_.nameCamelCase).mkString(" ::\n") - val selectMap = columnCodeList.map(_.selectMapItem).mkString(",\n") - - s"""package $packageString - |import java.util.UUID - |import slick.collection.heterogeneous.HNil - |import slick.lifted.{ProvenShape, Rep} - |import xyz.discretezoo.web.DynamicSupport.ColumnSelector - |import xyz.discretezoo.web.ZooPostgresProfile.api._ - | - |final class $tableClass(tag: Tag) extends Table[$caseClass](tag, "$dbTableName") with ColumnSelector { - | - |def ID: Rep[UUID] = column[UUID]("ID", O.PrimaryKey) - |$accessorMethods - | - |def * : ProvenShape[$caseClass] = ( - |ID :: - |$caseClassMapParameters :: - |HNil - |).mapTo[$caseClass] - | - |val select: Map[String, Rep[_]] = Map( - |$selectMap - |) - | - |val inCollection: Map[String, Rep[Boolean]] = Map( - |"ID" -> true - |) - | - |}""".stripMargin - } - - def codeCaseClass: String = { - val cols = columnCodeList.map(_.caseClassField).mkString(",\n") - val selectMap = columnCodeList.map(_.selectMapItem).mkString(",\n") - - s"""package $packageString - |import java.util.UUID - |import xyz.discretezoo.web.ZooObject - | - |case class $caseClass( - |ID: UUID, - |$cols) extends ZooObject { - | - |def select: Map[String, _] = Map( - |$selectMap - |) - | - |}""".stripMargin - } - - def codePlainQueryObject: String = { - val getResultParameters = table.columns.map(c => { - val special = Seq("UUID", "List[Int]") - val column = ColumnCode(c) - column.typeString match { - case "UUID" => "r.nextObject.asInstanceOf[UUID]" - case "List[Int]" => "r.<<[Seq[Int]].toList" - case _ => "r.<<" - } - }).mkString(", ") - - s"""package $packageString - |import java.util.UUID - |import slick.jdbc.GetResult - |import xyz.discretezoo.web.PlainSQLSupport - |import xyz.discretezoo.web.ZooPostgresProfile.api._ - | - |object $plainQueryObject extends PlainSQLSupport[$caseClass] { - | - |override val tableName: String = "$dbTableName" - |override implicit val getResult: GetResult[$caseClass] = GetResult(r => $caseClass(r.nextObject.asInstanceOf[UUID], $getResultParameters)) - | - |val inCollection: Map[String, String] = Map( - |"$tableName" -> "ID" - |) - | - |}""".stripMargin - } - + // JsonSupport + def jsonSupportImport: String = s"import $packageString.$caseClass" def jsonSupportMap: String = { val columns = columnCodeList.map(c => s" ${c.jsonWriterMapItem}").mkString(",\n") s"""case o: $caseClass => JsObject( @@ -123,7 +83,7 @@ case class TableCode(prefix: String, description: String, table: Table) { s""""$tableObject": { | "$tableName": { | "id": "$tableName", - | "name": "$description" + | "name": "${table.datasetName}" | } |} """.stripMargin From 2bf7868c35552b749cc1b1a8bfc2937b3ad833c2 Mon Sep 17 00:00:00 2001 From: Makarius Date: Mon, 25 Mar 2019 20:21:37 +0100 Subject: [PATCH 05/63] use some AFP entry/session metadata as RDF meta data for theories --- .../info/kwarc/mmt/isabelle/Importer.scala | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 4b0ce6a96b..2bcee3ab7b 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -806,6 +806,15 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] dirs = dirs ::: select_dirs, strict = true) + /* AFP */ + + private val optional_afp: Option[isabelle.AFP] = + { + if (isabelle.Isabelle_System.getenv("AFP_BASE").isEmpty) None + else Some(isabelle.AFP.init(options)) + } + + /* resources */ val dump_options: isabelle.Options = @@ -1066,6 +1075,19 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] isabelle.RDF.meta_data(rendering.meta_data(element_range)) } + private def session_meta_data(name: String): isabelle.Properties.T = + { + (for { afp <- optional_afp; entry <- afp.sessions_map.get(name) } + yield entry.rdf_meta_data) getOrElse Nil + } + + private val rdf_author_info: Set[String] = + Set( + isabelle.RDF.Property.creator, + isabelle.RDF.Property.contributor, + isabelle.RDF.Property.license) + + def read_theory_export(rendering: isabelle.Rendering): Theory_Export = { val snapshot = rendering.snapshot @@ -1085,7 +1107,10 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] val node_elements = isabelle.Thy_Element.parse_elements(syntax.keywords, snapshot.node.commands.toList) - val node_meta_data = + val theory_session_meta_data = + session_meta_data(theory_qualifier(node_name)).filter(p => rdf_author_info(p._1)) + + val theory_meta_data = node_elements.find(element => element.head.span.name == isabelle.Thy_Header.THEORY) match { case Some(element) => element_meta_data(rendering, element) case None => Nil @@ -1185,7 +1210,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] Theory_Export(node_name, node_source = Source(snapshot.node.source), node_timing = node_timing, - node_meta_data = node_meta_data, + node_meta_data = isabelle.Library.distinct(theory_session_meta_data ::: theory_meta_data), parents = theory.parents, segments = segments, typedefs = theory.typedefs) From cacc2d151783f07acb9d0ee3afafa37bab1a0b9e Mon Sep 17 00:00:00 2001 From: Makarius Date: Mon, 25 Mar 2019 20:52:38 +0100 Subject: [PATCH 06/63] default license for Isabelle sessions/theories --- .../src/info/kwarc/mmt/isabelle/Importer.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 2bcee3ab7b..959a321d9b 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -806,7 +806,11 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] dirs = dirs ::: select_dirs, strict = true) - /* AFP */ + /* Isabelle + AFP library info */ + + private val isabelle_sessions: Set[String] = + isabelle.Sessions.load_structure(options, select_dirs = List(isabelle.Path.explode("$ISABELLE_HOME"))). + selection(isabelle.Sessions.Selection.empty).imports_graph.keys.toSet private val optional_afp: Option[isabelle.AFP] = { @@ -1077,8 +1081,11 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] private def session_meta_data(name: String): isabelle.Properties.T = { - (for { afp <- optional_afp; entry <- afp.sessions_map.get(name) } - yield entry.rdf_meta_data) getOrElse Nil + if (isabelle_sessions(name)) List(isabelle.RDF.Property.license -> "BSD") + else { + (for { afp <- optional_afp; entry <- afp.sessions_map.get(name) } + yield entry.rdf_meta_data) getOrElse Nil + } } private val rdf_author_info: Set[String] = From 4491b87d209da2fe33769c4c0247b55d48ab0414 Mon Sep 17 00:00:00 2001 From: rappatoni Date: Tue, 26 Mar 2019 11:54:21 +0100 Subject: [PATCH 07/63] Max' tests --- Test/build.sbt | 12 +++ Test/project/build.properties | 1 + .../mmt/api/web/JSONBasedGraphServer.scala | 17 ++- src/test/Translator.scala | 102 ++++++++++++++++++ src/test/preamble.scala | 2 + 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 Test/build.sbt create mode 100644 Test/project/build.properties create mode 100644 src/test/Translator.scala diff --git a/Test/build.sbt b/Test/build.sbt new file mode 100644 index 0000000000..aa5b0b6927 --- /dev/null +++ b/Test/build.sbt @@ -0,0 +1,12 @@ +import sbt.Keys.libraryDependencies + +lazy val mmt = RootProject(file("C:/mmt2/MMT/src")) + +lazy val playground = Project(id = "playground", base = file(".")).settings( + name := "playground", + version := "0.1", + scalaVersion := "2.12.8", + scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation"), + // Add further desired libraryDependencies here + // e.g. libraryDependencies += "org.jgrapht" % "jgrapht-core" % "1.3.0", +).dependsOn(mmt).aggregate(mmt) \ No newline at end of file diff --git a/Test/project/build.properties b/Test/project/build.properties new file mode 100644 index 0000000000..7609b47819 --- /dev/null +++ b/Test/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.2.8 \ No newline at end of file diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 969a3baec0..96bbb764cb 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -38,6 +38,7 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { log("Paths: " + request.pathForExtension) log("Query: " + request.query) log("Path: " + request.parsedQuery("uri")) + //log("Semantic: " + request.parsedQuery("semantic")) if (request.pathForExtension.headOption == Some("menu")) { val id = request.parsedQuery("id").getOrElse("top") log("Returing menu for " + id) @@ -166,6 +167,8 @@ abstract class SimpleJGraphExporter(key : String) extends JGraphExporter(key) { } + + class JDocgraph extends SimpleJGraphExporter("docgraph"){ val builder = GraphBuilder.PlainBuilder val selector = new JGraphSelector { @@ -248,6 +251,15 @@ class JPgraph extends SimpleJGraphExporter("pgraph") { log("Done.") ret } + /** private def allattacks = { + log("Loading attacks...") + val ret = (controller.depstore.getInds(IsAttack) collect { + case mp : MPath => mp + }).toList + log("Done.") + ret + } + */ } class JArchiveGraph extends SimpleJGraphExporter("archivegraph") { val builder = GraphBuilder.AlignmentBuilder(log(_,None)) @@ -293,7 +305,10 @@ class JMPDGraph extends SimpleJGraphExporter("mpd") { c.rl.get match { case "Law" => "model" case "BoundaryCondition" => "boundarycondition" - case _ => "theory"} + case _ => "theory" + case "Accepted" => "acceptedtheory" + case "Rejected" => "rejectedtheory" + case "Undecided" => "undecidedtheory"} }).get ostyle match { diff --git a/src/test/Translator.scala b/src/test/Translator.scala new file mode 100644 index 0000000000..2bd35d0fc9 --- /dev/null +++ b/src/test/Translator.scala @@ -0,0 +1,102 @@ + +import info.kwarc.mmt.api.{GeneralError, Level} +import info.kwarc.mmt.api.archives.Update +import info.kwarc.mmt.api.web._ +import info.kwarc.mmt.api.presentation.MMTSyntaxPresenter +import info.kwarc.mmt.api.utils.{EmptyList, File, FilePath} +import info.kwarc.mmt.api.ontology +import info.kwarc.mmt.api.ontology.RelationalReader +import info.kwarc.mmt.api.archives.Archive + +/** +object Tester extends App { + val filetest = File("C:")/"mmt2" + if (filetest.exists) + { + print("exists") + } + else {throw GeneralError("Does not exist")} +} + +object Translator extends MagicTest{ + def run : Unit = { + val a = controller.backend.getArchive( "Happening") + //println(controller.backend.getArchives.map(_.id).mkString("; ")) + println(a) + val pres = new MMTSyntaxPresenter() + controller.extman.addExtension(pres) + pres.build(a.get, Update(Level.Warning), FilePath("/")) + } +} + +object Translator2 extends MagicTest{ + import info.kwarc.mmt.api.presentation._ + import info.kwarc.mmt.api.archives._ + def run : Unit = { + val a = controller.backend.getArchive("MMT/LATIN").get + val pres = new MMTSyntaxPresenter() + controller.extman.addExtension(pres) + // pres.build(a.get,Update(Level.Warning),FilePath("")) + controller.buildArchive(List(a.id), "present-text-notations", Build, FilePath("")) + } +} + +object MyReader extends MagicTest{ + def run : Unit = { + val a = controller.backend.getArchive( "MitM/algebra") + val read =new RelationalReader() + a map read.oncePerArchive + } +} + +object MyReader2 extends MagicTest{ + def run : Unit = { + val a = controller.backend.getArchive( "MitM/Foundation") + val mygraph = a.get.allContent + println{mygraph} + } +} + + + +// object testserver extends Server (8080, ) {} + +class SemanticComputer { + /** Takes an MMT archive and computes argumentation semantics on it. + * + */ + def main: Unit = { + + } + + def grounded: Unit = { + + } + +} + +/** + * + */ + * + **/ + +object Graphtester extends MagicTest { + def run : Unit = { + val test = WebQuery("type=archivegraph&graphdata=MMT/urtheories&semantic=grounded") + // println(test) + println( test("graphdata")) + //val serve = new JSONBasedGraphServer() + + + + } + + +} + +object Jsonprinter extends MagicTest { + def run : Unit = { + new JSONBasedGraphServer("GET", Map(("uri", "MMT/urtheories" ), )) + } +} \ No newline at end of file diff --git a/src/test/preamble.scala b/src/test/preamble.scala index c00b7bc60f..3fbfefe52b 100644 --- a/src/test/preamble.scala +++ b/src/test/preamble.scala @@ -117,6 +117,8 @@ object MagicTest { home / "Projects" / "gl.mathhub.info", // Tom home / "Development" / "KWARC" / "content", // Jonas home / "content", // Michael + //File("C:/mmt2/content/Mathhub"), //Max + File("C:") / "/mmt2" / "/content" / "/MathHub", File("C:") / "other" / "oaff", ).find(_.exists).getOrElse(throw GeneralError("MagicTest failed: No known archive root")) } From 2a02c2e93c4b6007ce991a2a95442dc76976b75c Mon Sep 17 00:00:00 2001 From: Makarius Date: Tue, 26 Mar 2019 11:55:35 +0100 Subject: [PATCH 08/63] tuned --- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 959a321d9b..5628dfd499 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -541,8 +541,9 @@ object Importer thy_draft.declare_item(item, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.`object`)) - if (segment.is_axiomatization) + if (segment.is_axiomatization) { thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.primitive)) + } val tp = Isabelle.Type.all(decl.typargs, thy_draft.content.import_type(decl.typ)) val df = decl.abbrev.map(rhs => Isabelle.Type.abs(decl.typargs, thy_draft.content.import_term(rhs))) From 14d62a24c6aff95fded74f4c6ed0f780d41a35a7 Mon Sep 17 00:00:00 2001 From: Makarius Date: Tue, 26 Mar 2019 13:28:54 +0100 Subject: [PATCH 09/63] support ulo:predicate --- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala | 3 +++ src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala | 1 + 2 files changed, 4 insertions(+) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 5628dfd499..e889750cae 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -544,6 +544,9 @@ object Importer if (segment.is_axiomatization) { thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.primitive)) } + if (decl.propositional) { + thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.predicate)) + } val tp = Isabelle.Type.all(decl.typargs, thy_draft.content.import_type(decl.typ)) val df = decl.abbrev.map(rhs => Isabelle.Type.abs(decl.typargs, thy_draft.content.import_term(rhs))) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala index a333ea00bc..c5e9efe94c 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala @@ -42,6 +42,7 @@ object Ontology val universe = ulo("universe") // type classes (primitive entity) val name = ulo("name") // external entity name (xname) + val predicate = ulo("predicate") // term constants with propositional body type val theory = ulo("theory") // theory, locale (subsumes type classes as logical specification) From 36eb3020ddf77aba4506045ebea68727568ce723 Mon Sep 17 00:00:00 2001 From: Makarius Date: Tue, 26 Mar 2019 22:21:51 +0100 Subject: [PATCH 10/63] support ulo:inductive-on (non-mutual primrec) --- .../src/info/kwarc/mmt/isabelle/Importer.scala | 8 ++++++++ .../src/info/kwarc/mmt/isabelle/Ontology.scala | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index e889750cae..f0b89494a8 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -548,6 +548,14 @@ object Importer thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.predicate)) } + decl.primrec_types match { + case List(type_name) => + thy_draft.rdf_triple( + Ontology.binary(item.global_name, Ontology.ULO.inductive_on, + thy_draft.content.get_type(type_name).global_name)) + case _ => + } + val tp = Isabelle.Type.all(decl.typargs, thy_draft.content.import_type(decl.typ)) val df = decl.abbrev.map(rhs => Isabelle.Type.abs(decl.typargs, thy_draft.content.import_term(rhs))) add_constant(item, tp, df) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala index c5e9efe94c..91d5cbf43f 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala @@ -66,6 +66,8 @@ object Ontology val instance_of = ulo("instance-of") // locale interpretation + val inductive_on = ulo("inductive-on") // const is specified via primitive recursion on type + val specifies = ulo("specifies") // theory/locale declares item val specified_in = ulo("specified-in") // inverse of "specifies" From f505341a29b090530a4aeec401dc91aed131509d Mon Sep 17 00:00:00 2001 From: Makarius Date: Tue, 26 Mar 2019 22:52:40 +0100 Subject: [PATCH 11/63] adapted to evolving ontology --- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala | 5 ++++- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index f0b89494a8..ded9dd4e37 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -540,13 +540,16 @@ object Importer val item = thy_draft.make_item(decl.entity, decl.syntax, (decl.typargs, decl.typ)) thy_draft.declare_item(item, segment.meta_data) - thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.`object`)) if (segment.is_axiomatization) { thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.primitive)) } + if (decl.propositional) { thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.predicate)) } + else { + thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.function)) + } decl.primrec_types match { case List(type_name) => diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala index 91d5cbf43f..a722d03f45 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala @@ -37,7 +37,7 @@ object Ontology /* unaries */ val `type` = ulo("type") // type constructors - val `object` = ulo("object") // term constants + val function = ulo("function") // term constants val statement = ulo("statement") // fact items (thm) val universe = ulo("universe") // type classes (primitive entity) From 45df57a1fa6bae4ba07ae087ff7e4bbb202d4fea Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Wed, 27 Mar 2019 11:43:58 +0100 Subject: [PATCH 12/63] bunch of refactorings --- .../kwarc/mmt/api/archives/BuildQueue.scala | 2 +- .../kwarc/mmt/api/archives/BuildTarget.scala | 12 +-- .../kwarc/mmt/stex/LaTeXBuildTarget.scala | 73 ++++++++------ .../src/info/kwarc/mmt/stex/LaTeXML.scala | 96 ++++++++++-------- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 99 ++++++++++++------- .../src/info/kwarc/mmt/stex/STeXUtils.scala | 63 +++++++----- 6 files changed, 206 insertions(+), 139 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala index f6641c87dd..9adbe82b9f 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala @@ -18,7 +18,7 @@ class QueuedTask(val target: TraversingBuildTarget, estRes: BuildResult, val tas var lowPriority: Boolean = true /** task should be queued at beginning */ - def highPriority = !lowPriority + def highPriority: Boolean = !lowPriority /** task was not requested directly but added as dependency of some other task */ // TODO make this part of constructor to avoid having a var? diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index a1aaeb5cb3..9f8efa3ec5 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -254,7 +254,7 @@ class BuildTask(val key: String, val archive: Archive, val inFile: File, val chi /** build targets should set this to true if they skipped the file so that it is not passed on to the parent directory */ var skipped = false /** the narration-base of the containing archive */ - val base = archive.narrationBase + val base : URI = archive.narrationBase /** the MPath corresponding to the inFile if inFile is a file in a content-structured dimension */ def contentMPath: MPath = Archive.ContentPathToMMTPath(inPath) @@ -355,21 +355,21 @@ abstract class TraversingBuildTarget extends BuildTarget { /// ***************** auxiliary methods for computing paths to output/error files etc. - protected def getOutFile(a: Archive, inPath: FilePath) = (a / outDim / inPath).setExtension(outExt) + protected def getOutFile(a: Archive, inPath: FilePath): File = (a / outDim / inPath).setExtension(outExt) - protected def getFolderOutFile(a: Archive, inPath: FilePath) = getOutFile(a, inPath / folderName) + protected def getFolderOutFile(a: Archive, inPath: FilePath) : File = getOutFile(a, inPath / folderName) protected def getErrorFile(a: Archive, inPath: FilePath): File = FileBuildDependency(key, a, inPath).getErrorFile(controller) //TODO why is this method not like the others? //TODO why is this not protected? - def getFolderErrorFile(a: Archive, inPath: FilePath) = a / errors / key / inPath / (folderName + ".err") + def getFolderErrorFile(a: Archive, inPath: FilePath): File = a / errors / key / inPath / (folderName + ".err") @MMT_TODO("needs review") - protected def getTestOutFile(a: Archive, inPath: FilePath) = + protected def getTestOutFile(a: Archive, inPath: FilePath): File = (a / Dim("test", outDim.toString) / inPath).setExtension(outExt) - protected def getOutPath(a: Archive, outFile: File) = outFile.toFilePath + protected def getOutPath(a: Archive, outFile: File) : FilePath = outFile.toFilePath /** auxiliary method for logging results */ protected def logResult(s: String) { diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala index 8ad850e5aa..ffdb39b03c 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala @@ -19,19 +19,21 @@ import scala.concurrent.duration._ import scala.sys.process.{ProcessBuilder, ProcessLogger} /** common code for sms, latexml und pdf generation */ -abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments { - val localpathsFile = "localpaths.tex" - val inDim = source - var pipeOutput: Boolean = false - val pipeOutputOption: String = "pipe-worker-output" +abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments +{ + val localpathsFile : String = "localpaths.tex" + val inDim : RedirectableDimension = source + var pipeOutput : Boolean = false + val pipeOutputOption : String = "pipe-worker-output" + /** timout in seconds */ - private val timeoutDefault: Int = 300 - protected var timeoutVal: Int = timeoutDefault - protected val timeoutOption: String = "timeout" - protected var nameOfExecutable: String = "" + private val timeoutDefault : Int = 300 + protected var timeoutVal : Int = timeoutDefault + protected val timeoutOption : String = "timeout" + protected var nameOfExecutable : String = "" protected case class LatexError(s: String, l: String) extends ExtensionError(key, s) { - override val extraMessage = l + override val extraMessage : String = l } protected def commonOpts: OptionDescrs = List( @@ -46,9 +48,9 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis override def start(args: List[String]) { anaStartArgs(args) pipeOutput = optionsMap.get(pipeOutputOption).isDefined - optionsMap.get(timeoutOption).foreach { case v => timeoutVal = v.getIntVal } - optionsMap.get(key).foreach { case v => nameOfExecutable = v.getStringVal } - optionsMap.get("execute").foreach { case v => + optionsMap.get(timeoutOption).foreach(v => timeoutVal = v.getIntVal) + optionsMap.get(key).foreach(v => nameOfExecutable = v.getStringVal) + optionsMap.get("execute").foreach { v => if (nameOfExecutable.isEmpty) nameOfExecutable = v.getStringVal else logError("executable already set by: --" + key + "=" + nameOfExecutable) } @@ -111,8 +113,9 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis /** to be implemented */ def reallyBuildFile(bt: BuildTask): BuildResult - def buildFile(bt: BuildTask): BuildResult = if (!skip(bt)) reallyBuildFile(bt) - else BuildEmpty("file excluded by MANIFEST") + def buildFile(bt: BuildTask): BuildResult = { + if (!skip(bt)) reallyBuildFile(bt) else BuildEmpty("file excluded by MANIFEST") + } protected def readingSource(a: Archive, in: File, amble: Option[File] = None): List[Dependency] = { val res = getDeps(a, in, Set(in), amble) @@ -136,20 +139,28 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis safe } - override def estimateResult(bt: BuildTask): BuildSuccess = { - val in = bt.inFile - val a = bt.archive - val ds = if (in.exists && in.isFile) { - readingSource(a, in) ++ - (if (noAmble(in) || key == "sms") Nil - else { - val pre = getAmbleFile("pre", bt) - val post = getAmbleFile("post", bt) - List(pre, post).map(PhysicalDependency) ++ - readingSource(a, in, Some(pre)) ++ - readingSource(a, in, Some(post)) - }) - } else if (in.isDirectory) Nil + override def estimateResult(bt: BuildTask) : BuildSuccess = + { + val in = bt.inFile + val archive = bt.archive + + val ds: List[Dependency] = if (in.exists && in.isFile) { + + var dps: List[Dependency] = Nil + + if (!noAmble(in) || key != "sms") + { + val pre = getAmbleFile(preOrPost = "pre", bt) + val post = getAmbleFile(preOrPost = "post", bt) + + dps = List(pre, post).map(PhysicalDependency) ++ + readingSource(archive, in, Some(pre)) ++ + readingSource(archive, in, Some(post)) + } + + readingSource(archive, in) ++ dps + + } else if (in.isDirectory) { Nil } else { logResult("unknown file: " + in) logResult(" for: " + key) @@ -199,7 +210,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { val outDim: ArchiveDimension = source override val outExt = "tex" - override def getFolderOutFile(a: Archive, inPath: FilePath) = a / outDim / inPath + override def getFolderOutFile(a: Archive, inPath: FilePath): File = a / outDim / inPath // we do nothing for single files def reallyBuildFile(bt: BuildTask): BuildResult = BuildEmpty("nothing to do for files") @@ -251,7 +262,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { override def buildDepsFirst(a: Archive, up: Update, in: FilePath = EmptyPath) { a.traverse[Unit](inDim, in, TraverseMode(includeFile, includeDir, parallel))({ - case _ => + _ => }, { case (c@Current(inDir, inPath), _) => buildDir(a, inPath, inDir, force = false) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala index 4e4ac853a6..17d3318110 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala @@ -19,7 +19,7 @@ class AllPdf extends LaTeXDirTarget { BuildResult.empty } - override def estimateResult(bt: BuildTask) = { + override def estimateResult(bt: BuildTask): BuildSuccess = { if (bt.isDir) { val a = bt.archive val ls = getAllFiles(bt).map(f => FileBuildDependency("pdflatex", a, bt.inPath / f)) @@ -54,56 +54,70 @@ class AllTeX extends LaTeXDirTarget { } } - def buildDir(a: Archive, in: FilePath, dir: File, force: Boolean): BuildResult = { + def buildDir(a: Archive, in: FilePath, dir: File, force: Boolean): BuildResult = + { val dirFiles = getDirFiles(a, dir, includeFile) var success = false - if (dirFiles.nonEmpty) { + + if (dirFiles.nonEmpty) + { createLocalPaths(a, dir) - val deps = getDepsMap(getFilesRec(a, in)) - val ds = Relational.flatTopsort(controller, deps) - val ts = ds.collect { + val deps : Map[Dependency, Set[Dependency]] = getDepsMap(getFilesRec(a, in)) + val ds : List[Dependency] = Relational.flatTopsort(controller, deps) + + val ts: List[File] = ds.collect { case bd: FileBuildDependency if List(key, "tex-deps").contains(bd.key) => bd }.map(d => d.archive / inDim / d.inPath) + val files = ts.distinct.filter(dirFiles.map(f => dir / f).contains(_)).map(_.getName) assert(files.length == dirFiles.length) - val langs = files.flatMap(f => getLang(File(f))).toSet + + /* Find all languages present (by filename extension) */ + val langs : Set[String] = files.flatMap(f => getLang(File(f))).toSet val nonLangFiles = langFiles(None, files) - if (nonLangFiles.nonEmpty) success = createAllFile(a, None, dir, nonLangFiles, force) + if (nonLangFiles.nonEmpty) { success = createAllFile(a, None, dir, nonLangFiles, force) } + langs.toList.sorted.foreach { l => - val res = createAllFile(a, Some(l), dir, files, force) + val res : Boolean = createAllFile(a, Some(l), dir, files, force) success ||= res } } - if (success) BuildResult.empty - else BuildEmpty("up-to-date") + if (success) BuildResult.empty // New all-file created + else BuildEmpty("up-to-date") // Empty or all-file already up-to-date } private def ambleText(preOrPost: String, a: Archive, lang: Option[String]): List[String] = readSourceRebust(getAmbleFile(preOrPost, a, lang)).getLines().toList - /** return success */ - private def createAllFile(a: Archive, lang: Option[String], dir: File, - files: List[String], force: Boolean): Boolean = { - val all = dir / ("all" + lang.map("." + _).getOrElse("") + ".tex") - val ls = langFiles(lang, files) + /** Creates up-to-date all-File. Returns true if there was no all-file before, or it had to be updated. */ + private def createAllFile(a: Archive, lang: Option[String], dir: File, files: List[String], force: Boolean): Boolean = + { + val allFileName = dir / ("all" + lang.map("." + _).getOrElse("") + ".tex") + + val ls : List[String] = langFiles(lang, files) val w = new StringBuilder def writeln(s: String): Unit = w.append(s + "\n") - ambleText("pre", a, lang).foreach(writeln) + + /* Generate contents for up-to-date all-File */ + ambleText(preOrPost = "pre", a, lang).foreach(writeln) writeln("") + /* Handle each file of the given language */ ls.foreach { f => writeln("\\begin{center} \\LARGE File: \\url{" + f + "} \\end{center}") writeln("\\input{" + File(f).stripExtension + "} \\newpage") writeln("") } - ambleText("post", a, lang).foreach(writeln) - val newContent = w.result - val outPath = getOutPath(a, all) - if (force || !all.exists() || File.read(all) != newContent) { - File.write(all, newContent) + ambleText(preOrPost = "post", a, lang).foreach(writeln) + + val newContent : String = w.result + val outPath : FilePath = getOutPath(a, allFileName) + + if (force || !allFileName.exists() || File.read(allFileName) != newContent) { + File.write(allFileName, newContent) logSuccess(outPath) true } else { - logResult("up-to-date " + outPath) + logResult("up-to-Date " + outPath) // all-File existed and was already up-to-date false } } @@ -230,10 +244,10 @@ class LaTeXML extends LaTeXBuildTarget { } private def str2Level(lev: String): Level.Level = lev match { - case "Info" => Level.Info + case "Info" => Level.Info case "Error" => Level.Error case "Fatal" => Level.Fatal - case _ => Level.Warning + case _ => Level.Warning } private def line2Region(sLine: String, inFile: File): SourceRegion = { @@ -256,11 +270,11 @@ class LaTeXML extends LaTeXBuildTarget { } private object LtxLog { - var optLevel: Option[Level.Level] = None - var msg: List[String] = Nil - var newMsg = true - var region = SourceRegion.none - var phase = 1 + var optLevel : Option[Level.Level] = None + var msg : List[String] = Nil + var newMsg : Boolean = true + var region : SourceRegion = SourceRegion.none + var phase : Int = 1 def phaseToString(p: Int): String = "latexml-" + (p match { case 1 => "compiler" @@ -454,21 +468,20 @@ class LaTeXML extends LaTeXBuildTarget { } /** pdf generation */ -class PdfLatex extends LaTeXBuildTarget { +class PdfLatex extends LaTeXBuildTarget +{ val key = "pdflatex" override val outExt = "pdf" val outDim: ArchiveDimension = Dim("export", "pdflatex", inDim.toString) private var pdflatexPath: String = "xelatex" - override def includeFile(n: String): Boolean = - n.endsWith(".tex") && !n.endsWith(localpathsFile) + override def includeFile(n: String): Boolean = n.endsWith(".tex") && !n.endsWith(localpathsFile) override def start(args: List[String]) { super.start(args) val (_, nonOpts) = splitOptions(remainingStartArguments) - val nonOptArgs = if (nameOfExecutable.nonEmpty) nameOfExecutable :: nonOpts - else nonOpts - val newPath = getFromFirstArgOrEnvvar(nonOptArgs, "PDFLATEX", pdflatexPath) + val nonOptArgs = if (nameOfExecutable.nonEmpty) nameOfExecutable :: nonOpts else nonOpts + val newPath = getFromFirstArgOrEnvvar(nonOptArgs, name = "PDFLATEX", pdflatexPath) if (newPath != pdflatexPath) { pdflatexPath = newPath log("using executable \"" + pdflatexPath + "\"") @@ -557,12 +570,13 @@ class PdfLatex extends LaTeXBuildTarget { } } -class TikzSvg extends PdfLatex { - override val key = "tikzsvg" - override val outExt = "svg" - override val outDim = source +class TikzSvg extends PdfLatex +{ + override val key : String = "tikzsvg" + override val outExt : String = "svg" + override val outDim : RedirectableDimension = source - override def includeDir(n: String): Boolean = n.endsWith("tikz") + override def includeDir(n: String) : Boolean = n.endsWith("tikz") override def reallyBuildFile(bt: BuildTask): BuildResult = { val pdfFile = bt.inFile.setExtension("pdf") diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 2cb44d1108..1c621d0a1f 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -4,10 +4,16 @@ import info.kwarc.mmt.api.archives._ import info.kwarc.mmt.api.utils.{File, FilePath} import info.kwarc.mmt.stex.STeXUtils._ -case class STeXStructure(smslines: List[String], deps: List[Dependency]) { - def join(s2: STeXStructure): STeXStructure = - if (s2.smslines.isEmpty && s2.deps.isEmpty) this - else STeXStructure(s2.smslines ++ smslines, s2.deps ++ deps) +case class STeXStructure(smslines: List[String], deps: List[Dependency]) +{ + /* Union of two STeXStructures. */ + def join(that : STeXStructure) : STeXStructure = { + if (that.smslines.isEmpty && that.deps.isEmpty) { + this + } else { + STeXStructure(that.smslines ++ smslines, that.deps ++ deps) + } + } } /** @@ -35,8 +41,7 @@ trait STeXAnalysis { } } - def mkFileDep(archive: Archive, filePath: FilePath): Dependency = - FileBuildDependency("tex-deps", archive, filePath) + def mkFileDep(archive: Archive, filePath: FilePath): Dependency = FileBuildDependency("tex-deps", archive, filePath) def mhRepos(a: Archive, r: String, b: String): List[Dependency] = { val fp = entryToPath(b) @@ -47,20 +52,22 @@ trait STeXAnalysis { } } - protected def toKeyDep(d: Dependency, key: String): Dependency = d match { + protected def toKeyDep(d: Dependency, key: String) : Dependency = d match { case FileBuildDependency(_, ar, fp) => FileBuildDependency(key, ar, fp) case fd => fd } - protected def matchPathAndRep(a: Archive, inFile: File, line: String, parents: Set[File]): List[Dependency] = + protected def matchPathAndRep(archive : Archive, inFile: File, line: String, parents: Set[File]) : List[Dependency] = line match { - case beginModnl(_, _, b) => List(mkFileDep(a, entryToPath(b))) + case beginModnl(_, _, b) => List(mkFileDep(archive, entryToPath(b))) + case input(_, _, _, b) => val p = if (line.startsWith("\\lib")) - STeXUtils.getAmbleFile(b + ".tex", a, None) + STeXUtils.getAmbleFile(b + ".tex", archive, None) else (inFile.up / b).setExtension("tex") val d = PhysicalDependency(p) - d :: (if (!parents.contains(p)) getDeps(a, p, parents + p) else Nil) + d :: (if (!parents.contains(p)) getDeps(archive, p, parents + p) else Nil) + case includeGraphics(_, _, b) => val gExts = List("png", "jpg", "eps", "pdf") val p = inFile.up / b @@ -72,18 +79,37 @@ trait STeXAnalysis { if (os.isEmpty) log(inFile + " misses graphics file: " + b) os.toList.map(s => PhysicalDependency(p.setExtension(s))) } + + case useMhModule(r,b) => { + println("\nuseMhModule mit r = " + r + " und b = " + b) + + val argm : Map[String,String] = getArgMap(r) + + val archiveS : String = argm.getOrElse("repos", archString(archive)) + + assert(getArgMap(r).get("path").isDefined) + val pathS : FilePath = FilePath(getArgMap(r)("path")) + + val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") + println("useMhModule nu_dep: " + nu_dep) + + List(nu_dep) + } + case importOrUseModule(r) => getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList + case mhinputRef(_, r, b) => val fp = entryToPath(b) Option(r) match { - case Some(id) => mkDep(a, id, fp) - case None => List(mkFileDep(a, fp)) + case Some(id) => mkDep(archive, id, fp) + case None => List(mkFileDep(archive, fp)) } - case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) - case guse(r, b) => mkDep(a, r, entryToPath(b)) - case useMhProblem(_, r, b) => createMhImport(a, r, b).flatMap(_.deps).map(toKeyDep(_, "tikzsvg")) - case includeMhProblem(_, r, b) => mhRepos(a, r, b) + + case tikzinput(_, r, b) => mhRepos(archive, r, b).map(toKeyDep(_, key = "tikzsvg")) + case guse(r, b) => mkDep(archive, r, entryToPath(b)) + case useMhProblem(_, r, b) => createMhImport(archive, r, b).flatMap(_.deps).map(toKeyDep(_, key = "tikzsvg")) + case includeMhProblem(_, r, b) => mhRepos(archive, r, b) case _ => Nil } @@ -91,7 +117,7 @@ trait STeXAnalysis { "\\importmodule[load=" + a.root.up.up + "/" + r + "/source/" + p + ",ext=" + ext + "]" + s + "%" private def mkMhImport(a: Archive, r: String, p: String, s: String): STeXStructure = - STeXStructure(List(mkImport(a, r, p, s, "sms")), mkDep(a, r, entryToPath(p)).map(toKeyDep(_, "sms"))) + STeXStructure(List(mkImport(a, r, p, s, ext = "sms")), mkDep(a, r, entryToPath(p)).map(toKeyDep(_, key = "sms"))) private def mkGImport(a: Archive, r: String, p: String): STeXStructure = STeXStructure(List(mkImport(a, r, p, "{" + p + "}", "tex"), "\\mhcurrentrepos{" + r + "}%"), @@ -132,44 +158,51 @@ trait STeXAnalysis { /** create sms file */ def createSms(a: Archive, inFile: File, outFile: File) { val smsLines = mkSTeXStructure(a, inFile, readSourceRebust(inFile).getLines, Set.empty).smslines - if (smsLines.nonEmpty) File.write(outFile, smsLines.reverse.mkString("", "\n", "\n")) - else log("no sms content") + if (smsLines.nonEmpty) File.write(outFile, smsLines.reverse.mkString("", "\n", "\n")) else log("no sms content") } /** get dependencies */ def getDeps(a: Archive, in: File, parents: Set[File], amble: Option[File] = None): List[Dependency] = { val f = amble.getOrElse(in) - if (f.exists) + if (f.exists) { mkSTeXStructure(a, in, readSourceRebust(f).getLines, parents).deps + } else Nil } /** in file is used for relative \input paths */ - def mkSTeXStructure(a: Archive, in: File, lines: Iterator[String], parents: Set[File]): STeXStructure = { - var struct = STeXStructure(Nil, Nil) - def join(s: STeXStructure) = { - struct = struct.join(s) + def mkSTeXStructure(archive: Archive, in : File, lines : Iterator[String], parents : Set[File]): STeXStructure = + { + var structure : STeXStructure = STeXStructure(Nil, Nil) + def join(s : STeXStructure) : Unit = { + structure = structure.join(s) } lines.foreach { line => val l = stripComment(line).trim - val verbIndex = l.indexOf("\\verb") - if (verbIndex <= -1) { - val sl = matchSmsEntry(a, l) + val isVerbatim = l.contains("\\verb") + if (!isVerbatim) + { + val sl : List[STeXStructure] = matchSmsEntry(archive, l) sl.foreach(join) - if (key != "sms") { - val od = matchPathAndRep(a, in, l, parents) + + if (key != "sms") + { + val od = matchPathAndRep(archive, in, l, parents) od.foreach(d => join(STeXStructure(Nil, List(d)))) } } } - struct + structure } - def matchSmsEntry(a: Archive, line: String): List[STeXStructure] = { + def matchSmsEntry(a: Archive, line: String) : List[STeXStructure] = { line match { case importMhModule(r, b) => - createMhImport(a, r, b) + //println("smsEntry: a = " + a.id + " line = " + line) + val foo = createMhImport(a, r, b) + //println("foo (deps) = " + foo.head.deps.toString()) + foo case gimport(_, r, p) => List(createGImport(a, r, p)) case smsGStruct(_, r, _, p) => diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index ca3de52fbd..7b86c4915d 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -6,7 +6,8 @@ import info.kwarc.mmt.api.utils._ import scala.io.{BufferedSource, Codec} import scala.util.matching.Regex -object STeXUtils { +object STeXUtils +{ val c : String = java.io.File.pathSeparator def mathHubDir(bt: BuildTask): File = bt.archive.root.up.up.up @@ -19,7 +20,8 @@ object STeXUtils { def sysEnv(v: String): String = sys.env.getOrElse(v, "") - def env(bt: BuildTask): List[(String, String)] = { + def env(bt: BuildTask): List[(String, String)] = + { val sty = "STEXSTYDIR" val tex = "TEXINPUTS" val styEnv = sysEnv(sty) @@ -33,11 +35,20 @@ object STeXUtils { def getLang(f: File): Option[String] = f.stripExtension.getExtension + /* convenience method that you can hand a BuildTask to*/ def getAmbleFile(preOrPost: String, bt: BuildTask): File = { getAmbleFile(preOrPost, bt.archive, getLang(bt.inFile)) } - private def getLangAmbleFile(defaultFile: File, lang: Option[String]): File = + def getAmbleFile(preOrPost: String, a: Archive, lang: Option[String]) : File = { + def ambleFile(root: File): File = (root / "lib" / preOrPost).setExtension("tex") + val repoFile = getLangAmbleFile(ambleFile(a.root), lang) + if (repoFile.exists()) + repoFile + else getLangAmbleFile(ambleFile(groupMetaInf(a)), lang) + } + + private def getLangAmbleFile(defaultFile: File, lang: Option[String]) : File = if (lang.isDefined) { val langFile = defaultFile.stripExtension.setExtension(lang.get + ".tex") if (langFile.exists) @@ -48,16 +59,7 @@ object STeXUtils { def groupMetaInf(a: Archive): File = a.root.up / "meta-inf" - def getAmbleFile(preOrPost: String, a: Archive, lang: Option[String]): File = { - def ambleFile(root: File): File = (root / "lib" / preOrPost).setExtension("tex") - val repoFile = getLangAmbleFile(ambleFile(a.root), lang) - if (repoFile.exists()) - repoFile - else getLangAmbleFile(ambleFile(groupMetaInf(a)), lang) - } - - def readSourceRebust(f: File): BufferedSource = - scala.io.Source.fromFile(f)(Codec.UTF8) + def readSourceRebust(f: File) : BufferedSource = scala.io.Source.fromFile(f)(Codec.UTF8) def stripComment(line: String): String = { val idx = line.indexOf('%') @@ -88,26 +90,33 @@ object STeXUtils { private val optArg1 = opt0 + arg1 private val bs = "\\\\" private val oStar = "\\*?" - val input: Regex = (bs + "(lib)?input" + oStar + optArg1).r - val includeGraphics: Regex = (bs + "includegraphics" + oStar + optArg1).r - val importOrUseModule: Regex = (bs + "(import|use)Module" + opt + any).r - val guse: Regex = (bs + "guse" + opt + arg1).r - val useMhProblem: Regex = (bs + "includemhproblem" + optArg1).r - val includeMhProblem: Regex = (bs + "includemhproblem" + optArg1).r - val beginModnl: Regex = (bs + begin("m?h?modnl") + optArg1).r - val mhinputRef: Regex = (bs + "m?h?inputref" + optArg1).r - val tikzinput: Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r + + val input : Regex = (bs + "(lib)?input" + oStar + optArg1).r + val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r + val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r + val useMhProblem : Regex = (bs + "usemhproblem" + optArg1).r + val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r + val beginModnl : Regex = (bs + begin("m?h?modnl") + optArg1).r + val mhinputRef : Regex = (bs + "m?h?inputref" + optArg1).r + val tikzinput : Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r + private val smsKeys: List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ List("import", "adopt", "adoptmh").map(_ + "module") - private val smsTopKeys: List[String] = List("module", "importmodulevia", "importmhmodulevia") - val smsRegs: Regex = { + + private val smsTopKeys : List[String] = List("module", "importmodulevia", "importmhmodulevia") + + val smsRegs : Regex = { val begins: String = begin(mkRegGroup(smsTopKeys)) val ends: String = smsTopKeys.mkString("|end\\{(", "|", ")\\}") ("^\\\\(" + mkRegGroup(smsKeys) + "|" + begins + ends + ")").r } - val importMhModule: Regex = (bs + "importmhmodule" + opt + "(.*?)").r - val gimport: Regex = (bs + "gimport" + oStar + optArg1).r + + val importMhModule : Regex = (bs + "importmhmodule" + opt + "(.*?)").r + val useMhModule : Regex = (bs + "usemhmodule" + opt + arg + any).r + + val gimport : Regex = (bs + "gimport" + oStar + optArg1).r + val guse : Regex = (bs + "guse" + opt + arg1).r private def optArg2(s: String): String = bs + begin(s) + opt + arg + arg @@ -129,7 +138,7 @@ object STeXUtils { res } - def getProfile(a: Archive): Option[String] = { + def getProfile(a: Archive) : Option[String] = { val key = "profile" var opt = a.properties.get(key) if (opt.isEmpty) { From 0d56ff1cbb78b8d1be60be3d04c9021279a6fb4b Mon Sep 17 00:00:00 2001 From: Makarius Date: Wed, 27 Mar 2019 12:08:46 +0100 Subject: [PATCH 13/63] plain names without mail address --- .../src/info/kwarc/mmt/isabelle/Importer.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index ded9dd4e37..7aafe6d909 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -1103,12 +1103,15 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] } } - private val rdf_author_info: Set[String] = - Set( - isabelle.RDF.Property.creator, - isabelle.RDF.Property.contributor, - isabelle.RDF.Property.license) - + private def rdf_author_info(entry: isabelle.Properties.Entry): Option[isabelle.Properties.Entry] = + { + val (a, b) = entry + if (a == isabelle.RDF.Property.creator || a == isabelle.RDF.Property.contributor) { + Some(a -> isabelle.AFP.trim_mail(b)) + } + else if (a == isabelle.RDF.Property.license) Some(entry) + else None + } def read_theory_export(rendering: isabelle.Rendering): Theory_Export = { @@ -1130,7 +1133,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] isabelle.Thy_Element.parse_elements(syntax.keywords, snapshot.node.commands.toList) val theory_session_meta_data = - session_meta_data(theory_qualifier(node_name)).filter(p => rdf_author_info(p._1)) + session_meta_data(theory_qualifier(node_name)).flatMap(rdf_author_info) val theory_meta_data = node_elements.find(element => element.head.span.name == isabelle.Thy_Header.THEORY) match { From aaad67953fe842264fb4e3b48216275a3bf3fe1b Mon Sep 17 00:00:00 2001 From: Makarius Date: Wed, 27 Mar 2019 15:57:04 +0100 Subject: [PATCH 14/63] support primitive corecursion as well --- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala | 3 ++- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 7aafe6d909..0ae684263f 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -553,8 +553,9 @@ object Importer decl.primrec_types match { case List(type_name) => + val predicate = if (decl.corecursive) Ontology.ULO.coinductive_for else Ontology.ULO.inductive_on thy_draft.rdf_triple( - Ontology.binary(item.global_name, Ontology.ULO.inductive_on, + Ontology.binary(item.global_name, predicate, thy_draft.content.get_type(type_name).global_name)) case _ => } diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala index a722d03f45..30e3144d42 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala @@ -67,6 +67,7 @@ object Ontology val instance_of = ulo("instance-of") // locale interpretation val inductive_on = ulo("inductive-on") // const is specified via primitive recursion on type + val coinductive_for = ulo("coinductive-for") // const is specified via primitive co-recursion for type val specifies = ulo("specifies") // theory/locale declares item val specified_in = ulo("specified-in") // inverse of "specifies" From 1eb0404aad05dc9faffb97aa93708a2f34c356e7 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Thu, 28 Mar 2019 11:20:56 +0100 Subject: [PATCH 15/63] Resolve archive description paths properly This commit resolves the archive descriptions paths both w.r.t. the git repository root and w.r.t. the meta-inf folder. --- .../Context/Builders/ArchiveBuilder.scala | 7 +- .../Context/Builders/GroupBuilder.scala | 6 +- .../info/kwarc/mmt/api/archives/LMHHub.scala | 72 ++++++++++++------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala index b61aa970f3..789898270f 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala @@ -50,12 +50,9 @@ trait ArchiveBuilder { this: Builder => val tags = entry.tags.map(t => getTagRef("@" + t).getOrElse(return buildFailure(entry.id, s"getTagRef(archive.tag[@$t])"))) // get the description file - val file = entry.root / entry.archive.properties.getOrElse("description", "desc.html") - val description = if(entry.root <= file && file.exists()) { - File.read(file) - } else { "No description provided" } + val description = entry.readLongDescription.getOrElse("No description provided") - val responsible = entry.archive.properties.getOrElse("responsible", "").split(",").map(_.trim).toList + val responsible = entry.properties.getOrElse("responsible", "").split(",").map(_.trim).toList val narrativeRootPath = entry.archive.narrationBase.toString val narrativeRoot = getDocument(narrativeRootPath) diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala index 3b7886319d..ca8408159f 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala @@ -44,11 +44,7 @@ trait GroupBuilder { this: Builder => val ref = getGroupRef(entry.group).getOrElse(return buildFailure(entry.group, "getGroupRef(group.id)")) // get the description file - val file = entry.root / entry.properties.getOrElse("description", "desc.html") - val description = if(entry.root <= file && file.exists()) { - File.read(file) - } else { "No description provided" } - + val description = entry.readLongDescription.getOrElse("No description provided") val responsible = entry.properties.getOrElse("responsible", "").split(",").map(_.trim).toList val archives = mathHub.entries_.collect({case archive: LMHHubArchiveEntry if archive.group == ref.id => archive.id }) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala index ac662838f6..c57040f701 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala @@ -127,35 +127,52 @@ trait LMHHubEntry extends Logger { val logPrefix: String = "lmh" def report: Report = controller.report - /** the name of the group of this entry */ - lazy val group: String = id.split("/").toList.headOption.getOrElse("") - /** the name of this archive */ - lazy val name: String = id.split("/").toList.lastOption.getOrElse("") - - // Things to be implemented + /** the local root of this archive entry */ + val root: File /** loads the LMHHubEntry or throw an error if it is invalid */ def load(): Unit + + /** the properties of this entry, if any */ + def properties: Map[String, String] + + /** reads the long description */ + def readLongDescription: Option[String] = { + val filename = properties.getOrElse("description", "desc.html") + List(root, root / "META-INF").map(_ / filename).find(_.exists).map(File.read) + } + + /** the id of this archive entry */ val id: String - /** the local root of this archive entry */ - val root: File + /** the name of the group of this entry */ + lazy val group: String = id.split("/").dropRight(1).mkString("/") + /** the name of this archive */ + lazy val name: String = id.split("/").lastOption.getOrElse("") + /** check if this archive matches a given spec */ def matches(spec : String): Boolean = LMHHub.matchesComponents(spec, id) + + + // version control things + /** download information about archive versions from the remote */ def fetch: Boolean /** push the newest version of this archive to the remote */ def push: Boolean /** pull the newest version of this archive from the remote */ def pull: Boolean + /** reset the remote url of this archive to a given one */ def setRemote(remote : String) : Boolean /** fix the remote url of the archive */ def fixRemote: Boolean = setRemote(hub.remoteURL(id)) + /** returns the physical version (a.k.a commit hash) of an installed archive */ def physicalVersion: Option[String] /** returns the logical version (a.k.a branch) of an installed archive */ def logicalVersion: Option[String] + /** gets the version of an installed archive, a.k.a. the branch of the git commit hash */ def version: Option[String] = logicalVersion.map(Some(_)).getOrElse(physicalVersion) } @@ -164,16 +181,20 @@ trait LMHHubEntry extends Logger { trait LMHHubDirectoryEntry extends LMHHubEntry { def load(): Unit = {} - lazy val id: String = (root / "..").canonical.name + "/" + root.canonical.name + def properties: Map[String, String] = Map() + + // Read the properties from the manifest + lazy val id: String = { + properties.getOrElse("id", { + val canon = s"${(root / "..").canonical.name}/${root.canonical.name}" + log(s"Unable to read id from manifest, falling back to $canon") + canon + }) + } } /** represents a single archive inside an [[LMHHub]] that is installed on disk */ trait LMHHubArchiveEntry extends LMHHubDirectoryEntry { - /** returns the [[Archive]] instance belonging to this local ArchiveHub entry */ - def archive : Archive = { - load() - controller.backend.getArchive(root).get - } /** loads this archive into the controller (if not done already) */ override def load() { controller.backend.getArchive(root).getOrElse { @@ -185,12 +206,18 @@ trait LMHHubArchiveEntry extends LMHHubDirectoryEntry { } } - /** get the id of this archive */ - override lazy val id: String = archive.id + /** returns the [[Archive]] instance belonging to this local ArchiveHub entry */ + lazy val archive : Archive = { + load() + controller.backend.getArchive(root).get + } + + /** reads the archive props */ + override def properties: Map[String, String] = archive.properties.toMap /** the list of dependencies of this archive */ def dependencies: List[String] = { - val string = archive.properties.getOrElse("dependencies", "").replace(",", " ") + val string = properties.getOrElse("dependencies", "").replace(",", " ") // check if we have a meta-inf repository, and if yes install it val deps = (if(hub.hasGroup(group)) List(group + "/meta-inf") else Nil) ::: stringToList(string) deps.distinct @@ -199,7 +226,7 @@ trait LMHHubArchiveEntry extends LMHHubDirectoryEntry { // TODO: Change meta-inf property used here /** the list of tags associated with this archive */ def tags: List[String] = { - val mfTags = archive.properties.get("tags").map(stringToList(_, ",")).getOrElse(Nil) + val mfTags = properties.get("tags").map(stringToList(_, ",")).getOrElse(Nil) (List("group/"+group) ::: mfTags).map(_.toLowerCase) } } @@ -209,7 +236,7 @@ case class NotLoadableArchiveEntry(root: File) extends api.Error("not a loadable /** represents a group archive inside an [[LMHHub]] that is installed on disk */ trait LMHHubGroupEntry extends LMHHubDirectoryEntry { - private var groupManifest: mutable.Map[String, String] = null + private var groupManifest: Map[String, String] = null override def load(): Unit = { val manifest = { @@ -217,20 +244,17 @@ trait LMHHubGroupEntry extends LMHHubDirectoryEntry { .find(_.exists).getOrElse(throw NotLoadableGroupEntry(root)) } try { - groupManifest = File.readProperties(manifest) + groupManifest = File.readProperties(manifest).toMap } catch { case e: Exception => throw NotLoadableGroupEntry(root).setCausedBy(e) } } /** the group properties */ - def properties : mutable.Map[String, String] = { + override def properties : Map[String, String] = { load() groupManifest } - - // TODO: Do we want to read the entry from the folder - // override lazy val id: String = properties.getOrElse("id", (root / "..").name) + "/" + "meta-inf" } /** Error that is thrown when an archive on disk is not an actual archive */ From 4b7943999c9fac3085a42e62a5c412bb31647742 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 28 Mar 2019 15:18:31 +0100 Subject: [PATCH 16/63] some progress --- .../kwarc/mmt/api/archives/BuildTarget.scala | 53 ++++++++++++++---- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 56 +++++++++---------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index 9f8efa3ec5..24918cd620 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -7,6 +7,8 @@ import Level.Level import frontend._ import utils._ +import java.util.Date + case class TestModifiers(compareWithTest: Boolean = false, addTest: Boolean = false, updateTest: Boolean = false) { def makeTests: Boolean = compareWithTest || addTest || updateTest } @@ -18,12 +20,12 @@ sealed abstract class BuildTargetModifier { /** default modifier: build the target */ case class Build(update: Update) extends BuildTargetModifier { - def toString(dim: String) = dim + def toString(dim: String) : String = dim } /** don't run, just delete all output files */ case object Clean extends BuildTargetModifier { - def toString(dim: String) = "-" + dim + def toString(dim: String) : String = "-" + dim } /** incremental build: skip this build if it nothing has changed */ @@ -34,7 +36,7 @@ case class Update(errorLevel: Level, dryRun: Boolean = false, testOpts: TestModi if (errorLevel <= Level.Force) "" else if (errorLevel < Level.Ignore) "!" else "*" - def toString(dim: String) = dim + key + def toString(dim: String) : String = dim + key // use dependency level for dependencies def forDependencies: Update = dependencyLevel match { @@ -50,7 +52,7 @@ case class Update(errorLevel: Level, dryRun: Boolean = false, testOpts: TestModi @MMT_TODO("needs review") //TODO this is only needed if called on the shell; check if any user actually calls it (presumably at most stex building, possibly in mathhub) case class BuildDepsFirst(update: Update) extends BuildTargetModifier { - def toString(dim: String) = dim + "&" + def toString(dim: String) : String = dim + "&" } /** forces building independent of status */ @@ -191,7 +193,7 @@ abstract class BuildTarget extends FormatBasedExtension { /** a string identifying this build target, used for parsing commands, logging, error messages */ def key: String - override def toString = super.toString + s" with key $key" + override def toString : String = super.toString + " with key " + key def isApplicable(format: String): Boolean = format == key @@ -268,9 +270,9 @@ class BuildTask(val key: String, val archive: Archive, val inFile: File, val chi /** the DPath corresponding to the inFile if inFile is in a narration-structured dimension */ def narrationDPath: DPath = DPath(base / inPath.segments) - def isDir = children.isDefined + def isDir: Boolean = children.isDefined - def isEmptyDir = children.isDefined && children.get.isEmpty + def isEmptyDir: Boolean = children.isDefined && children.get.isEmpty /** the name of the folder if inFile is a folder */ def dirName: String = outFile.toFilePath.dirPath.name @@ -460,6 +462,31 @@ abstract class TraversingBuildTarget extends BuildTarget { val outPath = bt.outPath val Update(errLev, dryRun, testMod, _) = up val rn = rebuildNeeded(deps, bt, errLev) + + /* + def exshow(s : File): String = if (s.exists()) { + val d = new Date(s.lastModified()) + "E (" + d.toString + ")" + } else "X" + + if (outPath.toString.contains("open-content.sms")) { + + var info : String = "" + + val i = bt.inFile + val e = bt.asDependency.getErrorFile(controller) + val foo = Modification(i,e) + + info += "MARKER" + if (errLev <= Level.Force) { info += "[forced]" ; assert(rn) } else { info += "[not forced]" } + info += "(rn=" + rn + ") StrictM: " + foo.toString + info += " (" + exshow(i) + "," + exshow(e) + ") // " + info += bt.inFile.toFilePath.toString + + println(info) + } + */ + if (!rn) { logResult("up-to-date " + outPath) } else if (dryRun) { @@ -481,13 +508,13 @@ abstract class TraversingBuildTarget extends BuildTarget { private def rebuildNeeded(deps: Set[Dependency], bt: BuildTask, level: Level): Boolean = { val errorFile = bt.asDependency.getErrorFile(controller) val errs = hadErrors(errorFile, level) - val mod = modified(bt.inFile, errorFile) + val mod = strictModified(bt.inFile, errorFile) level <= Level.Force || mod || errs || deps.exists { case bd: BuildDependency => val errFile = bd.getErrorFile(controller) - modified(errFile, errorFile) - case PhysicalDependency(fFile) => modified(fFile, errorFile) + strictModified(errFile, errorFile) + case PhysicalDependency(fFile) => strictModified(fFile, errorFile) case _ => false // for now } || bt.isDir && bt.children.getOrElse(Nil).exists { bf => modified(bf.asDependency.getErrorFile(controller), errorFile) @@ -611,6 +638,12 @@ abstract class TraversingBuildTarget extends BuildTarget { mod == Modified || mod == Added } + /** Stricter version of modified that also return true on "Deleted" */ + private def strictModified(inFile : File, errorFile : File) : Boolean = { + val mod = Modification(inFile, errorFile) + mod != Unmodified + } + /** @return status of input file, obtained by comparing to error file */ private def hadErrors(errorFile: File, errorLevel: Level): Boolean = if (errorLevel > Level.Fatal) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 1c621d0a1f..5771da5d10 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -80,22 +80,6 @@ trait STeXAnalysis { os.toList.map(s => PhysicalDependency(p.setExtension(s))) } - case useMhModule(r,b) => { - println("\nuseMhModule mit r = " + r + " und b = " + b) - - val argm : Map[String,String] = getArgMap(r) - - val archiveS : String = argm.getOrElse("repos", archString(archive)) - - assert(getArgMap(r).get("path").isDefined) - val pathS : FilePath = FilePath(getArgMap(r)("path")) - - val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") - println("useMhModule nu_dep: " + nu_dep) - - List(nu_dep) - } - case importOrUseModule(r) => getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList @@ -196,37 +180,53 @@ trait STeXAnalysis { structure } - def matchSmsEntry(a: Archive, line: String) : List[STeXStructure] = { + def matchSmsEntry(archive: Archive, line: String) : List[STeXStructure] = { line match { case importMhModule(r, b) => //println("smsEntry: a = " + a.id + " line = " + line) - val foo = createMhImport(a, r, b) + val foo = createMhImport(archive, r, b) //println("foo (deps) = " + foo.head.deps.toString()) foo + case useMhModule(r,b) => { + //println("\nuseMhModule mit r = " + r + " und b = " + b) + + val argm : Map[String,String] = getArgMap(r) + + val archiveS : String = argm.getOrElse("repos", archString(archive)) + + assert(getArgMap(r).get("path").isDefined) + val pathS : FilePath = FilePath(getArgMap(r)("path")) + + val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") + //println("useMhModule nu_dep: " + nu_dep) + //println("useMhModule archive: " + archive.id + " // line: " + line) + + List(STeXStructure(List(line), List(nu_dep))) + } case gimport(_, r, p) => - List(createGImport(a, r, p)) + List(createGImport(archive, r, p)) case smsGStruct(_, r, _, p) => - List(createGImport(a, r, p)) + List(createGImport(archive, r, p)) case smsMhStruct(r, _, p) => - createMhImport(a, r, p) + createMhImport(archive, r, p) case smsSStruct(r, _, p) => List(createImport(r, p)) case smsViewsig(r, _, f, t) => val m = getArgMap(r) - val fr = m.getOrElse("fromrepos", archString(a)) - val tr = m.getOrElse("torepos", archString(a)) - List(mkGImport(a, fr, f), mkGImport(a, tr, t)) + val fr = m.getOrElse("fromrepos", archString(archive)) + val tr = m.getOrElse("torepos", archString(archive)) + List(mkGImport(archive, fr, f), mkGImport(archive, tr, t)) case smsViewnl(_, r, p) => - List(createGImport(a, archString(a), p)) + List(createGImport(archive, archString(archive), p)) case smsMhView(r, _, f, t) => val m = getArgMap(r) var ofp = m.get("frompath") var otp = m.get("topath") - val fr = m.getOrElse("fromrepos", archString(a)) - val tr = m.getOrElse("torepos", archString(a)) + val fr = m.getOrElse("fromrepos", archString(archive)) + val tr = m.getOrElse("torepos", archString(archive)) (ofp, otp) match { case (Some(fp), Some(tp)) => - List(mkMhImport(a, fr, fp, f), mkMhImport(a, tr, tp, t)) + List(mkMhImport(archive, fr, fp, f), mkMhImport(archive, tr, tp, t)) case _ => Nil } case smsView(r, f, t) => From b9772b27b08cc23442731aed16453a6ade85871a Mon Sep 17 00:00:00 2001 From: Makarius Date: Thu, 28 Mar 2019 15:44:09 +0100 Subject: [PATCH 17/63] recursion types for consts need to be processed after all other types --- .../info/kwarc/mmt/isabelle/Importer.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 0ae684263f..acd0b8feae 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -551,15 +551,6 @@ object Importer thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.function)) } - decl.primrec_types match { - case List(type_name) => - val predicate = if (decl.corecursive) Ontology.ULO.coinductive_for else Ontology.ULO.inductive_on - thy_draft.rdf_triple( - Ontology.binary(item.global_name, predicate, - thy_draft.content.get_type(type_name).global_name)) - case _ => - } - val tp = Isabelle.Type.all(decl.typargs, thy_draft.content.import_type(decl.typ)) val df = decl.abbrev.map(rhs => Isabelle.Type.abs(decl.typargs, thy_draft.content.import_term(rhs))) add_constant(item, tp, df) @@ -684,7 +675,22 @@ object Importer } } - // RDF document + for (segment <- thy_export.segments) { + // information about recursion (from Spec_Rules): after all types have been exported + for (decl <- segment.consts) { + val item = thy_draft.content.get_const(decl.entity.name) + decl.primrec_types match { + case List(type_name) => + val predicate = if (decl.corecursive) Ontology.ULO.coinductive_for else Ontology.ULO.inductive_on + thy_draft.rdf_triple( + Ontology.binary(item.global_name, predicate, + thy_draft.content.get_type(type_name).global_name)) + case _ => + } + } + } + + // RDF document { val path = thy_archive.archive_rdf_path.ext("xz") isabelle.Isabelle_System.mkdirs(path.dir) From e0e480938292219149aeb1f35eb65929ab5f0eed Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 28 Mar 2019 15:58:18 +0100 Subject: [PATCH 18/63] update sbt (minor, 1.1.1. -> 1.2.7) and scala (patch 2.12.3 -> 2.12.8) versions --- src/build.sbt | 4 ++-- src/project/build.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/build.sbt b/src/build.sbt index 26cb2ed9e0..e5bcb03d55 100644 --- a/src/build.sbt +++ b/src/build.sbt @@ -41,7 +41,7 @@ lazy val mmtMainClass = "info.kwarc.mmt.api.frontend.Run" // ================================= // GLOBAL SETTINGS // ================================= -scalaVersion in Global := "2.12.3" +scalaVersion in Global := "2.12.8" scalacOptions in Global := Seq( "-feature", "-language:postfixOps", "-language:implicitConversions", "-deprecation", "-Xmax-classfile-name", "128", // fix long classnames on weird filesystems @@ -216,7 +216,7 @@ lazy val lf = (project in file("mmt-lf")). dependsOn(lfcatalog). settings(mmtProjectsSettings("mmt-lf"): _*). settings( - // libraryDependencies += "org.scala-lang" % "scala-parser-combinators" % "2.12.3" % "test", + // libraryDependencies += "org.scala-lang" % "scala-parser-combinators" % "2.12.8" % "test", ) // ================================= diff --git a/src/project/build.properties b/src/project/build.properties index 9f782f7048..72f902892a 100644 --- a/src/project/build.properties +++ b/src/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.1 \ No newline at end of file +sbt.version=1.2.7 From 94fafb88ab75a9f59adbd582b6906db3c27b797f Mon Sep 17 00:00:00 2001 From: rappatoni Date: Tue, 2 Apr 2019 19:28:26 +0200 Subject: [PATCH 19/63] Initial work on tgview backend and wrapper for argsem solvers --- src/Semantic-Computer/Semantic-Computer.iml | 12 ++++++ .../mmt/api/web/JSONBasedGraphServer.scala | 39 ++++++++++++++----- src/test/Translator.scala | 29 +++++++++++--- src/test/preamble.scala | 7 +++- 4 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 src/Semantic-Computer/Semantic-Computer.iml diff --git a/src/Semantic-Computer/Semantic-Computer.iml b/src/Semantic-Computer/Semantic-Computer.iml new file mode 100644 index 0000000000..d534117121 --- /dev/null +++ b/src/Semantic-Computer/Semantic-Computer.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 96bbb764cb..79fe7bc28e 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -12,6 +12,7 @@ import info.kwarc.mmt.api.presentation.{HTMLPresenter, MMTDocExporter, MathMLPre import info.kwarc.mmt.api.symbols._ import info.kwarc.mmt.api.utils._ + import scala.util.Try /** @@ -35,13 +36,14 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { def apply(request: ServerRequest): ServerResponse = { + println(request) log("Paths: " + request.pathForExtension) log("Query: " + request.query) log("Path: " + request.parsedQuery("uri")) - //log("Semantic: " + request.parsedQuery("semantic")) + log("Semantic: " + request.parsedQuery("semantic")) if (request.pathForExtension.headOption == Some("menu")) { val id = request.parsedQuery("id").getOrElse("top") - log("Returing menu for " + id) + log("Returning menu for " + id) if (id == "full") ServerResponse.fromJSON(sidebar.getJSON("top",true)) else ServerResponse.fromJSON(sidebar.getJSON(id)) } else if (request.pathForExtension.headOption == Some("json")) { @@ -50,10 +52,18 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { throw CatchError(s"exporter $key not available") } - log("Computing " + key + " for " + uri + "... ") - val ret = ServerResponse.fromJSON(exp.buildGraph(uri)) - log("Done") - ret + val sem = request.parsedQuery("semantic").getOrElse(null) + if (sem == null) { + log("Computing " + key + " for " + uri + "... ") + val ret = ServerResponse.fromJSON(exp.buildGraph(uri)) + log("Done") + ret + } else {log("Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") + val comp = request.parsedQuery("computer").getOrElse(return ServerResponse.errorResponse(GetError("No computer specified"), "json")) + val ret = ServerResponse.fromJSON(exp.computeSem(exp.buildGraph(uri), sem, comp)) + log("Done") + ret } + } else ServerResponse.errorResponse("Invalid path", "json") } } @@ -147,16 +157,16 @@ class JGraphSideBar extends Extension { } } -abstract class JGraphExporter(val key : String) extends FormatBasedExtension { +abstract class JGraphExporter(val key : String, val semantic : String = "none", val computer : String = "best") extends FormatBasedExtension { def isApplicable (format: String): Boolean = format == key def buildGraph(s : String) : JSON + def computeSem(f: JSON, sem: String, comp: String) : JSON } -abstract class SimpleJGraphExporter(key : String) extends JGraphExporter(key) { +abstract class SimpleJGraphExporter(key : String, semantic : String = "none", computer : String = "best") extends JGraphExporter(key,semantic) { override def logPrefix: String = key val builder : JGraphBuilder val selector : JGraphSelector - def buildGraph(s : String) : JSON = { val (ths,vs) = selector.select(s)(controller) log("building...") @@ -165,6 +175,11 @@ abstract class SimpleJGraphExporter(key : String) extends JGraphExporter(key) { res } + def computeSem(f: JSON, sem: String, comp: String = "best"): JSON = { + val semcomp = new SemanticComputer(f, sem, comp) + val ret = semcomp.TgfToJson(semcomp.CallComputer(semcomp.JsonToTgf(f), sem, comp)) + ret + } } @@ -522,3 +537,9 @@ object GraphBuilder { } } } + +class SemanticComputer (val f: JSON, val sem: String, val computer: String = "best") { + def JsonToTgf (f: JSON) : String ="test" + def TgfToJson (tgf: String) : JSON = null + def CallComputer (tgf: String, semantic: String, computer: String) : String ="test" +} diff --git a/src/test/Translator.scala b/src/test/Translator.scala index 2bd35d0fc9..cfd9d19a76 100644 --- a/src/test/Translator.scala +++ b/src/test/Translator.scala @@ -8,6 +8,18 @@ import info.kwarc.mmt.api.ontology import info.kwarc.mmt.api.ontology.RelationalReader import info.kwarc.mmt.api.archives.Archive +import info.kwarc.mmt.api.archives.Archive +import info.kwarc.mmt.api._ +import info.kwarc.mmt.api.documents.{Document, NRef} +import info.kwarc.mmt.api.frontend.{Controller, Extension, FormatBasedExtension} +import info.kwarc.mmt.api.modules._ +import info.kwarc.mmt.api.objects.{OMID, OMMOD, OMS} +import info.kwarc.mmt.api.ontology.Declares._ +import info.kwarc.mmt.api.ontology._ +import info.kwarc.mmt.api.presentation.{HTMLPresenter, MMTDocExporter, MathMLPresenter, StructurePresenter} +import info.kwarc.mmt.api.symbols._ +import info.kwarc.mmt.api.utils._ + /** object Tester extends App { val filetest = File("C:")/"mmt2" @@ -81,11 +93,11 @@ class SemanticComputer { * **/ -object Graphtester extends MagicTest { +object Graphtester extends MagicTest("jgraph") { def run : Unit = { val test = WebQuery("type=archivegraph&graphdata=MMT/urtheories&semantic=grounded") // println(test) - println( test("graphdata")) + //println( test("graphdata")) //val serve = new JSONBasedGraphServer() @@ -94,9 +106,16 @@ object Graphtester extends MagicTest { } - +/** object Jsonprinter extends MagicTest { def run : Unit = { - new JSONBasedGraphServer("GET", Map(("uri", "MMT/urtheories" ), )) + val server = new JSONBasedGraphServer + server(ServerRequest("GET", Map(("uri", "MMT/urtheories"),("key", "archivegraph"),("id", "full")), Session, ("http://localhost:8080/graphs/tgview.html?type=archivegraph&graphdata=MMT/urtheories&highlight=null"),"?type=archivegraph&graphdata=MMT/urtheories&highlight=null", Body )) } -} \ No newline at end of file +}**/ + +class SemanticComputer (f: JSON, sem: String, computer: String = "best") { + def JsonToTgf (f: JSON) : String ="test" + def TgfToJson (tgf: String) : JSON = null + def CallComputer (tgf: String, computer: String) : String ="test" +} diff --git a/src/test/preamble.scala b/src/test/preamble.scala index 3fbfefe52b..6898dcd285 100644 --- a/src/test/preamble.scala +++ b/src/test/preamble.scala @@ -118,7 +118,7 @@ object MagicTest { home / "Development" / "KWARC" / "content", // Jonas home / "content", // Michael //File("C:/mmt2/content/Mathhub"), //Max - File("C:") / "/mmt2" / "/content" / "/MathHub", + File("C:") / "/mmt2" / "/content" / "/MathHub", // Max File("C:") / "other" / "oaff", ).find(_.exists).getOrElse(throw GeneralError("MagicTest failed: No known archive root")) } @@ -134,7 +134,10 @@ object MagicTest { lazy val logfile: Option[File] = { if((home / "work").exists){ Some(home / "work" / "mmtlog.html") // Dennis - } else { + } // else if ((File("C:") / "/mmt2" / "/My stuff").exists) { + // Some(File("C:") / "/mmt2" / "/My stuff"/"mmtlog.html") // Max + //} + else { None } } From 15616b574d137be0cdee42b110ffc84299a4c1b0 Mon Sep 17 00:00:00 2001 From: rappatoni Date: Tue, 2 Apr 2019 21:50:43 +0200 Subject: [PATCH 20/63] Some more changes --- .../src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 79fe7bc28e..7cff483f1f 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -59,7 +59,7 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { log("Done") ret } else {log("Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") - val comp = request.parsedQuery("computer").getOrElse(return ServerResponse.errorResponse(GetError("No computer specified"), "json")) + val comp = request.parsedQuery("computer").getOrElse(return ServerResponse.errorResponse(GetError("No solver specified"), "json")) val ret = ServerResponse.fromJSON(exp.computeSem(exp.buildGraph(uri), sem, comp)) log("Done") ret } From 12fc2e87e49ddcf11fc238062c335d57a7fad0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=BCller?= Date: Thu, 4 Apr 2019 15:08:44 +0200 Subject: [PATCH 21/63] stats --- .../main/info/kwarc/mmt/api/archives/Archive.scala | 11 +++++++++++ src/mmt-coq/src/info/kwarc/mmt/coq/Importer.scala | 2 ++ src/mmt-coq/src/info/kwarc/mmt/coq/Syntax.scala | 10 +++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala index 1a570c9b8d..adca17eeaf 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala @@ -3,6 +3,7 @@ package info.kwarc.mmt.api.archives import info.kwarc.mmt.api._ import info.kwarc.mmt.api.backend._ import info.kwarc.mmt.api.frontend._ +import info.kwarc.mmt.api.modules.Theory import info.kwarc.mmt.api.objects._ import info.kwarc.mmt.api.ontology._ import info.kwarc.mmt.api.utils._ @@ -136,6 +137,16 @@ class Archive(val root: File, val properties: mutable.Map[String, String], val r else None } + /** Returns (#Theories,#Constants)**/ + def stats(implicit controller: Controller) = { + // val arch = controller.backend.getArchive(a).get + val ths = allContent.flatMap{mp => + Try(controller.get(mp).asInstanceOf[Theory]).toOption + } + val const = ths.flatMap(_.getConstants) + (ths.length,const.length) + } + /** * Kinda hacky; can be used to get all Modules residing in this archive somewhat quickly * TODO do properly diff --git a/src/mmt-coq/src/info/kwarc/mmt/coq/Importer.scala b/src/mmt-coq/src/info/kwarc/mmt/coq/Importer.scala index 95ef3eb652..e3be82d240 100644 --- a/src/mmt-coq/src/info/kwarc/mmt/coq/Importer.scala +++ b/src/mmt-coq/src/info/kwarc/mmt/coq/Importer.scala @@ -349,6 +349,8 @@ class Importer extends archives.Importer { case => // TODO something Nil + case {_*} => + Nil case _ : scala.xml.Comment => Nil case sec @
{ch @ _*}
=> val uri = URI((sec\"@uri").mkString) diff --git a/src/mmt-coq/src/info/kwarc/mmt/coq/Syntax.scala b/src/mmt-coq/src/info/kwarc/mmt/coq/Syntax.scala index 08e25830b8..8d987d7111 100644 --- a/src/mmt-coq/src/info/kwarc/mmt/coq/Syntax.scala +++ b/src/mmt-coq/src/info/kwarc/mmt/coq/Syntax.scala @@ -83,7 +83,15 @@ object Coq { val parent = current.asInstanceOf[Theory].path state.controller.library.getImplicit(mp,parent) match { case Some(_) => - case None => state.controller.add(PlainInclude(mp,parent),AtBegin) + case None => try { + state.controller.add(PlainInclude(mp,parent),AtBegin) + } + catch { + case e:ExtensionError => + case e => + println(e.getClass) + ??? + } } } def toGlobalName(uri : URI)(implicit state : TranslationState) : GlobalName = Try(coqtoomdoc(uri)(state.controller)).toOption match { From 312999e105b2d6f5b183357fdcb7a38b2a110413 Mon Sep 17 00:00:00 2001 From: rappatoni Date: Thu, 4 Apr 2019 16:10:04 +0200 Subject: [PATCH 22/63] Refactor to enable calling graph builder directly through query --- .../mmt/api/web/JSONBasedGraphServer.scala | 71 +++++++++++++++++-- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 7cff483f1f..129626731c 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -18,24 +18,61 @@ import scala.util.Try /** * Created by jazzpirate on 07.06.17. */ + +class DirectGraphBuilder extends Extension{ + + private case class CatchError(s : String) extends Throwable + + override def start(args: List[String]) { + controller.extman.addExtension(new JDocgraph) + controller.extman.addExtension(new JThgraph) + controller.extman.addExtension(new JPgraph) + controller.extman.addExtension(new JArchiveGraph) + controller.extman.addExtension(new JMPDGraph) + super.start(args) + } + + + def apply(query: String): JSON = { + + val uri = WebQuery(query)("uri").getOrElse(return JSONString("Not a URI")) + val key = WebQuery(query)("key").getOrElse("pgraph") + val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { + throw CatchError(s"exporter $key not available") + } + val sem = WebQuery(uri)("semantic").getOrElse("none") + if (sem == "none") { + log("Computing " + key + " for " + uri + "... ") + val ret = exp.buildGraph(uri) + log("Done") + ret + } else {log("Got here and Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") + val comp = WebQuery(query)("computer").getOrElse(return JSONString("No solver specified")) + val ret = exp.computeSem(exp.buildGraph(uri), sem, comp) + log("Done") + ret } +}} + + class JSONBasedGraphServer extends ServerExtension("jgraph") { override val logPrefix = "jgraph" private case class CatchError(s : String) extends Throwable override def start(args: List[String]) { controller.extman.addExtension(new JGraphSideBar) - controller.extman.addExtension(new JDocgraph) + controller.extman.addExtension(new DirectGraphBuilder) + /** controller.extman.addExtension(new JDocgraph) controller.extman.addExtension(new JThgraph) controller.extman.addExtension(new JPgraph) controller.extman.addExtension(new JArchiveGraph) controller.extman.addExtension(new JMPDGraph) - super.start(args) + super.start(args) **/ } lazy val sidebar = controller.extman.get(classOf[JGraphSideBar]).head + lazy val buil = controller.extman.get(classOf[DirectGraphBuilder]).head - - def apply(request: ServerRequest): ServerResponse = { + /** def apply(request: ServerRequest): ServerResponse = { println(request) log("Paths: " + request.pathForExtension) log("Query: " + request.query) @@ -52,19 +89,39 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { throw CatchError(s"exporter $key not available") } - val sem = request.parsedQuery("semantic").getOrElse(null) - if (sem == null) { + val sem = request.parsedQuery("semantic").getOrElse("none") + if (sem == "none") { log("Computing " + key + " for " + uri + "... ") val ret = ServerResponse.fromJSON(exp.buildGraph(uri)) log("Done") ret - } else {log("Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") + } else {log("Got here and Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") val comp = request.parsedQuery("computer").getOrElse(return ServerResponse.errorResponse(GetError("No solver specified"), "json")) val ret = ServerResponse.fromJSON(exp.computeSem(exp.buildGraph(uri), sem, comp)) log("Done") ret } } else ServerResponse.errorResponse("Invalid path", "json") + } **/ + + def apply(request:ServerRequest): ServerResponse = { + log("Paths: " + request.pathForExtension) + log("Query: " + request.query) + log("Path: " + request.parsedQuery("uri")) + log("Semantic: " + request.parsedQuery("semantic")) + if (request.pathForExtension.headOption == Some("menu")) { + val id = request.parsedQuery("id").getOrElse("top") + log("Returning menu for " + id) + if (id == "full") ServerResponse.fromJSON(sidebar.getJSON("top",true)) + else ServerResponse.fromJSON(sidebar.getJSON(id)) + } else if (request.pathForExtension.headOption == Some("json")) { + val graph = buil(request.query) + if (graph == JSONString("Not a URI")) {return ServerResponse.errorResponse(GetError("Not a URI"), "json")} + else if (graph == JSONString("No solver specified")){return ServerResponse.errorResponse(GetError("No solver specified"), "json")} + else { + val ret = ServerResponse.fromJSON(graph) + ret} + } else ServerResponse.errorResponse("Invalid path", "json") } } From dba2e23997189246b0e44a310f5cdd1fd733d635 Mon Sep 17 00:00:00 2001 From: rappatoni Date: Thu, 4 Apr 2019 17:05:42 +0200 Subject: [PATCH 23/63] Refactor some more and clean-up --- .../mmt/api/web/JSONBasedGraphServer.scala | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 129626731c..969c14a4e6 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -33,21 +33,18 @@ class DirectGraphBuilder extends Extension{ } - def apply(query: String): JSON = { - - val uri = WebQuery(query)("uri").getOrElse(return JSONString("Not a URI")) - val key = WebQuery(query)("key").getOrElse("pgraph") + def apply(uri: String, key: String, sem: String, comp: String): JSON = { + /* val uri = WebQuery(query)("uri").getOrElse(return JSONString("Not a URI")) + val key = WebQuery(query)("key").getOrElse("pgraph") */ val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { throw CatchError(s"exporter $key not available") } - val sem = WebQuery(uri)("semantic").getOrElse("none") if (sem == "none") { log("Computing " + key + " for " + uri + "... ") val ret = exp.buildGraph(uri) log("Done") ret - } else {log("Got here and Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") - val comp = WebQuery(query)("computer").getOrElse(return JSONString("No solver specified")) + } else {log("Got here and computing " + key + " for " + uri + "with" + sem + "semantic" + "using" + comp + "...") val ret = exp.computeSem(exp.buildGraph(uri), sem, comp) log("Done") ret } @@ -115,12 +112,13 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { if (id == "full") ServerResponse.fromJSON(sidebar.getJSON("top",true)) else ServerResponse.fromJSON(sidebar.getJSON(id)) } else if (request.pathForExtension.headOption == Some("json")) { - val graph = buil(request.query) - if (graph == JSONString("Not a URI")) {return ServerResponse.errorResponse(GetError("Not a URI"), "json")} - else if (graph == JSONString("No solver specified")){return ServerResponse.errorResponse(GetError("No solver specified"), "json")} - else { + val uri = request.parsedQuery("uri").getOrElse(return ServerResponse.errorResponse(GetError("Not a URI"), "json")) + val key = request.parsedQuery("key").getOrElse("pgraph") + val sem = request.parsedQuery("semantic").getOrElse("none") + val comp = request.parsedQuery("comp").getOrElse("default solver") + val graph = buil(uri, key , sem , comp) val ret = ServerResponse.fromJSON(graph) - ret} + ret } else ServerResponse.errorResponse("Invalid path", "json") } } From ce09406434cb39f1eda0e44b909f16956e6d84b2 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Thu, 4 Apr 2019 17:16:02 +0200 Subject: [PATCH 24/63] MathHub: Add primitive statistics This commit implements a very simple statistics implementation --- .../Context/Builders/ArchiveBuilder.scala | 4 +-- .../library/Context/Builders/Builder.scala | 5 +++- .../Context/Builders/DocumentBuilder.scala | 2 +- .../Context/Builders/GroupBuilder.scala | 8 ++--- .../Context/Builders/GroupsBuilder.scala | 2 +- .../info/kwarc/mmt/api/archives/Archive.scala | 14 +++++++-- .../info/kwarc/mmt/api/archives/LMHHub.scala | 29 +++++++++++++++---- .../info/kwarc/mmt/api/archives/MathHub.scala | 2 +- 8 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala index 789898270f..568e0319d6 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/ArchiveBuilder.scala @@ -13,7 +13,7 @@ trait ArchiveBuilder { this: Builder => /** tries to find an archive with a given id */ protected def tryArchive(id: String) : Option[LMHHubArchiveEntry] = { logDebug(s"trying $id as archive") - val optEntry = mathHub.entries_.collectFirst({ + val optEntry = mathHub.installedEntries.collectFirst({ case e: LMHHubArchiveEntry if e.id == id => e }) @@ -67,7 +67,7 @@ trait ArchiveBuilder { this: Builder => Some(IArchive( ref.parent, ref.id, ref.name, - getStats(ref.id), + getStats(entry.statistics), ref.title, ref.teaser, tags, version, diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/Builder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/Builder.scala index ed6bf79662..10ee0c0a41 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/Builder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/Builder.scala @@ -1,6 +1,6 @@ package info.kwarc.mmt.mathhub.library.Context.Builders -import info.kwarc.mmt.api.archives.{Archive, MathHub} +import info.kwarc.mmt.api.archives.{Archive, MathHub, SimpleStatistics} import info.kwarc.mmt.api._ import info.kwarc.mmt.api.documents.Document import info.kwarc.mmt.api.frontend.{Controller, Logger} @@ -313,4 +313,7 @@ trait Getters { this: Builder => trait Statistics { this: Builder => // TODO: Build statistics in a cached form protected def getStats(path: String): Option[List[IStatistic]] = None + protected def getStats(stats: Option[SimpleStatistics]): Option[List[IStatistic]] = stats.map {s => + List(IStatistic("any_con", s.constantCount), IStatistic("theo", s.theoryCount)) + } } \ No newline at end of file diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/DocumentBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/DocumentBuilder.scala index e5e3c75b3f..5a81a513af 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/DocumentBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/DocumentBuilder.scala @@ -23,7 +23,7 @@ trait DocumentBuilder { this: Builder => private def makeDocumentRef(path: DPath): Option[IDocumentRef] = { // if we are the narrationBase of an archive, that is our parent - val parent = mathHub.entries_.find({ + val parent = mathHub.installedEntries.find({ case ae: LMHHubArchiveEntry => ae.archive.narrationBase.toString == path.toPath case _ => false }) match { diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala index ca8408159f..94cf26c282 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupBuilder.scala @@ -3,7 +3,7 @@ package info.kwarc.mmt.mathhub.library.Context.Builders import info.kwarc.mmt.api.archives.{LMHHubArchiveEntry, LMHHubGroupEntry} import info.kwarc.mmt.api.utils.File import info.kwarc.mmt.mathhub.library.Context.MathHubAPIContext -import info.kwarc.mmt.mathhub.library.{IGroup, IGroupRef} +import info.kwarc.mmt.mathhub.library.{IGroup, IGroupRef, IStatistic} /** a builder for groups */ trait GroupBuilder { this: Builder => @@ -12,7 +12,7 @@ trait GroupBuilder { this: Builder => protected def tryGroup(id: String) : Option[LMHHubGroupEntry] = { logDebug(s"trying $id as group") - val optEntry = mathHub.entries_.collectFirst({ + val optEntry = mathHub.installedEntries.collectFirst({ case e: LMHHubGroupEntry if e.group == id => e }) @@ -47,11 +47,11 @@ trait GroupBuilder { this: Builder => val description = entry.readLongDescription.getOrElse("No description provided") val responsible = entry.properties.getOrElse("responsible", "").split(",").map(_.trim).toList - val archives = mathHub.entries_.collect({case archive: LMHHubArchiveEntry if archive.group == ref.id => archive.id }) + val archives = entry.members.collect({case archive: LMHHubArchiveEntry => archive.id }) Some(IGroup( ref.id, ref.name, - getStats(ref.id), + getStats(entry.statistics), ref.title, ref.teaser, description, diff --git a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupsBuilder.scala b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupsBuilder.scala index 7b7a45b6f0..83c4fd3d54 100644 --- a/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupsBuilder.scala +++ b/src/mathhub-mmt/src/info/kwarc/mmt/mathhub/library/Context/Builders/GroupsBuilder.scala @@ -5,7 +5,7 @@ import info.kwarc.mmt.mathhub.library.IGroupRef trait GroupsBuilder { this: Builder => def getGroups() : List[IGroupRef] = { val mh = mathHub - mh.entries_ + mh.installedEntries .collect({case ae: mh.MathHubGroupEntry => ae.group }).distinct .flatMap(e => getGroupRef(e)) } diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala index adca17eeaf..7f2faf9ce7 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/Archive.scala @@ -138,13 +138,13 @@ class Archive(val root: File, val properties: mutable.Map[String, String], val r } /** Returns (#Theories,#Constants)**/ - def stats(implicit controller: Controller) = { + def stats(implicit controller: Controller): SimpleStatistics = { // val arch = controller.backend.getArchive(a).get val ths = allContent.flatMap{mp => Try(controller.get(mp).asInstanceOf[Theory]).toOption } val const = ths.flatMap(_.getConstants) - (ths.length,const.length) + SimpleStatistics(ths.length,const.length) } /** @@ -292,3 +292,13 @@ object Archive { /** returns a trivial TraverseMode */ def traverseIf(e: String): TraverseMode = TraverseMode(extensionIs(e), _ => true, parallel = false) } + +/** very simple statistics implementation */ +case class SimpleStatistics(theoryCount: Int, constantCount: Int) { + def + (other: SimpleStatistics): SimpleStatistics = { + SimpleStatistics(theoryCount + other.theoryCount, constantCount + other.constantCount) + } +} +object SimpleStatistics { + val empty: SimpleStatistics = SimpleStatistics(0, 0) +} \ No newline at end of file diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala index c57040f701..47c82b6687 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/LMHHub.scala @@ -15,16 +15,16 @@ abstract class LMHHub extends Logger { protected def report = controller.report /** find all locally installed entries */ - protected def entries_ : List[LMHHubEntry] + def installedEntries : List[LMHHubEntry] /** find all repositories given a specification */ - def entries(spec: String*): List[LMHHubEntry] = if (spec.nonEmpty) entries_.filter(e => spec.exists(e.matches)) else entries_ + def entries(spec: String*): List[LMHHubEntry] = if (spec.nonEmpty) installedEntries.filter(e => spec.exists(e.matches)) else installedEntries /** finds all archive entries available locally */ - def archiveEntries: List[LMHHubArchiveEntry] = entries_.collect({case a: LMHHubArchiveEntry => a}) + def archiveEntries: List[LMHHubArchiveEntry] = installedEntries.collect({case a: LMHHubArchiveEntry => a}) /** finds all group entries available locally */ - def groupEntries: List[LMHHubGroupEntry] = entries_.collect({case g: LMHHubGroupEntry => g}) + def groupEntries: List[LMHHubGroupEntry] = installedEntries.collect({case g: LMHHubGroupEntry => g}) /** finds all directory entries available locally */ - def dirEntries: List[LMHHubDirectoryEntry] = entries_.collect({case d: LMHHubDirectoryEntry => d}) + def dirEntries: List[LMHHubDirectoryEntry] = installedEntries.collect({case d: LMHHubDirectoryEntry => d}) /** checks if a group exists remotely */ def hasGroup(name: String): Boolean @@ -182,6 +182,7 @@ trait LMHHubDirectoryEntry extends LMHHubEntry { def load(): Unit = {} def properties: Map[String, String] = Map() + def statistics: Option[SimpleStatistics] = None // Read the properties from the manifest lazy val id: String = { @@ -215,6 +216,9 @@ trait LMHHubArchiveEntry extends LMHHubDirectoryEntry { /** reads the archive props */ override def properties: Map[String, String] = archive.properties.toMap + /** reads archive statistics */ + override def statistics: Option[SimpleStatistics] = Some(archive.stats(controller)) + /** the list of dependencies of this archive */ def dependencies: List[String] = { val string = properties.getOrElse("dependencies", "").replace(",", " ") @@ -250,6 +254,21 @@ trait LMHHubGroupEntry extends LMHHubDirectoryEntry { } } + /** finds all LMH Hub entries that are a member of this group */ + def members: List[LMHHubEntry] = { + hub.installedEntries.filter(c => c.group == group && c.id != id) + } + + /** collects statistics in this archive */ + override def statistics: Option[SimpleStatistics] = { + Some( + members + .collect({case de: LMHHubDirectoryEntry => de.statistics}) + .collect({ case Some(s: SimpleStatistics) => s}) + .fold(SimpleStatistics.empty)(_ + _) + ) + } + /** the group properties */ override def properties : Map[String, String] = { load() diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/MathHub.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/MathHub.scala index 7210e93e67..69caf9eee4 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/MathHub.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/MathHub.scala @@ -141,7 +141,7 @@ class MathHub(val controller: Controller, var local: File, var remote: URI, var } /** find all the archives known to the controller */ - def entries_ : List[MathHubEntry] = { + def installedEntries : List[MathHubEntry] = { val folders = new ListBuffer[File] debug("scanning for archives") From 29a5f0ac87684e900fc35c997a6e282a4271a19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=BCller?= Date: Fri, 5 Apr 2019 19:03:07 +0200 Subject: [PATCH 25/63] frameit fix --- src/frameit-mmt/src/FrameIT.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/frameit-mmt/src/FrameIT.scala b/src/frameit-mmt/src/FrameIT.scala index e167910264..1300911784 100644 --- a/src/frameit-mmt/src/FrameIT.scala +++ b/src/frameit-mmt/src/FrameIT.scala @@ -78,7 +78,6 @@ class FrameViewer extends Extension { class FrameitPlugin extends ServerExtension("frameit") with Logger with MMTTask { override val logPrefix = "frameit" - val test : Class[FrameViewer] = classOf[FrameViewer] lazy val fv = controller.extman.get(classOf[FrameViewer]).headOption.getOrElse { val a = new FrameViewer controller.extman.addExtension(a) @@ -199,7 +198,7 @@ class FrameitPlugin extends ServerExtension("frameit") with Logger with MMTTask controller.extman.addExtension(ret) ret } - implicit val ce : CheckingEnvironment = new CheckingEnvironment(controller.simplifier,ErrorThrower,RelationHandler.ignore, this) + implicit lazy val ce : CheckingEnvironment = new CheckingEnvironment(controller.simplifier,ErrorThrower,RelationHandler.ignore, this) val dpath = DPath(URI.http colon "cds.omdoc.org") / "FrameIT" val sitpath = dpath ? "situation_theory" From 340c40882533285887a06117645d0130721c8b6f Mon Sep 17 00:00:00 2001 From: Colin Rothgang Date: Sun, 7 Apr 2019 13:27:03 +0200 Subject: [PATCH 26/63] Adapted implementation of inductive definitions for code reuse in reflections. --- .../mmt/api/symbols/DerivedDeclaration.scala | 8 +++-- .../InductiveDefinitions.scala | 2 +- .../structuralfeatures/InductiveMatch.scala | 2 +- .../InductiveProofDefinitions.scala | 2 +- .../structuralfeatures/InductiveTypes.scala | 35 ++++++++++--------- .../InternalDeclaration.scala | 2 +- .../mmt/lf/structuralfeatures/Quotients.scala | 2 +- .../RecordDefinitions.scala | 2 +- .../mmt/lf/structuralfeatures/Records.scala | 2 +- .../StructuralFeaturesUtil.scala | 4 +-- .../mmt/lf/structuralfeatures/Subtypes.scala | 2 +- 11 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/symbols/DerivedDeclaration.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/symbols/DerivedDeclaration.scala index 13c5d39ed1..588bb528f6 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/symbols/DerivedDeclaration.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/symbols/DerivedDeclaration.scala @@ -688,14 +688,16 @@ class BoundTheoryParameters(id : String, pi : GlobalName, lambda : GlobalName, a object StructuralFeatureUtil { - def singleExternalDeclaration(d: Constant) = { + def externalDeclarationsToElaboration(decls: List[Constant]) = { new Elaboration { - val elabDecls = List(d) + val elabDecls = decls def domain = elabDecls map {d => d.name} def getO(n: LocalName) = { elabDecls.find(_.name == n) } } } - + def singleExternalDeclaration(d: Constant) = { + externalDeclarationsToElaboration(List(d)) + } } diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveDefinitions.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveDefinitions.scala index 4849f58444..f6b68c1b8b 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveDefinitions.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveDefinitions.scala @@ -11,7 +11,7 @@ import frontend.Controller import info.kwarc.mmt.lf._ import InternalDeclaration._ import InternalDeclarationUtil._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import TermConstructingFeatureUtil._ import inductiveUtil._ diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveMatch.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveMatch.scala index 21bffd496e..6fddea24e3 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveMatch.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveMatch.scala @@ -13,7 +13,7 @@ import InternalDeclaration._ import InternalDeclarationUtil._ import TermConstructingFeatureUtil._ import inductiveUtil._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ /** theories as a set of types of expressions */ class InductiveMatch extends StructuralFeature("match") with TypedParametricTheoryLike { diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveProofDefinitions.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveProofDefinitions.scala index 0794edb86c..3d6a848913 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveProofDefinitions.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveProofDefinitions.scala @@ -12,7 +12,7 @@ import info.kwarc.mmt.lf._ import InternalDeclaration._ import InternalDeclarationUtil._ import TermConstructingFeatureUtil._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import inductiveUtil._ /** theories as a set of types of expressions */ diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala index bab5aeef28..b08dec0d32 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala @@ -11,6 +11,7 @@ import frontend.Controller import info.kwarc.mmt.lf._ import InternalDeclaration._ import InternalDeclarationUtil._ +import StructuralFeatureUtils._ import StructuralFeatureUtil._ object inductiveUtil { @@ -38,7 +39,6 @@ class InductiveTypes extends StructuralFeature("inductive") with ParametricTheor * @param dd the derived declaration from which the inductive type(s) are to be constructed */ override def check(dd: DerivedDeclaration)(implicit env: ExtendedCheckingEnvironment) {} - /** * Elaborates an declaration of one or multiple mutual inductive types into their declaration, @@ -50,10 +50,20 @@ class InductiveTypes extends StructuralFeature("inductive") with ParametricTheor def elaborate(parent: ModuleOrLink, dd: DerivedDeclaration) = { val context = Type.getParameters(dd) implicit val parentTerm = dd.path + + val decls = parseInternalDeclarations(dd, controller, Some(context)) + elaborateDeclarations(context, decls) + } + + /** Elaborates an derived declaration D using the inductive feature. This is used to reuse the functionality of this feature in different features, speciafically the reflection feature. + * @param context The context of D + * @param parentTerm The path to D, used as prefix for the external declarations + * @param decls The internal declaration of the D + */ + def elaborateDeclarations(context: Context, decls: List[InternalDeclaration])(implicit parentTerm: GlobalName) : Elaboration = { // to hold the result var elabDecls : List[Constant] = Nil - val decls = parseInternalDeclarations(dd, controller, Some(context)) val tpdecls = tpls(decls) val tmdecls = tmls(decls) val constrdecls = constrs(tmdecls) @@ -69,29 +79,22 @@ class InductiveTypes extends StructuralFeature("inductive") with ParametricTheor * some of the (in)equality axioms would be ill-typed because * the type system already forces elements of different instances of a dependent type to be unequal and of unequal type */ - elabDecls = elabDecls.reverse ::: tmdecls.flatMap(x => noConf(x, tmdecls, types)(dd.path)) + elabDecls = elabDecls.reverse ::: tmdecls.flatMap(x => noConf(x, tmdecls, types)(parentTerm)) // the no junk axioms - elabDecls ++= noJunks(decls, context)(dd.path) + elabDecls ++= noJunks(decls, context)(parentTerm) // the testers - elabDecls ++= testers(tmdecls, tpdecls, decls, context)(dd.path) + elabDecls ++= testers(tmdecls, tpdecls, decls, context)(parentTerm) // the unappliers - elabDecls ++= unappliers(constrdecls, tpdecls, decls, context)(dd.path) + elabDecls ++= unappliers(constrdecls, tpdecls, decls, context)(parentTerm) // the inductive proof declarations - elabDecls ++= indProofs(tpdecls, constrdecls, context)(dd.path) + elabDecls ++= indProofs(tpdecls, constrdecls, context)(parentTerm) - //elabDecls foreach {d =>log(defaultPresenter(d)(controller))} //This typically prints all external declarations several times - new Elaboration { - def domain = elabDecls map {d => d.name} - def getO(n: LocalName) = { - elabDecls.find(_.name == n) foreach(c => log(defaultPresenter(c)(controller))) - elabDecls.find(_.name == n) - } - } - } + externalDeclarationsToElaboration(elabDecls) +} /** Check whether the TermLevel has a higher order argument of an inductively defined type * In that case an error is thrown diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InternalDeclaration.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InternalDeclaration.scala index c658a5fce5..7a51622672 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InternalDeclaration.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InternalDeclaration.scala @@ -188,7 +188,7 @@ object InternalDeclaration { } } -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ /** helper class for the various declarations in an inductive type */ sealed abstract class InternalDeclaration { diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Quotients.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Quotients.scala index e7554256fd..12120aefd1 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Quotients.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Quotients.scala @@ -9,7 +9,7 @@ import modules._ import frontend.Controller import info.kwarc.mmt.lf._ import InternalDeclaration._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import InternalDeclarationUtil._ @deprecated("this is experimental and may still be removed", "") diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/RecordDefinitions.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/RecordDefinitions.scala index 1cb3cdfa7d..01c0dbd029 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/RecordDefinitions.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/RecordDefinitions.scala @@ -14,7 +14,7 @@ import InternalDeclarationUtil._ import RecordUtil._ import symbols.StructuralFeatureUtil._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import TermConstructingFeatureUtil._ diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Records.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Records.scala index d9a3b8bf96..a473b35ab6 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Records.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Records.scala @@ -11,7 +11,7 @@ import frontend.Controller import info.kwarc.mmt.lf._ import InternalDeclaration._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import InternalDeclarationUtil._ object RecordUtil { diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/StructuralFeaturesUtil.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/StructuralFeaturesUtil.scala index 8fb388bb0c..c696433420 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/StructuralFeaturesUtil.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/StructuralFeaturesUtil.scala @@ -12,7 +12,7 @@ import info.kwarc.mmt.lf._ import InternalDeclarationUtil._ import InternalDeclaration._ -object StructuralFeatureUtil { +object StructuralFeatureUtils { val theory: MPath = LF._base ? "DHOL" object Ded { val path = LF._base ? "Ded" ? "DED" @@ -83,7 +83,7 @@ object StructuralFeatureUtil { } } -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ object TermConstructingFeatureUtil { def correspondingDecl(dd: DerivedDeclaration, d: LocalName): Option[Constant] = { dd.getO(d) map { diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Subtypes.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Subtypes.scala index 3948e87227..8ce04e553e 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Subtypes.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Subtypes.scala @@ -9,7 +9,7 @@ import modules._ import frontend.Controller import info.kwarc.mmt.lf._ import InternalDeclaration._ -import StructuralFeatureUtil._ +import StructuralFeatureUtils._ import InternalDeclarationUtil._ @deprecated("this is experimental and may still be removed", "") From 4e1683492aceadaec4cdc988ebd4ed73d12ce734 Mon Sep 17 00:00:00 2001 From: Colin Rothgang Date: Sun, 7 Apr 2019 14:07:47 +0200 Subject: [PATCH 27/63] Added structural feature for reflections of theories. --- .../structuralfeatures/InductiveTypes.scala | 11 ++++ .../lf/structuralfeatures/Reflections.scala | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Reflections.scala diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala index b08dec0d32..495f372767 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/InductiveTypes.scala @@ -402,4 +402,15 @@ class InductiveTypes extends StructuralFeature("inductive") with ParametricTheor } } +object InductiveTypes { + /** Elaborates an derived declaration D using the inductive feature. This is used to reuse the functionality of this feature in different features, speciafically the reflection feature. + * @param context The context of D + * @param parentTerm The path to D, used as prefix for the external declarations + * @param decls The internal declaration of the D + */ + def elaborateDeclarations(context: Context, decls: List[InternalDeclaration])(implicit parentTerm: GlobalName) : Elaboration = { + InductiveTypes.elaborateDeclarations(context, decls) + } +} + object InductiveRule extends StructuralFeatureRule(classOf[InductiveTypes], "inductive") \ No newline at end of file diff --git a/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Reflections.scala b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Reflections.scala new file mode 100644 index 0000000000..4396bc798b --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/lf/structuralfeatures/Reflections.scala @@ -0,0 +1,50 @@ +package info.kwarc.mmt.lf.structuralfeatures + +import info.kwarc.mmt.api._ +import objects._ +import symbols._ +import notations._ +import checking._ +import modules._ +import frontend.Controller + +import info.kwarc.mmt.lf._ +import InternalDeclaration._ +import InternalDeclarationUtil._ +import StructuralFeatureUtils._ +import StructuralFeatureUtil._ +import inductiveUtil._ +import structuralfeatures.InductiveTypes._ + +/** theories as a set of types of expressions */ +class Reflections extends StructuralFeature("reflect") with TypedParametricTheoryLike { + + /** + * Checks the validity of the inductive type(s) to be constructed + * @param dd the derived declaration from which the inductive type(s) are to be constructed + */ + override def check(dd: DerivedDeclaration)(implicit env: ExtendedCheckingEnvironment) {} + + /** + * Elaborates an declaration of one or multiple mutual inductive types into their declaration, + * as well as the corresponding no confusion and no junk axioms + * Constructs a structure whose models are exactly the (not necessarily initial) models of the declared inductive types + * @param parent The parent module of the declared inductive types + * @param dd the derived declaration to be elaborated + */ + def elaborate(parent: ModuleOrLink, dd: DerivedDeclaration) = { + val (indDefPath, context, indParams) = ParamType.getParams(dd) + val (indD, indCtx) = controller.library.get(indDefPath) match { + case indD: DerivedDeclaration if (indD.feature == "inductive") => (indD, Type.getParameters(indD)) + case d: DerivedDeclaration => throw LocalError("the referenced derived declaration is not of the feature inductive but of the feature "+d.feature+".") + case _ => throw LocalError("Expected definition of corresponding inductively-defined types at "+indDefPath.toString() + +" but no derived declaration found at that location.") + } + val decls = parseInternalDeclarations(indD, controller, Some(context))(dd.path) + + + structuralfeatures.InductiveTypes.elaborateDeclarations(context, decls)(dd.path) + } +} + +object ReflectionRule extends StructuralFeatureRule(classOf[Reflections], "reflect") \ No newline at end of file From a3a09b1149b0b756e8fd91a72a79d16f787057cd Mon Sep 17 00:00:00 2001 From: rappatoni Date: Wed, 10 Apr 2019 13:45:20 +0200 Subject: [PATCH 28/63] Refactoring only --- .../mmt/api/web/JSONBasedGraphServer.scala | 71 ++----------------- 1 file changed, 5 insertions(+), 66 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 969c14a4e6..5350df0cc0 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -33,22 +33,15 @@ class DirectGraphBuilder extends Extension{ } - def apply(uri: String, key: String, sem: String, comp: String): JSON = { - /* val uri = WebQuery(query)("uri").getOrElse(return JSONString("Not a URI")) - val key = WebQuery(query)("key").getOrElse("pgraph") */ + def apply(uri: String, key: String, sem: String = "none", comp: String = "default solver"): JSON = { val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { throw CatchError(s"exporter $key not available") } - if (sem == "none") { log("Computing " + key + " for " + uri + "... ") val ret = exp.buildGraph(uri) log("Done") ret - } else {log("Got here and computing " + key + " for " + uri + "with" + sem + "semantic" + "using" + comp + "...") - val ret = exp.computeSem(exp.buildGraph(uri), sem, comp) - log("Done") - ret } -}} +} class JSONBasedGraphServer extends ServerExtension("jgraph") { @@ -58,54 +51,15 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { override def start(args: List[String]) { controller.extman.addExtension(new JGraphSideBar) controller.extman.addExtension(new DirectGraphBuilder) - /** controller.extman.addExtension(new JDocgraph) - controller.extman.addExtension(new JThgraph) - controller.extman.addExtension(new JPgraph) - controller.extman.addExtension(new JArchiveGraph) - controller.extman.addExtension(new JMPDGraph) - super.start(args) **/ } lazy val sidebar = controller.extman.get(classOf[JGraphSideBar]).head lazy val buil = controller.extman.get(classOf[DirectGraphBuilder]).head - /** def apply(request: ServerRequest): ServerResponse = { - println(request) - log("Paths: " + request.pathForExtension) - log("Query: " + request.query) - log("Path: " + request.parsedQuery("uri")) - log("Semantic: " + request.parsedQuery("semantic")) - if (request.pathForExtension.headOption == Some("menu")) { - val id = request.parsedQuery("id").getOrElse("top") - log("Returning menu for " + id) - if (id == "full") ServerResponse.fromJSON(sidebar.getJSON("top",true)) - else ServerResponse.fromJSON(sidebar.getJSON(id)) - } else if (request.pathForExtension.headOption == Some("json")) { - val uri = request.parsedQuery("uri").getOrElse(return ServerResponse.errorResponse(GetError("Not a URI"), "json")) - val key = request.parsedQuery("key").getOrElse("pgraph") - val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { - throw CatchError(s"exporter $key not available") - } - val sem = request.parsedQuery("semantic").getOrElse("none") - if (sem == "none") { - log("Computing " + key + " for " + uri + "... ") - val ret = ServerResponse.fromJSON(exp.buildGraph(uri)) - log("Done") - ret - } else {log("Got here and Computing " + key + " for " + uri + "with" + sem + "semantic" + "... ") - val comp = request.parsedQuery("computer").getOrElse(return ServerResponse.errorResponse(GetError("No solver specified"), "json")) - val ret = ServerResponse.fromJSON(exp.computeSem(exp.buildGraph(uri), sem, comp)) - log("Done") - ret } - - } else ServerResponse.errorResponse("Invalid path", "json") - } **/ - def apply(request:ServerRequest): ServerResponse = { log("Paths: " + request.pathForExtension) log("Query: " + request.query) log("Path: " + request.parsedQuery("uri")) - log("Semantic: " + request.parsedQuery("semantic")) if (request.pathForExtension.headOption == Some("menu")) { val id = request.parsedQuery("id").getOrElse("top") log("Returning menu for " + id) @@ -114,9 +68,7 @@ class JSONBasedGraphServer extends ServerExtension("jgraph") { } else if (request.pathForExtension.headOption == Some("json")) { val uri = request.parsedQuery("uri").getOrElse(return ServerResponse.errorResponse(GetError("Not a URI"), "json")) val key = request.parsedQuery("key").getOrElse("pgraph") - val sem = request.parsedQuery("semantic").getOrElse("none") - val comp = request.parsedQuery("comp").getOrElse("default solver") - val graph = buil(uri, key , sem , comp) + val graph = buil(uri, key) val ret = ServerResponse.fromJSON(graph) ret } else ServerResponse.errorResponse("Invalid path", "json") @@ -212,13 +164,12 @@ class JGraphSideBar extends Extension { } } -abstract class JGraphExporter(val key : String, val semantic : String = "none", val computer : String = "best") extends FormatBasedExtension { +abstract class JGraphExporter(val key : String) extends FormatBasedExtension { def isApplicable (format: String): Boolean = format == key def buildGraph(s : String) : JSON - def computeSem(f: JSON, sem: String, comp: String) : JSON } -abstract class SimpleJGraphExporter(key : String, semantic : String = "none", computer : String = "best") extends JGraphExporter(key,semantic) { +abstract class SimpleJGraphExporter(key : String) extends JGraphExporter(key) { override def logPrefix: String = key val builder : JGraphBuilder val selector : JGraphSelector @@ -229,12 +180,6 @@ abstract class SimpleJGraphExporter(key : String, semantic : String = "none", co log("Done.") res } - - def computeSem(f: JSON, sem: String, comp: String = "best"): JSON = { - val semcomp = new SemanticComputer(f, sem, comp) - val ret = semcomp.TgfToJson(semcomp.CallComputer(semcomp.JsonToTgf(f), sem, comp)) - ret - } } @@ -592,9 +537,3 @@ object GraphBuilder { } } } - -class SemanticComputer (val f: JSON, val sem: String, val computer: String = "best") { - def JsonToTgf (f: JSON) : String ="test" - def TgfToJson (tgf: String) : JSON = null - def CallComputer (tgf: String, semantic: String, computer: String) : String ="test" -} From 26535bc1b3255bbe180040f90d9c85088cb5fceb Mon Sep 17 00:00:00 2001 From: rappatoni Date: Wed, 10 Apr 2019 14:16:19 +0200 Subject: [PATCH 29/63] Clean Up --- .gitignore | 2 + .../mmt/api/web/JSONBasedGraphServer.scala | 14 +-- src/test/Translator.scala | 91 +------------------ 3 files changed, 10 insertions(+), 97 deletions(-) diff --git a/.gitignore b/.gitignore index ac5e28967f..9f180d8688 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ scripts/travis/deploy_key # IntelliJ Failes .idea .idea_modules +*.iml + # Eclipse Files bin diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala index 5350df0cc0..470cb37781 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/JSONBasedGraphServer.scala @@ -19,9 +19,9 @@ import scala.util.Try * Created by jazzpirate on 07.06.17. */ -class DirectGraphBuilder extends Extension{ +class DirectGraphBuilder extends Extension { - private case class CatchError(s : String) extends Throwable + private case class CatchError(s: String) extends Throwable override def start(args: List[String]) { controller.extman.addExtension(new JDocgraph) @@ -37,13 +37,13 @@ class DirectGraphBuilder extends Extension{ val exp = controller.extman.getOrAddExtension(classOf[JGraphExporter], key).getOrElse { throw CatchError(s"exporter $key not available") } - log("Computing " + key + " for " + uri + "... ") - val ret = exp.buildGraph(uri) - log("Done") - ret + log("Computing " + key + " for " + uri + "... ") + val ret = exp.buildGraph(uri) + log("Done") + ret + } } - class JSONBasedGraphServer extends ServerExtension("jgraph") { override val logPrefix = "jgraph" private case class CatchError(s : String) extends Throwable diff --git a/src/test/Translator.scala b/src/test/Translator.scala index cfd9d19a76..e3e753da6d 100644 --- a/src/test/Translator.scala +++ b/src/test/Translator.scala @@ -20,102 +20,13 @@ import info.kwarc.mmt.api.presentation.{HTMLPresenter, MMTDocExporter, MathMLPre import info.kwarc.mmt.api.symbols._ import info.kwarc.mmt.api.utils._ -/** -object Tester extends App { - val filetest = File("C:")/"mmt2" - if (filetest.exists) - { - print("exists") - } - else {throw GeneralError("Does not exist")} -} - -object Translator extends MagicTest{ - def run : Unit = { - val a = controller.backend.getArchive( "Happening") - //println(controller.backend.getArchives.map(_.id).mkString("; ")) - println(a) - val pres = new MMTSyntaxPresenter() - controller.extman.addExtension(pres) - pres.build(a.get, Update(Level.Warning), FilePath("/")) - } -} - -object Translator2 extends MagicTest{ - import info.kwarc.mmt.api.presentation._ - import info.kwarc.mmt.api.archives._ - def run : Unit = { - val a = controller.backend.getArchive("MMT/LATIN").get - val pres = new MMTSyntaxPresenter() - controller.extman.addExtension(pres) - // pres.build(a.get,Update(Level.Warning),FilePath("")) - controller.buildArchive(List(a.id), "present-text-notations", Build, FilePath("")) - } -} - -object MyReader extends MagicTest{ - def run : Unit = { - val a = controller.backend.getArchive( "MitM/algebra") - val read =new RelationalReader() - a map read.oncePerArchive - } -} - -object MyReader2 extends MagicTest{ - def run : Unit = { - val a = controller.backend.getArchive( "MitM/Foundation") - val mygraph = a.get.allContent - println{mygraph} - } -} - - - -// object testserver extends Server (8080, ) {} - -class SemanticComputer { - /** Takes an MMT archive and computes argumentation semantics on it. - * - */ - def main: Unit = { - - } - - def grounded: Unit = { - - } - -} - -/** - * - */ - * - **/ object Graphtester extends MagicTest("jgraph") { def run : Unit = { - val test = WebQuery("type=archivegraph&graphdata=MMT/urtheories&semantic=grounded") + //val test = WebQuery("type=archivegraph&graphdata=MMT/urtheories&semantic=grounded") // println(test) //println( test("graphdata")) //val serve = new JSONBasedGraphServer() - - - } - - } -/** -object Jsonprinter extends MagicTest { - def run : Unit = { - val server = new JSONBasedGraphServer - server(ServerRequest("GET", Map(("uri", "MMT/urtheories"),("key", "archivegraph"),("id", "full")), Session, ("http://localhost:8080/graphs/tgview.html?type=archivegraph&graphdata=MMT/urtheories&highlight=null"),"?type=archivegraph&graphdata=MMT/urtheories&highlight=null", Body )) - } -}**/ -class SemanticComputer (f: JSON, sem: String, computer: String = "best") { - def JsonToTgf (f: JSON) : String ="test" - def TgfToJson (tgf: String) : JSON = null - def CallComputer (tgf: String, computer: String) : String ="test" -} From 74567dfa93d0bf89424a3cb80b98009a259de4b1 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Wed, 10 Apr 2019 17:36:15 +0200 Subject: [PATCH 30/63] Revert "some progress" This reverts commit 4b7943999c9fac3085a42e62a5c412bb31647742. --- .../kwarc/mmt/api/archives/BuildTarget.scala | 53 ++++-------------- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 56 +++++++++---------- 2 files changed, 38 insertions(+), 71 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index 24918cd620..9f8efa3ec5 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -7,8 +7,6 @@ import Level.Level import frontend._ import utils._ -import java.util.Date - case class TestModifiers(compareWithTest: Boolean = false, addTest: Boolean = false, updateTest: Boolean = false) { def makeTests: Boolean = compareWithTest || addTest || updateTest } @@ -20,12 +18,12 @@ sealed abstract class BuildTargetModifier { /** default modifier: build the target */ case class Build(update: Update) extends BuildTargetModifier { - def toString(dim: String) : String = dim + def toString(dim: String) = dim } /** don't run, just delete all output files */ case object Clean extends BuildTargetModifier { - def toString(dim: String) : String = "-" + dim + def toString(dim: String) = "-" + dim } /** incremental build: skip this build if it nothing has changed */ @@ -36,7 +34,7 @@ case class Update(errorLevel: Level, dryRun: Boolean = false, testOpts: TestModi if (errorLevel <= Level.Force) "" else if (errorLevel < Level.Ignore) "!" else "*" - def toString(dim: String) : String = dim + key + def toString(dim: String) = dim + key // use dependency level for dependencies def forDependencies: Update = dependencyLevel match { @@ -52,7 +50,7 @@ case class Update(errorLevel: Level, dryRun: Boolean = false, testOpts: TestModi @MMT_TODO("needs review") //TODO this is only needed if called on the shell; check if any user actually calls it (presumably at most stex building, possibly in mathhub) case class BuildDepsFirst(update: Update) extends BuildTargetModifier { - def toString(dim: String) : String = dim + "&" + def toString(dim: String) = dim + "&" } /** forces building independent of status */ @@ -193,7 +191,7 @@ abstract class BuildTarget extends FormatBasedExtension { /** a string identifying this build target, used for parsing commands, logging, error messages */ def key: String - override def toString : String = super.toString + " with key " + key + override def toString = super.toString + s" with key $key" def isApplicable(format: String): Boolean = format == key @@ -270,9 +268,9 @@ class BuildTask(val key: String, val archive: Archive, val inFile: File, val chi /** the DPath corresponding to the inFile if inFile is in a narration-structured dimension */ def narrationDPath: DPath = DPath(base / inPath.segments) - def isDir: Boolean = children.isDefined + def isDir = children.isDefined - def isEmptyDir: Boolean = children.isDefined && children.get.isEmpty + def isEmptyDir = children.isDefined && children.get.isEmpty /** the name of the folder if inFile is a folder */ def dirName: String = outFile.toFilePath.dirPath.name @@ -462,31 +460,6 @@ abstract class TraversingBuildTarget extends BuildTarget { val outPath = bt.outPath val Update(errLev, dryRun, testMod, _) = up val rn = rebuildNeeded(deps, bt, errLev) - - /* - def exshow(s : File): String = if (s.exists()) { - val d = new Date(s.lastModified()) - "E (" + d.toString + ")" - } else "X" - - if (outPath.toString.contains("open-content.sms")) { - - var info : String = "" - - val i = bt.inFile - val e = bt.asDependency.getErrorFile(controller) - val foo = Modification(i,e) - - info += "MARKER" - if (errLev <= Level.Force) { info += "[forced]" ; assert(rn) } else { info += "[not forced]" } - info += "(rn=" + rn + ") StrictM: " + foo.toString - info += " (" + exshow(i) + "," + exshow(e) + ") // " - info += bt.inFile.toFilePath.toString - - println(info) - } - */ - if (!rn) { logResult("up-to-date " + outPath) } else if (dryRun) { @@ -508,13 +481,13 @@ abstract class TraversingBuildTarget extends BuildTarget { private def rebuildNeeded(deps: Set[Dependency], bt: BuildTask, level: Level): Boolean = { val errorFile = bt.asDependency.getErrorFile(controller) val errs = hadErrors(errorFile, level) - val mod = strictModified(bt.inFile, errorFile) + val mod = modified(bt.inFile, errorFile) level <= Level.Force || mod || errs || deps.exists { case bd: BuildDependency => val errFile = bd.getErrorFile(controller) - strictModified(errFile, errorFile) - case PhysicalDependency(fFile) => strictModified(fFile, errorFile) + modified(errFile, errorFile) + case PhysicalDependency(fFile) => modified(fFile, errorFile) case _ => false // for now } || bt.isDir && bt.children.getOrElse(Nil).exists { bf => modified(bf.asDependency.getErrorFile(controller), errorFile) @@ -638,12 +611,6 @@ abstract class TraversingBuildTarget extends BuildTarget { mod == Modified || mod == Added } - /** Stricter version of modified that also return true on "Deleted" */ - private def strictModified(inFile : File, errorFile : File) : Boolean = { - val mod = Modification(inFile, errorFile) - mod != Unmodified - } - /** @return status of input file, obtained by comparing to error file */ private def hadErrors(errorFile: File, errorLevel: Level): Boolean = if (errorLevel > Level.Fatal) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 5771da5d10..1c621d0a1f 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -80,6 +80,22 @@ trait STeXAnalysis { os.toList.map(s => PhysicalDependency(p.setExtension(s))) } + case useMhModule(r,b) => { + println("\nuseMhModule mit r = " + r + " und b = " + b) + + val argm : Map[String,String] = getArgMap(r) + + val archiveS : String = argm.getOrElse("repos", archString(archive)) + + assert(getArgMap(r).get("path").isDefined) + val pathS : FilePath = FilePath(getArgMap(r)("path")) + + val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") + println("useMhModule nu_dep: " + nu_dep) + + List(nu_dep) + } + case importOrUseModule(r) => getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList @@ -180,53 +196,37 @@ trait STeXAnalysis { structure } - def matchSmsEntry(archive: Archive, line: String) : List[STeXStructure] = { + def matchSmsEntry(a: Archive, line: String) : List[STeXStructure] = { line match { case importMhModule(r, b) => //println("smsEntry: a = " + a.id + " line = " + line) - val foo = createMhImport(archive, r, b) + val foo = createMhImport(a, r, b) //println("foo (deps) = " + foo.head.deps.toString()) foo - case useMhModule(r,b) => { - //println("\nuseMhModule mit r = " + r + " und b = " + b) - - val argm : Map[String,String] = getArgMap(r) - - val archiveS : String = argm.getOrElse("repos", archString(archive)) - - assert(getArgMap(r).get("path").isDefined) - val pathS : FilePath = FilePath(getArgMap(r)("path")) - - val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") - //println("useMhModule nu_dep: " + nu_dep) - //println("useMhModule archive: " + archive.id + " // line: " + line) - - List(STeXStructure(List(line), List(nu_dep))) - } case gimport(_, r, p) => - List(createGImport(archive, r, p)) + List(createGImport(a, r, p)) case smsGStruct(_, r, _, p) => - List(createGImport(archive, r, p)) + List(createGImport(a, r, p)) case smsMhStruct(r, _, p) => - createMhImport(archive, r, p) + createMhImport(a, r, p) case smsSStruct(r, _, p) => List(createImport(r, p)) case smsViewsig(r, _, f, t) => val m = getArgMap(r) - val fr = m.getOrElse("fromrepos", archString(archive)) - val tr = m.getOrElse("torepos", archString(archive)) - List(mkGImport(archive, fr, f), mkGImport(archive, tr, t)) + val fr = m.getOrElse("fromrepos", archString(a)) + val tr = m.getOrElse("torepos", archString(a)) + List(mkGImport(a, fr, f), mkGImport(a, tr, t)) case smsViewnl(_, r, p) => - List(createGImport(archive, archString(archive), p)) + List(createGImport(a, archString(a), p)) case smsMhView(r, _, f, t) => val m = getArgMap(r) var ofp = m.get("frompath") var otp = m.get("topath") - val fr = m.getOrElse("fromrepos", archString(archive)) - val tr = m.getOrElse("torepos", archString(archive)) + val fr = m.getOrElse("fromrepos", archString(a)) + val tr = m.getOrElse("torepos", archString(a)) (ofp, otp) match { case (Some(fp), Some(tp)) => - List(mkMhImport(archive, fr, fp, f), mkMhImport(archive, tr, tp, t)) + List(mkMhImport(a, fr, fp, f), mkMhImport(a, tr, tp, t)) case _ => Nil } case smsView(r, f, t) => From 8fb468c51f11c4a95824dfe2caa5b6bdbcca9097 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Wed, 10 Apr 2019 17:53:54 +0200 Subject: [PATCH 31/63] Revert "bunch of refactorings" This reverts commit 45df57a1fa6bae4ba07ae087ff7e4bbb202d4fea. --- .../kwarc/mmt/api/archives/BuildQueue.scala | 2 +- .../kwarc/mmt/api/archives/BuildTarget.scala | 12 +-- .../kwarc/mmt/stex/LaTeXBuildTarget.scala | 73 ++++++-------- .../src/info/kwarc/mmt/stex/LaTeXML.scala | 96 ++++++++---------- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 99 +++++++------------ .../src/info/kwarc/mmt/stex/STeXUtils.scala | 63 +++++------- 6 files changed, 139 insertions(+), 206 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala index 9adbe82b9f..f6641c87dd 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildQueue.scala @@ -18,7 +18,7 @@ class QueuedTask(val target: TraversingBuildTarget, estRes: BuildResult, val tas var lowPriority: Boolean = true /** task should be queued at beginning */ - def highPriority: Boolean = !lowPriority + def highPriority = !lowPriority /** task was not requested directly but added as dependency of some other task */ // TODO make this part of constructor to avoid having a var? diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index 9f8efa3ec5..a1aaeb5cb3 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -254,7 +254,7 @@ class BuildTask(val key: String, val archive: Archive, val inFile: File, val chi /** build targets should set this to true if they skipped the file so that it is not passed on to the parent directory */ var skipped = false /** the narration-base of the containing archive */ - val base : URI = archive.narrationBase + val base = archive.narrationBase /** the MPath corresponding to the inFile if inFile is a file in a content-structured dimension */ def contentMPath: MPath = Archive.ContentPathToMMTPath(inPath) @@ -355,21 +355,21 @@ abstract class TraversingBuildTarget extends BuildTarget { /// ***************** auxiliary methods for computing paths to output/error files etc. - protected def getOutFile(a: Archive, inPath: FilePath): File = (a / outDim / inPath).setExtension(outExt) + protected def getOutFile(a: Archive, inPath: FilePath) = (a / outDim / inPath).setExtension(outExt) - protected def getFolderOutFile(a: Archive, inPath: FilePath) : File = getOutFile(a, inPath / folderName) + protected def getFolderOutFile(a: Archive, inPath: FilePath) = getOutFile(a, inPath / folderName) protected def getErrorFile(a: Archive, inPath: FilePath): File = FileBuildDependency(key, a, inPath).getErrorFile(controller) //TODO why is this method not like the others? //TODO why is this not protected? - def getFolderErrorFile(a: Archive, inPath: FilePath): File = a / errors / key / inPath / (folderName + ".err") + def getFolderErrorFile(a: Archive, inPath: FilePath) = a / errors / key / inPath / (folderName + ".err") @MMT_TODO("needs review") - protected def getTestOutFile(a: Archive, inPath: FilePath): File = + protected def getTestOutFile(a: Archive, inPath: FilePath) = (a / Dim("test", outDim.toString) / inPath).setExtension(outExt) - protected def getOutPath(a: Archive, outFile: File) : FilePath = outFile.toFilePath + protected def getOutPath(a: Archive, outFile: File) = outFile.toFilePath /** auxiliary method for logging results */ protected def logResult(s: String) { diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala index ffdb39b03c..8ad850e5aa 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala @@ -19,21 +19,19 @@ import scala.concurrent.duration._ import scala.sys.process.{ProcessBuilder, ProcessLogger} /** common code for sms, latexml und pdf generation */ -abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments -{ - val localpathsFile : String = "localpaths.tex" - val inDim : RedirectableDimension = source - var pipeOutput : Boolean = false - val pipeOutputOption : String = "pipe-worker-output" - +abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments { + val localpathsFile = "localpaths.tex" + val inDim = source + var pipeOutput: Boolean = false + val pipeOutputOption: String = "pipe-worker-output" /** timout in seconds */ - private val timeoutDefault : Int = 300 - protected var timeoutVal : Int = timeoutDefault - protected val timeoutOption : String = "timeout" - protected var nameOfExecutable : String = "" + private val timeoutDefault: Int = 300 + protected var timeoutVal: Int = timeoutDefault + protected val timeoutOption: String = "timeout" + protected var nameOfExecutable: String = "" protected case class LatexError(s: String, l: String) extends ExtensionError(key, s) { - override val extraMessage : String = l + override val extraMessage = l } protected def commonOpts: OptionDescrs = List( @@ -48,9 +46,9 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis override def start(args: List[String]) { anaStartArgs(args) pipeOutput = optionsMap.get(pipeOutputOption).isDefined - optionsMap.get(timeoutOption).foreach(v => timeoutVal = v.getIntVal) - optionsMap.get(key).foreach(v => nameOfExecutable = v.getStringVal) - optionsMap.get("execute").foreach { v => + optionsMap.get(timeoutOption).foreach { case v => timeoutVal = v.getIntVal } + optionsMap.get(key).foreach { case v => nameOfExecutable = v.getStringVal } + optionsMap.get("execute").foreach { case v => if (nameOfExecutable.isEmpty) nameOfExecutable = v.getStringVal else logError("executable already set by: --" + key + "=" + nameOfExecutable) } @@ -113,9 +111,8 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis /** to be implemented */ def reallyBuildFile(bt: BuildTask): BuildResult - def buildFile(bt: BuildTask): BuildResult = { - if (!skip(bt)) reallyBuildFile(bt) else BuildEmpty("file excluded by MANIFEST") - } + def buildFile(bt: BuildTask): BuildResult = if (!skip(bt)) reallyBuildFile(bt) + else BuildEmpty("file excluded by MANIFEST") protected def readingSource(a: Archive, in: File, amble: Option[File] = None): List[Dependency] = { val res = getDeps(a, in, Set(in), amble) @@ -139,28 +136,20 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis safe } - override def estimateResult(bt: BuildTask) : BuildSuccess = - { - val in = bt.inFile - val archive = bt.archive - - val ds: List[Dependency] = if (in.exists && in.isFile) { - - var dps: List[Dependency] = Nil - - if (!noAmble(in) || key != "sms") - { - val pre = getAmbleFile(preOrPost = "pre", bt) - val post = getAmbleFile(preOrPost = "post", bt) - - dps = List(pre, post).map(PhysicalDependency) ++ - readingSource(archive, in, Some(pre)) ++ - readingSource(archive, in, Some(post)) - } - - readingSource(archive, in) ++ dps - - } else if (in.isDirectory) { Nil } + override def estimateResult(bt: BuildTask): BuildSuccess = { + val in = bt.inFile + val a = bt.archive + val ds = if (in.exists && in.isFile) { + readingSource(a, in) ++ + (if (noAmble(in) || key == "sms") Nil + else { + val pre = getAmbleFile("pre", bt) + val post = getAmbleFile("post", bt) + List(pre, post).map(PhysicalDependency) ++ + readingSource(a, in, Some(pre)) ++ + readingSource(a, in, Some(post)) + }) + } else if (in.isDirectory) Nil else { logResult("unknown file: " + in) logResult(" for: " + key) @@ -210,7 +199,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { val outDim: ArchiveDimension = source override val outExt = "tex" - override def getFolderOutFile(a: Archive, inPath: FilePath): File = a / outDim / inPath + override def getFolderOutFile(a: Archive, inPath: FilePath) = a / outDim / inPath // we do nothing for single files def reallyBuildFile(bt: BuildTask): BuildResult = BuildEmpty("nothing to do for files") @@ -262,7 +251,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { override def buildDepsFirst(a: Archive, up: Update, in: FilePath = EmptyPath) { a.traverse[Unit](inDim, in, TraverseMode(includeFile, includeDir, parallel))({ - _ => + case _ => }, { case (c@Current(inDir, inPath), _) => buildDir(a, inPath, inDir, force = false) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala index 17d3318110..4e4ac853a6 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala @@ -19,7 +19,7 @@ class AllPdf extends LaTeXDirTarget { BuildResult.empty } - override def estimateResult(bt: BuildTask): BuildSuccess = { + override def estimateResult(bt: BuildTask) = { if (bt.isDir) { val a = bt.archive val ls = getAllFiles(bt).map(f => FileBuildDependency("pdflatex", a, bt.inPath / f)) @@ -54,70 +54,56 @@ class AllTeX extends LaTeXDirTarget { } } - def buildDir(a: Archive, in: FilePath, dir: File, force: Boolean): BuildResult = - { + def buildDir(a: Archive, in: FilePath, dir: File, force: Boolean): BuildResult = { val dirFiles = getDirFiles(a, dir, includeFile) var success = false - - if (dirFiles.nonEmpty) - { + if (dirFiles.nonEmpty) { createLocalPaths(a, dir) - val deps : Map[Dependency, Set[Dependency]] = getDepsMap(getFilesRec(a, in)) - val ds : List[Dependency] = Relational.flatTopsort(controller, deps) - - val ts: List[File] = ds.collect { + val deps = getDepsMap(getFilesRec(a, in)) + val ds = Relational.flatTopsort(controller, deps) + val ts = ds.collect { case bd: FileBuildDependency if List(key, "tex-deps").contains(bd.key) => bd }.map(d => d.archive / inDim / d.inPath) - val files = ts.distinct.filter(dirFiles.map(f => dir / f).contains(_)).map(_.getName) assert(files.length == dirFiles.length) - - /* Find all languages present (by filename extension) */ - val langs : Set[String] = files.flatMap(f => getLang(File(f))).toSet + val langs = files.flatMap(f => getLang(File(f))).toSet val nonLangFiles = langFiles(None, files) - if (nonLangFiles.nonEmpty) { success = createAllFile(a, None, dir, nonLangFiles, force) } - + if (nonLangFiles.nonEmpty) success = createAllFile(a, None, dir, nonLangFiles, force) langs.toList.sorted.foreach { l => - val res : Boolean = createAllFile(a, Some(l), dir, files, force) + val res = createAllFile(a, Some(l), dir, files, force) success ||= res } } - if (success) BuildResult.empty // New all-file created - else BuildEmpty("up-to-date") // Empty or all-file already up-to-date + if (success) BuildResult.empty + else BuildEmpty("up-to-date") } private def ambleText(preOrPost: String, a: Archive, lang: Option[String]): List[String] = readSourceRebust(getAmbleFile(preOrPost, a, lang)).getLines().toList - /** Creates up-to-date all-File. Returns true if there was no all-file before, or it had to be updated. */ - private def createAllFile(a: Archive, lang: Option[String], dir: File, files: List[String], force: Boolean): Boolean = - { - val allFileName = dir / ("all" + lang.map("." + _).getOrElse("") + ".tex") - - val ls : List[String] = langFiles(lang, files) + /** return success */ + private def createAllFile(a: Archive, lang: Option[String], dir: File, + files: List[String], force: Boolean): Boolean = { + val all = dir / ("all" + lang.map("." + _).getOrElse("") + ".tex") + val ls = langFiles(lang, files) val w = new StringBuilder def writeln(s: String): Unit = w.append(s + "\n") - - /* Generate contents for up-to-date all-File */ - ambleText(preOrPost = "pre", a, lang).foreach(writeln) + ambleText("pre", a, lang).foreach(writeln) writeln("") - /* Handle each file of the given language */ ls.foreach { f => writeln("\\begin{center} \\LARGE File: \\url{" + f + "} \\end{center}") writeln("\\input{" + File(f).stripExtension + "} \\newpage") writeln("") } - ambleText(preOrPost = "post", a, lang).foreach(writeln) - - val newContent : String = w.result - val outPath : FilePath = getOutPath(a, allFileName) - - if (force || !allFileName.exists() || File.read(allFileName) != newContent) { - File.write(allFileName, newContent) + ambleText("post", a, lang).foreach(writeln) + val newContent = w.result + val outPath = getOutPath(a, all) + if (force || !all.exists() || File.read(all) != newContent) { + File.write(all, newContent) logSuccess(outPath) true } else { - logResult("up-to-Date " + outPath) // all-File existed and was already up-to-date + logResult("up-to-date " + outPath) false } } @@ -244,10 +230,10 @@ class LaTeXML extends LaTeXBuildTarget { } private def str2Level(lev: String): Level.Level = lev match { - case "Info" => Level.Info + case "Info" => Level.Info case "Error" => Level.Error case "Fatal" => Level.Fatal - case _ => Level.Warning + case _ => Level.Warning } private def line2Region(sLine: String, inFile: File): SourceRegion = { @@ -270,11 +256,11 @@ class LaTeXML extends LaTeXBuildTarget { } private object LtxLog { - var optLevel : Option[Level.Level] = None - var msg : List[String] = Nil - var newMsg : Boolean = true - var region : SourceRegion = SourceRegion.none - var phase : Int = 1 + var optLevel: Option[Level.Level] = None + var msg: List[String] = Nil + var newMsg = true + var region = SourceRegion.none + var phase = 1 def phaseToString(p: Int): String = "latexml-" + (p match { case 1 => "compiler" @@ -468,20 +454,21 @@ class LaTeXML extends LaTeXBuildTarget { } /** pdf generation */ -class PdfLatex extends LaTeXBuildTarget -{ +class PdfLatex extends LaTeXBuildTarget { val key = "pdflatex" override val outExt = "pdf" val outDim: ArchiveDimension = Dim("export", "pdflatex", inDim.toString) private var pdflatexPath: String = "xelatex" - override def includeFile(n: String): Boolean = n.endsWith(".tex") && !n.endsWith(localpathsFile) + override def includeFile(n: String): Boolean = + n.endsWith(".tex") && !n.endsWith(localpathsFile) override def start(args: List[String]) { super.start(args) val (_, nonOpts) = splitOptions(remainingStartArguments) - val nonOptArgs = if (nameOfExecutable.nonEmpty) nameOfExecutable :: nonOpts else nonOpts - val newPath = getFromFirstArgOrEnvvar(nonOptArgs, name = "PDFLATEX", pdflatexPath) + val nonOptArgs = if (nameOfExecutable.nonEmpty) nameOfExecutable :: nonOpts + else nonOpts + val newPath = getFromFirstArgOrEnvvar(nonOptArgs, "PDFLATEX", pdflatexPath) if (newPath != pdflatexPath) { pdflatexPath = newPath log("using executable \"" + pdflatexPath + "\"") @@ -570,13 +557,12 @@ class PdfLatex extends LaTeXBuildTarget } } -class TikzSvg extends PdfLatex -{ - override val key : String = "tikzsvg" - override val outExt : String = "svg" - override val outDim : RedirectableDimension = source +class TikzSvg extends PdfLatex { + override val key = "tikzsvg" + override val outExt = "svg" + override val outDim = source - override def includeDir(n: String) : Boolean = n.endsWith("tikz") + override def includeDir(n: String): Boolean = n.endsWith("tikz") override def reallyBuildFile(bt: BuildTask): BuildResult = { val pdfFile = bt.inFile.setExtension("pdf") diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 1c621d0a1f..2cb44d1108 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -4,16 +4,10 @@ import info.kwarc.mmt.api.archives._ import info.kwarc.mmt.api.utils.{File, FilePath} import info.kwarc.mmt.stex.STeXUtils._ -case class STeXStructure(smslines: List[String], deps: List[Dependency]) -{ - /* Union of two STeXStructures. */ - def join(that : STeXStructure) : STeXStructure = { - if (that.smslines.isEmpty && that.deps.isEmpty) { - this - } else { - STeXStructure(that.smslines ++ smslines, that.deps ++ deps) - } - } +case class STeXStructure(smslines: List[String], deps: List[Dependency]) { + def join(s2: STeXStructure): STeXStructure = + if (s2.smslines.isEmpty && s2.deps.isEmpty) this + else STeXStructure(s2.smslines ++ smslines, s2.deps ++ deps) } /** @@ -41,7 +35,8 @@ trait STeXAnalysis { } } - def mkFileDep(archive: Archive, filePath: FilePath): Dependency = FileBuildDependency("tex-deps", archive, filePath) + def mkFileDep(archive: Archive, filePath: FilePath): Dependency = + FileBuildDependency("tex-deps", archive, filePath) def mhRepos(a: Archive, r: String, b: String): List[Dependency] = { val fp = entryToPath(b) @@ -52,22 +47,20 @@ trait STeXAnalysis { } } - protected def toKeyDep(d: Dependency, key: String) : Dependency = d match { + protected def toKeyDep(d: Dependency, key: String): Dependency = d match { case FileBuildDependency(_, ar, fp) => FileBuildDependency(key, ar, fp) case fd => fd } - protected def matchPathAndRep(archive : Archive, inFile: File, line: String, parents: Set[File]) : List[Dependency] = + protected def matchPathAndRep(a: Archive, inFile: File, line: String, parents: Set[File]): List[Dependency] = line match { - case beginModnl(_, _, b) => List(mkFileDep(archive, entryToPath(b))) - + case beginModnl(_, _, b) => List(mkFileDep(a, entryToPath(b))) case input(_, _, _, b) => val p = if (line.startsWith("\\lib")) - STeXUtils.getAmbleFile(b + ".tex", archive, None) + STeXUtils.getAmbleFile(b + ".tex", a, None) else (inFile.up / b).setExtension("tex") val d = PhysicalDependency(p) - d :: (if (!parents.contains(p)) getDeps(archive, p, parents + p) else Nil) - + d :: (if (!parents.contains(p)) getDeps(a, p, parents + p) else Nil) case includeGraphics(_, _, b) => val gExts = List("png", "jpg", "eps", "pdf") val p = inFile.up / b @@ -79,37 +72,18 @@ trait STeXAnalysis { if (os.isEmpty) log(inFile + " misses graphics file: " + b) os.toList.map(s => PhysicalDependency(p.setExtension(s))) } - - case useMhModule(r,b) => { - println("\nuseMhModule mit r = " + r + " und b = " + b) - - val argm : Map[String,String] = getArgMap(r) - - val archiveS : String = argm.getOrElse("repos", archString(archive)) - - assert(getArgMap(r).get("path").isDefined) - val pathS : FilePath = FilePath(getArgMap(r)("path")) - - val nu_dep : Dependency = toKeyDep(mkFileDep(archive,pathS), key = "sms") - println("useMhModule nu_dep: " + nu_dep) - - List(nu_dep) - } - case importOrUseModule(r) => getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList - case mhinputRef(_, r, b) => val fp = entryToPath(b) Option(r) match { - case Some(id) => mkDep(archive, id, fp) - case None => List(mkFileDep(archive, fp)) + case Some(id) => mkDep(a, id, fp) + case None => List(mkFileDep(a, fp)) } - - case tikzinput(_, r, b) => mhRepos(archive, r, b).map(toKeyDep(_, key = "tikzsvg")) - case guse(r, b) => mkDep(archive, r, entryToPath(b)) - case useMhProblem(_, r, b) => createMhImport(archive, r, b).flatMap(_.deps).map(toKeyDep(_, key = "tikzsvg")) - case includeMhProblem(_, r, b) => mhRepos(archive, r, b) + case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) + case guse(r, b) => mkDep(a, r, entryToPath(b)) + case useMhProblem(_, r, b) => createMhImport(a, r, b).flatMap(_.deps).map(toKeyDep(_, "tikzsvg")) + case includeMhProblem(_, r, b) => mhRepos(a, r, b) case _ => Nil } @@ -117,7 +91,7 @@ trait STeXAnalysis { "\\importmodule[load=" + a.root.up.up + "/" + r + "/source/" + p + ",ext=" + ext + "]" + s + "%" private def mkMhImport(a: Archive, r: String, p: String, s: String): STeXStructure = - STeXStructure(List(mkImport(a, r, p, s, ext = "sms")), mkDep(a, r, entryToPath(p)).map(toKeyDep(_, key = "sms"))) + STeXStructure(List(mkImport(a, r, p, s, "sms")), mkDep(a, r, entryToPath(p)).map(toKeyDep(_, "sms"))) private def mkGImport(a: Archive, r: String, p: String): STeXStructure = STeXStructure(List(mkImport(a, r, p, "{" + p + "}", "tex"), "\\mhcurrentrepos{" + r + "}%"), @@ -158,51 +132,44 @@ trait STeXAnalysis { /** create sms file */ def createSms(a: Archive, inFile: File, outFile: File) { val smsLines = mkSTeXStructure(a, inFile, readSourceRebust(inFile).getLines, Set.empty).smslines - if (smsLines.nonEmpty) File.write(outFile, smsLines.reverse.mkString("", "\n", "\n")) else log("no sms content") + if (smsLines.nonEmpty) File.write(outFile, smsLines.reverse.mkString("", "\n", "\n")) + else log("no sms content") } /** get dependencies */ def getDeps(a: Archive, in: File, parents: Set[File], amble: Option[File] = None): List[Dependency] = { val f = amble.getOrElse(in) - if (f.exists) { + if (f.exists) mkSTeXStructure(a, in, readSourceRebust(f).getLines, parents).deps - } else Nil } /** in file is used for relative \input paths */ - def mkSTeXStructure(archive: Archive, in : File, lines : Iterator[String], parents : Set[File]): STeXStructure = - { - var structure : STeXStructure = STeXStructure(Nil, Nil) - def join(s : STeXStructure) : Unit = { - structure = structure.join(s) + def mkSTeXStructure(a: Archive, in: File, lines: Iterator[String], parents: Set[File]): STeXStructure = { + var struct = STeXStructure(Nil, Nil) + def join(s: STeXStructure) = { + struct = struct.join(s) } lines.foreach { line => val l = stripComment(line).trim - val isVerbatim = l.contains("\\verb") - if (!isVerbatim) - { - val sl : List[STeXStructure] = matchSmsEntry(archive, l) + val verbIndex = l.indexOf("\\verb") + if (verbIndex <= -1) { + val sl = matchSmsEntry(a, l) sl.foreach(join) - - if (key != "sms") - { - val od = matchPathAndRep(archive, in, l, parents) + if (key != "sms") { + val od = matchPathAndRep(a, in, l, parents) od.foreach(d => join(STeXStructure(Nil, List(d)))) } } } - structure + struct } - def matchSmsEntry(a: Archive, line: String) : List[STeXStructure] = { + def matchSmsEntry(a: Archive, line: String): List[STeXStructure] = { line match { case importMhModule(r, b) => - //println("smsEntry: a = " + a.id + " line = " + line) - val foo = createMhImport(a, r, b) - //println("foo (deps) = " + foo.head.deps.toString()) - foo + createMhImport(a, r, b) case gimport(_, r, p) => List(createGImport(a, r, p)) case smsGStruct(_, r, _, p) => diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index 7b86c4915d..ca3de52fbd 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -6,8 +6,7 @@ import info.kwarc.mmt.api.utils._ import scala.io.{BufferedSource, Codec} import scala.util.matching.Regex -object STeXUtils -{ +object STeXUtils { val c : String = java.io.File.pathSeparator def mathHubDir(bt: BuildTask): File = bt.archive.root.up.up.up @@ -20,8 +19,7 @@ object STeXUtils def sysEnv(v: String): String = sys.env.getOrElse(v, "") - def env(bt: BuildTask): List[(String, String)] = - { + def env(bt: BuildTask): List[(String, String)] = { val sty = "STEXSTYDIR" val tex = "TEXINPUTS" val styEnv = sysEnv(sty) @@ -35,20 +33,11 @@ object STeXUtils def getLang(f: File): Option[String] = f.stripExtension.getExtension - /* convenience method that you can hand a BuildTask to*/ def getAmbleFile(preOrPost: String, bt: BuildTask): File = { getAmbleFile(preOrPost, bt.archive, getLang(bt.inFile)) } - def getAmbleFile(preOrPost: String, a: Archive, lang: Option[String]) : File = { - def ambleFile(root: File): File = (root / "lib" / preOrPost).setExtension("tex") - val repoFile = getLangAmbleFile(ambleFile(a.root), lang) - if (repoFile.exists()) - repoFile - else getLangAmbleFile(ambleFile(groupMetaInf(a)), lang) - } - - private def getLangAmbleFile(defaultFile: File, lang: Option[String]) : File = + private def getLangAmbleFile(defaultFile: File, lang: Option[String]): File = if (lang.isDefined) { val langFile = defaultFile.stripExtension.setExtension(lang.get + ".tex") if (langFile.exists) @@ -59,7 +48,16 @@ object STeXUtils def groupMetaInf(a: Archive): File = a.root.up / "meta-inf" - def readSourceRebust(f: File) : BufferedSource = scala.io.Source.fromFile(f)(Codec.UTF8) + def getAmbleFile(preOrPost: String, a: Archive, lang: Option[String]): File = { + def ambleFile(root: File): File = (root / "lib" / preOrPost).setExtension("tex") + val repoFile = getLangAmbleFile(ambleFile(a.root), lang) + if (repoFile.exists()) + repoFile + else getLangAmbleFile(ambleFile(groupMetaInf(a)), lang) + } + + def readSourceRebust(f: File): BufferedSource = + scala.io.Source.fromFile(f)(Codec.UTF8) def stripComment(line: String): String = { val idx = line.indexOf('%') @@ -90,33 +88,26 @@ object STeXUtils private val optArg1 = opt0 + arg1 private val bs = "\\\\" private val oStar = "\\*?" - - val input : Regex = (bs + "(lib)?input" + oStar + optArg1).r - val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r - val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r - val useMhProblem : Regex = (bs + "usemhproblem" + optArg1).r - val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r - val beginModnl : Regex = (bs + begin("m?h?modnl") + optArg1).r - val mhinputRef : Regex = (bs + "m?h?inputref" + optArg1).r - val tikzinput : Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r - + val input: Regex = (bs + "(lib)?input" + oStar + optArg1).r + val includeGraphics: Regex = (bs + "includegraphics" + oStar + optArg1).r + val importOrUseModule: Regex = (bs + "(import|use)Module" + opt + any).r + val guse: Regex = (bs + "guse" + opt + arg1).r + val useMhProblem: Regex = (bs + "includemhproblem" + optArg1).r + val includeMhProblem: Regex = (bs + "includemhproblem" + optArg1).r + val beginModnl: Regex = (bs + begin("m?h?modnl") + optArg1).r + val mhinputRef: Regex = (bs + "m?h?inputref" + optArg1).r + val tikzinput: Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r private val smsKeys: List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ List("import", "adopt", "adoptmh").map(_ + "module") - - private val smsTopKeys : List[String] = List("module", "importmodulevia", "importmhmodulevia") - - val smsRegs : Regex = { + private val smsTopKeys: List[String] = List("module", "importmodulevia", "importmhmodulevia") + val smsRegs: Regex = { val begins: String = begin(mkRegGroup(smsTopKeys)) val ends: String = smsTopKeys.mkString("|end\\{(", "|", ")\\}") ("^\\\\(" + mkRegGroup(smsKeys) + "|" + begins + ends + ")").r } - - val importMhModule : Regex = (bs + "importmhmodule" + opt + "(.*?)").r - val useMhModule : Regex = (bs + "usemhmodule" + opt + arg + any).r - - val gimport : Regex = (bs + "gimport" + oStar + optArg1).r - val guse : Regex = (bs + "guse" + opt + arg1).r + val importMhModule: Regex = (bs + "importmhmodule" + opt + "(.*?)").r + val gimport: Regex = (bs + "gimport" + oStar + optArg1).r private def optArg2(s: String): String = bs + begin(s) + opt + arg + arg @@ -138,7 +129,7 @@ object STeXUtils res } - def getProfile(a: Archive) : Option[String] = { + def getProfile(a: Archive): Option[String] = { val key = "profile" var opt = a.properties.get(key) if (opt.isEmpty) { From d50f8286767567eaac6c13a71ccbfc5790c099e2 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 11 Apr 2019 13:19:41 +0200 Subject: [PATCH 32/63] recursive dependencies for mhinputref --- src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 2cb44d1108..396963a7c9 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -76,9 +76,10 @@ trait STeXAnalysis { getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList case mhinputRef(_, r, b) => val fp = entryToPath(b) + val alldeps = getDeps(a,fp.toFile,Set.empty) Option(r) match { - case Some(id) => mkDep(a, id, fp) - case None => List(mkFileDep(a, fp)) + case Some(id) => mkDep(a, id, fp) ::: alldeps + case None => List(mkFileDep(a, fp)) ::: alldeps } case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) case guse(r, b) => mkDep(a, r, entryToPath(b)) From 3f3dec8fb8ab0f3c4a194c37f79f27424d22bb37 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 11 Apr 2019 15:25:18 +0200 Subject: [PATCH 33/63] rebuild needed if outfile doesn't exist --- .../src/main/info/kwarc/mmt/api/archives/BuildTarget.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index a1aaeb5cb3..51a7895b12 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -482,7 +482,8 @@ abstract class TraversingBuildTarget extends BuildTarget { val errorFile = bt.asDependency.getErrorFile(controller) val errs = hadErrors(errorFile, level) val mod = modified(bt.inFile, errorFile) - level <= Level.Force || mod || errs || + val ex = !bt.outFile.exists() + level <= Level.Force || ex || mod || errs || deps.exists { case bd: BuildDependency => val errFile = bd.getErrorFile(controller) From 25e8c95b94ba6cfc5403885a632c29b01f48e94d Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 11 Apr 2019 18:22:47 +0200 Subject: [PATCH 34/63] make sms dependencies behave --- .../src/info/kwarc/mmt/stex/STeXAnalysis.scala | 10 ++++++++++ src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 396963a7c9..8b53e59fc9 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -171,8 +171,18 @@ trait STeXAnalysis { line match { case importMhModule(r, b) => createMhImport(a, r, b) + case useMhModule(opt,_) => + val argmap = getArgMap(opt) + val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) + assert(deps.length == 1) + List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) case gimport(_, r, p) => List(createGImport(a, r, p)) + case guse(opt,_) => + val argmap = getArgMap(opt) + val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) + assert(deps.length == 1) + List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) case smsGStruct(_, r, _, p) => List(createGImport(a, r, p)) case smsMhStruct(r, _, p) => diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index ca3de52fbd..e8145c6601 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -92,10 +92,11 @@ object STeXUtils { val includeGraphics: Regex = (bs + "includegraphics" + oStar + optArg1).r val importOrUseModule: Regex = (bs + "(import|use)Module" + opt + any).r val guse: Regex = (bs + "guse" + opt + arg1).r - val useMhProblem: Regex = (bs + "includemhproblem" + optArg1).r + val useMhProblem: Regex = (bs + "usemhproblem" + optArg1).r + val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r val includeMhProblem: Regex = (bs + "includemhproblem" + optArg1).r val beginModnl: Regex = (bs + begin("m?h?modnl") + optArg1).r - val mhinputRef: Regex = (bs + "m?h?inputref" + optArg1).r + val mhinputRef: Regex = (bs + "n?m?h?inputref" + optArg1).r val tikzinput: Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r private val smsKeys: List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ From 993b41915e56df57f6d14c9d0adbc413938a272c Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 11 Apr 2019 18:30:52 +0200 Subject: [PATCH 35/63] add option of usemhmodule without path givens (empty dependency) --- .../src/info/kwarc/mmt/stex/STeXAnalysis.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 8b53e59fc9..79ea0988df 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -173,16 +173,20 @@ trait STeXAnalysis { createMhImport(a, r, b) case useMhModule(opt,_) => val argmap = getArgMap(opt) - val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) - assert(deps.length == 1) - List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) + if (argmap.contains("path")) { + val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) + assert(deps.length == 1) + List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) + } else { Nil } case gimport(_, r, p) => List(createGImport(a, r, p)) case guse(opt,_) => val argmap = getArgMap(opt) - val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) - assert(deps.length == 1) - List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) + if (argmap.contains("path")) { + val deps = mkDep(a,argmap.getOrElse("repos",archString(a)),entryToPath(argmap("path"))) + assert(deps.length == 1) + List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) + } else { Nil } case smsGStruct(_, r, _, p) => List(createGImport(a, r, p)) case smsMhStruct(r, _, p) => From 9dc60c301b25ca1baa28e290a6fca97cb6f84939 Mon Sep 17 00:00:00 2001 From: Florian Rabe Date: Thu, 11 Apr 2019 23:48:21 +0200 Subject: [PATCH 36/63] --- src/mmt-api/resources/latex/unicode-latex-map | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mmt-api/resources/latex/unicode-latex-map b/src/mmt-api/resources/latex/unicode-latex-map index eddefaf4ce..0be3a8ae02 100644 --- a/src/mmt-api/resources/latex/unicode-latex-map +++ b/src/mmt-api/resources/latex/unicode-latex-map @@ -108,8 +108,8 @@ jbigoplus|⨁ jbigotimes|⨂ jsubset|⊂ jsupset|⊃ -jsq|⊆‍ -jsubseteq|⊆‍ +jsq|⊆ +jsubseteq|⊆ jsupseteq|⊇ jnsubset|⊄ jnsupset|⊅ From 0649f6eedd134ddc51badcedd0acada5fb1a3544 Mon Sep 17 00:00:00 2001 From: Florian Rabe Date: Fri, 12 Apr 2019 01:18:08 +0200 Subject: [PATCH 37/63] --- src/mmt-api/resources/latex/unicode-latex-map | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mmt-api/resources/latex/unicode-latex-map b/src/mmt-api/resources/latex/unicode-latex-map index 0be3a8ae02..25f53ef6a1 100644 --- a/src/mmt-api/resources/latex/unicode-latex-map +++ b/src/mmt-api/resources/latex/unicode-latex-map @@ -148,6 +148,8 @@ jtriangleright|▷ jtriangleleft|◁ jblacktriangleright|▶ jblacktriangleleft|◀ +jsmblksquare|▪ +jsmwhtsquare|▫ jder|⊦ jrightassert|⊦ jvdash|⊢ From 7547bef1afae38964db752a171b31346f2e0249e Mon Sep 17 00:00:00 2001 From: Makarius Date: Fri, 12 Apr 2019 23:16:18 +0200 Subject: [PATCH 38/63] more ULO properties based on document tags; --- .../info/kwarc/mmt/isabelle/Importer.scala | 38 +++++++++++++------ .../info/kwarc/mmt/isabelle/Ontology.scala | 3 ++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index acd0b8feae..11d88c7082 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -183,6 +183,7 @@ object Importer element: isabelle.Thy_Element.Element_Command = isabelle.Thy_Element.atom(isabelle.Command.empty), element_timing: isabelle.Document_Status.Overall_Timing = isabelle.Document_Status.Overall_Timing.empty, command_kind: Option[String] = None, + document_tags: List[String] = Nil, meta_data: isabelle.Properties.T = Nil, heading: Option[Int] = None, proof: Option[Proof_Text] = None, @@ -503,14 +504,14 @@ object Importer // document headings for (i <- segment.heading) { val item = make_dummy("heading", i) - thy_draft.declare_item(item, segment.meta_data) + thy_draft.declare_item(item, segment.document_tags, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.section)) } // classes for (decl <- segment.classes) { decl_error(decl.entity) { - val item = thy_draft.declare_entity(decl.entity, segment.meta_data) + val item = thy_draft.declare_entity(decl.entity, segment.document_tags, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.universe)) val tp = Isabelle.Class() add_constant(item, tp, None) @@ -521,7 +522,7 @@ object Importer for (decl <- segment.types) { decl_error(decl.entity) { val item = thy_draft.make_item(decl.entity, decl.syntax) - thy_draft.declare_item(item, segment.meta_data) + thy_draft.declare_item(item, segment.document_tags, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.`type`)) if (thy_export.typedefs.exists(typedef => typedef.name == item.entity_name)) { @@ -538,7 +539,7 @@ object Importer for (decl <- segment.consts) { decl_error(decl.entity) { val item = thy_draft.make_item(decl.entity, decl.syntax, (decl.typargs, decl.typ)) - thy_draft.declare_item(item, segment.meta_data) + thy_draft.declare_item(item, segment.document_tags, segment.meta_data) if (segment.is_axiomatization) { thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.primitive)) @@ -561,7 +562,7 @@ object Importer val facts: List[Item] = segment.facts_single.flatMap(decl => decl_error(decl.entity) { - val item = thy_draft.declare_entity(decl.entity, segment.meta_data) + val item = thy_draft.declare_entity(decl.entity, segment.document_tags, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.statement)) if (segment.is_definition) { @@ -610,7 +611,7 @@ object Importer for (locale <- segment.locales) { decl_error(locale.entity) { val content = thy_draft.content - val item = thy_draft.declare_entity(locale.entity, segment.meta_data) + val item = thy_draft.declare_entity(locale.entity, segment.document_tags, segment.meta_data) thy_draft.rdf_triple(Ontology.unary(item.global_name, Ontology.ULO.theory)) val loc_name = item.local_name val loc_thy = Theory.empty(thy.path.doc, thy.name / loc_name, None) @@ -661,7 +662,7 @@ object Importer // locale dependencies (inclusions) for (dep <- segment.locale_dependencies if dep.is_inclusion) { decl_error(dep.entity) { - val item = thy_draft.declare_entity(dep.entity, segment.meta_data) + val item = thy_draft.declare_entity(dep.entity, segment.document_tags, segment.meta_data) val content = thy_draft.content val from = OMMOD(content.get_locale(dep.source).global_name.toMPath) @@ -1170,6 +1171,10 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] isabelle.Document_Status.Overall_Timing.make( snapshot.state, snapshot.version, element.iterator.toList) + val element_range = commands_range(rendering.snapshot, element.head, element.last) + + val document_tags = rendering.document_tags(element_range) + val meta_data = element_meta_data(rendering, element) val heading = @@ -1226,6 +1231,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] element = element, element_timing = element_timing, command_kind = syntax.keywords.kinds.get(element.head.span.name), + document_tags = document_tags, meta_data = meta_data, heading = heading, proof = proof, @@ -1464,7 +1470,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] def rdf_triple(triple: isabelle.RDF.Triple): Unit = _state.change({ case (content, triples) => (content, triple :: triples) }) - def declare_item(item: Item, props: isabelle.Properties.T) + def declare_item(item: Item, tags: List[String], props: isabelle.Properties.T) { _state.change( { case (content, triples) => @@ -1477,8 +1483,15 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] val source_ref = item.source_ref.map(sref => Ontology.binary(item.global_name, Ontology.ULO.sourceref, sref.toURI)) + val important = + tags.reverse.collectFirst({ + case isabelle.Markup.Document_Tag.IMPORTANT => true + case isabelle.Markup.Document_Tag.UNIMPORTANT => false + }).toList.map(b => + Ontology.unary(item.global_name, if (b) Ontology.ULO.important else Ontology.ULO.unimportant)) val properties = props.map({ case (a, b) => Ontology.binary(item.global_name, a, b) }) - val triples1 = name :: specs ::: source_ref.toList ::: properties.reverse ::: triples + + val triples1 = name :: specs ::: source_ref.toList ::: important ::: properties.reverse ::: triples (content1, triples1) }) } @@ -1515,10 +1528,13 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] type_scheme = type_scheme) } - def declare_entity(entity: isabelle.Export_Theory.Entity, props: isabelle.Properties.T): Item = + def declare_entity( + entity: isabelle.Export_Theory.Entity, + tags: List[String], + props: isabelle.Properties.T): Item = { val item = make_item(entity) - declare_item(item, props) + declare_item(item, tags, props) item } diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala index 30e3144d42..4bcc894e80 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Ontology.scala @@ -59,6 +59,9 @@ object Ontology val derived = ulo("derived") // HOL typedefs, proven statements val experimental = ulo("experimental") // proof contains "sorry" + val important = ulo("important") // command is tagged as "important" + val unimportant = ulo("unimportant") // command is tagged as "unimportant" + /* binaries */ From 88f77237cddf504b21e1980f6826b9165426e2fd Mon Sep 17 00:00:00 2001 From: Makarius Date: Sat, 13 Apr 2019 14:36:07 +0200 Subject: [PATCH 39/63] expose document headings as OpaqueText --- src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 11d88c7082..7f2c062929 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -198,8 +198,8 @@ object Importer element.head.span.content.iterator.takeWhile(tok => !tok.is_begin).map(_.source).mkString def header_relevant: Boolean = header.nonEmpty && - (classes.nonEmpty || types.nonEmpty || consts.nonEmpty || facts.nonEmpty || - locales.nonEmpty || locale_dependencies.nonEmpty) + (heading.isDefined || classes.nonEmpty || types.nonEmpty || consts.nonEmpty || + facts.nonEmpty || locales.nonEmpty || locale_dependencies.nonEmpty) def command_name: String = element.head.span.name From 1b6468323d6da01ebc084d907ef9f1a9ae4f7938 Mon Sep 17 00:00:00 2001 From: Makarius Date: Sat, 13 Apr 2019 15:12:01 +0200 Subject: [PATCH 40/63] expose all document commands as OpaqueText --- .../src/info/kwarc/mmt/isabelle/Importer.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala index 7f2c062929..d505f67fbd 100644 --- a/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala +++ b/src/mmt-isabelle/src/info/kwarc/mmt/isabelle/Importer.scala @@ -184,6 +184,7 @@ object Importer element_timing: isabelle.Document_Status.Overall_Timing = isabelle.Document_Status.Overall_Timing.empty, command_kind: Option[String] = None, document_tags: List[String] = Nil, + document_command: Boolean = false, meta_data: isabelle.Properties.T = Nil, heading: Option[Int] = None, proof: Option[Proof_Text] = None, @@ -198,7 +199,7 @@ object Importer element.head.span.content.iterator.takeWhile(tok => !tok.is_begin).map(_.source).mkString def header_relevant: Boolean = header.nonEmpty && - (heading.isDefined || classes.nonEmpty || types.nonEmpty || consts.nonEmpty || + (document_command || classes.nonEmpty || types.nonEmpty || consts.nonEmpty || facts.nonEmpty || locales.nonEmpty || locale_dependencies.nonEmpty) def command_name: String = element.head.span.name @@ -1133,6 +1134,9 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] val syntax = resources.session_base.node_syntax(snapshot.version.nodes, node_name) + def document_command(element: isabelle.Thy_Element.Element_Command): Boolean = + isabelle.Document_Structure.is_document_command(syntax.keywords, element.head) + val node_timing = isabelle.Document_Status.Overall_Timing.make( snapshot.state, snapshot.version, snapshot.node.commands) @@ -1153,7 +1157,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] { val relevant_elements = node_elements.filter(element => - isabelle.Document_Structure.is_heading_command(element.head) || + document_command(element) || element.head.span.is_kind(syntax.keywords, isabelle.Keyword.theory, false)) val relevant_ids = @@ -1232,6 +1236,7 @@ Usage: isabelle mmt_import [OPTIONS] [SESSIONS ...] element_timing = element_timing, command_kind = syntax.keywords.kinds.get(element.head.span.name), document_tags = document_tags, + document_command = document_command(element), meta_data = meta_data, heading = heading, proof = proof, From 8fa0b548fbac118f1cddee478b612208fcfb43b5 Mon Sep 17 00:00:00 2001 From: Makarius Date: Sat, 13 Apr 2019 15:56:11 +0200 Subject: [PATCH 41/63] strip material already present in Isabelle classpath --- src/mmt-isabelle/lib/Tools/mmt_build | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/lib/Tools/mmt_build b/src/mmt-isabelle/lib/Tools/mmt_build index 9bbc8b1cd0..69278fde9d 100755 --- a/src/mmt-isabelle/lib/Tools/mmt_build +++ b/src/mmt-isabelle/lib/Tools/mmt_build @@ -2,6 +2,22 @@ # # DESCRIPTION: build and deploy MMT, using sbt -isabelle_admin_build jars || exit $? +set -e -cd "$ISABELLE_MMT_ROOT/src" && sbt mmt/deploy "$@" +# Isabelle Scala/Java components +isabelle_admin_build jars + +# MMT build +cd "$ISABELLE_MMT_ROOT/src" +sbt mmt/deploy "$@" + +# strip material already present in Isabelle classpath +cd "$ISABELLE_MMT_ROOT/deploy" +rm -rf tmp +mkdir tmp +cd tmp +jar xf ../mmt.jar +rm -r isabelle org/jline org/tukaani scala +jar cf ../mmt.jar . +cd .. +rm -rf tmp From 99bd70e7b1821790ade971b707b28d836ba19e4e Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Sun, 14 Apr 2019 21:30:52 +0200 Subject: [PATCH 42/63] make includemhproblem recurse properly --- src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 79ea0988df..5ba6ef754a 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -83,8 +83,9 @@ trait STeXAnalysis { } case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) case guse(r, b) => mkDep(a, r, entryToPath(b)) + // ToDo: The use of tikzsvg in useMhProblem seems like a bug. Verify? case useMhProblem(_, r, b) => createMhImport(a, r, b).flatMap(_.deps).map(toKeyDep(_, "tikzsvg")) - case includeMhProblem(_, r, b) => mhRepos(a, r, b) + case includeMhProblem(_, r, b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) case _ => Nil } From 1df31dab6216c9074b86a747da6b6d76f690e86d Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Mon, 15 Apr 2019 12:06:24 +0200 Subject: [PATCH 43/63] refactoring and documenting parts of mmt-stex, part 1 --- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 51 ++++++++----- .../src/info/kwarc/mmt/stex/STeXUtils.scala | 75 ++++++++++--------- 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 5ba6ef754a..1202e6a24b 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -4,10 +4,19 @@ import info.kwarc.mmt.api.archives._ import info.kwarc.mmt.api.utils.{File, FilePath} import info.kwarc.mmt.stex.STeXUtils._ -case class STeXStructure(smslines: List[String], deps: List[Dependency]) { - def join(s2: STeXStructure): STeXStructure = - if (s2.smslines.isEmpty && s2.deps.isEmpty) this - else STeXStructure(s2.smslines ++ smslines, s2.deps ++ deps) +/** + * Monoidal accumulator for SMS content and dependencies. + * @param smslines SMS content (List[String]) + * @param deps Collected dependencies (List[Dependency]) + */ +case class STeXStructure(smslines: List[String], deps: List[Dependency]) +{ + def empty : STeXStructure = STeXStructure(List.empty,List.empty) + + def <>(that : STeXStructure) : STeXStructure = + if (that.smslines.isEmpty && that.deps.isEmpty) { + this + } else STeXStructure(that.smslines ++ smslines, that.deps ++ deps) } /** @@ -131,11 +140,12 @@ trait STeXAnalysis { private def createImport(r: String, p: String): STeXStructure = STeXStructure(List("\\importmodule[" + r + "]{" + p + "}%"), Nil) - /** create sms file */ - def createSms(a: Archive, inFile: File, outFile: File) { - val smsLines = mkSTeXStructure(a, inFile, readSourceRebust(inFile).getLines, Set.empty).smslines - if (smsLines.nonEmpty) File.write(outFile, smsLines.reverse.mkString("", "\n", "\n")) - else log("no sms content") + /** Collect sms content and write to outFile. */ + def createSms(a: Archive, inFile: File, outFile: File) : Unit = { + val smsContent = mkSTeXStructure(a, inFile, readSourceRebust(inFile).getLines, Set.empty).smslines + + if (smsContent.isEmpty) {log("no sms content")} + else { File.write(outFile, smsContent.reverse.mkString("", "\n", "\n")) } } /** get dependencies */ @@ -146,26 +156,29 @@ trait STeXAnalysis { else Nil } - /** in file is used for relative \input paths */ - def mkSTeXStructure(a: Archive, in: File, lines: Iterator[String], parents: Set[File]): STeXStructure = { - var struct = STeXStructure(Nil, Nil) - def join(s: STeXStructure) = { - struct = struct.join(s) + /** inFile is used for relative \input paths */ + def mkSTeXStructure(a: Archive, in: File, lines: Iterator[String], parents: Set[File]): STeXStructure = + { + var localStruct = STeXStructure(Nil,Nil) + + def combine(s: STeXStructure) : Unit = { + localStruct = localStruct <> s } lines.foreach { line => val l = stripComment(line).trim - val verbIndex = l.indexOf("\\verb") - if (verbIndex <= -1) { + val isVerbatim = l.contains("\\verb") + if (!isVerbatim) + { val sl = matchSmsEntry(a, l) - sl.foreach(join) + sl.foreach(combine) if (key != "sms") { val od = matchPathAndRep(a, in, l, parents) - od.foreach(d => join(STeXStructure(Nil, List(d)))) + od.foreach(d => combine(STeXStructure(Nil, List(d)))) } } } - struct + localStruct } def matchSmsEntry(a: Archive, line: String): List[STeXStructure] = { diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index e8145c6601..7561f16acb 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -9,15 +9,12 @@ import scala.util.matching.Regex object STeXUtils { val c : String = java.io.File.pathSeparator - def mathHubDir(bt: BuildTask): File = bt.archive.root.up.up.up + def mathHubDir(bt: BuildTask) : File = bt.archive.root.up.up.up + def extBase(bt: BuildTask) : File = mathHubDir(bt) / "ext" + def stexStyDir(bt: BuildTask) : File = extBase(bt) / "sTeX" / "sty" + def styPath(bt: BuildTask) : File = mathHubDir(bt) / "sty" - def extBase(bt: BuildTask): File = mathHubDir(bt) / "ext" - - def stexStyDir(bt: BuildTask): File = extBase(bt) / "sTeX" / "sty" - - def styPath(bt: BuildTask): File = mathHubDir(bt) / "sty" - - def sysEnv(v: String): String = sys.env.getOrElse(v, "") + def sysEnv(v: String) : String = sys.env.getOrElse(v, "") def env(bt: BuildTask): List[(String, String)] = { val sty = "STEXSTYDIR" @@ -79,46 +76,50 @@ object STeXUtils { private def begin(s: String): String = "begin\\{" + s + "\\}" - private val space = "\\s*" - private val opt = "\\[(.*?)\\]" - private val opt0 = "(" + opt + ")?" - private val arg = space + "\\{(.*?)\\}" - private val any = ".*" - private val arg1 = arg + any - private val optArg1 = opt0 + arg1 - private val bs = "\\\\" - private val oStar = "\\*?" - val input: Regex = (bs + "(lib)?input" + oStar + optArg1).r - val includeGraphics: Regex = (bs + "includegraphics" + oStar + optArg1).r - val importOrUseModule: Regex = (bs + "(import|use)Module" + opt + any).r - val guse: Regex = (bs + "guse" + opt + arg1).r - val useMhProblem: Regex = (bs + "usemhproblem" + optArg1).r - val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r - val includeMhProblem: Regex = (bs + "includemhproblem" + optArg1).r - val beginModnl: Regex = (bs + begin("m?h?modnl") + optArg1).r - val mhinputRef: Regex = (bs + "n?m?h?inputref" + optArg1).r - val tikzinput: Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r - private val smsKeys: List[String] = List("gadopt", "symvariant", "gimport") ++ + private val space : String = "\\s*" + private val opt : String = "\\[(.*?)\\]" + private val opt0 : String = "(" + opt + ")?" + private val arg : String = space + "\\{(.*?)\\}" + private val any : String = ".*" + private val arg1 : String = arg + any + private val optArg1 : String = opt0 + arg1 + private val bs : String = "\\\\" + private val oStar : String = "\\*?" + + val input : Regex = (bs + "(lib)?input" + oStar + optArg1).r + val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r + val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r + val guse : Regex = (bs + "guse" + opt + arg1).r + val useMhProblem : Regex = (bs + "usemhproblem" + optArg1).r + val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r + val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r + val beginModnl : Regex = (bs + begin("m?h?modnl") + optArg1).r + val mhinputRef : Regex = (bs + "n?m?h?inputref" + optArg1).r + val tikzinput : Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r + + private val smsKeys : List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ List("import", "adopt", "adoptmh").map(_ + "module") + private val smsTopKeys: List[String] = List("module", "importmodulevia", "importmhmodulevia") + val smsRegs: Regex = { val begins: String = begin(mkRegGroup(smsTopKeys)) val ends: String = smsTopKeys.mkString("|end\\{(", "|", ")\\}") ("^\\\\(" + mkRegGroup(smsKeys) + "|" + begins + ends + ")").r } - val importMhModule: Regex = (bs + "importmhmodule" + opt + "(.*?)").r - val gimport: Regex = (bs + "gimport" + oStar + optArg1).r private def optArg2(s: String): String = bs + begin(s) + opt + arg + arg - val smsSStruct : Regex = optArg2("sstructure").r - val smsGStruct : Regex = (bs + begin("gstructure") + opt0 + arg + arg).r - val smsMhStruct : Regex = optArg2("mhstructure").r - val smsViewsig : Regex = (optArg2("gviewsig") + arg).r - val smsViewnl : Regex = (bs + begin("gviewnl") + opt0 + arg + any).r - val smsMhView : Regex = (optArg2("mhview") + arg).r - val smsView : Regex = optArg2("view").r + val importMhModule : Regex = (bs + "importmhmodule" + opt + "(.*?)").r + val gimport : Regex = (bs + "gimport" + oStar + optArg1).r + val smsSStruct : Regex = optArg2("sstructure").r + val smsGStruct : Regex = (bs + begin("gstructure") + opt0 + arg + arg).r + val smsMhStruct : Regex = optArg2("mhstructure").r + val smsViewsig : Regex = (optArg2("gviewsig") + arg).r + val smsViewnl : Regex = (bs + begin("gviewnl") + opt0 + arg + any).r + val smsMhView : Regex = (optArg2("mhview") + arg).r + val smsView : Regex = optArg2("view").r def entryToPath(p: String) : FilePath = File(p).setExtension("tex").toFilePath From 950fc55bc880c2184de055ef8df25c3803d219c0 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Mon, 15 Apr 2019 12:19:58 +0200 Subject: [PATCH 44/63] refactoring and documenting parts of mmt-stex, part 2 --- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 21 ++++++++++++++++--- .../src/info/kwarc/mmt/stex/STeXUtils.scala | 12 +++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 1202e6a24b..7b5f2e6b5e 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -156,7 +156,8 @@ trait STeXAnalysis { else Nil } - /** inFile is used for relative \input paths */ + /** Method for creating STeXStructures, used for generating sms content and finding dependencies. + * inFile is used for relative \input paths */ def mkSTeXStructure(a: Archive, in: File, lines: Iterator[String], parents: Set[File]): STeXStructure = { var localStruct = STeXStructure(Nil,Nil) @@ -181,10 +182,13 @@ trait STeXAnalysis { localStruct } - def matchSmsEntry(a: Archive, line: String): List[STeXStructure] = { - line match { + def matchSmsEntry(a: Archive, line: String): List[STeXStructure] = + { + line match + { case importMhModule(r, b) => createMhImport(a, r, b) + case useMhModule(opt,_) => val argmap = getArgMap(opt) if (argmap.contains("path")) { @@ -192,8 +196,10 @@ trait STeXAnalysis { assert(deps.length == 1) List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) } else { Nil } + case gimport(_, r, p) => List(createGImport(a, r, p)) + case guse(opt,_) => val argmap = getArgMap(opt) if (argmap.contains("path")) { @@ -201,19 +207,25 @@ trait STeXAnalysis { assert(deps.length == 1) List(STeXStructure(Nil,List(toKeyDep(deps.head,key = "sms")))) } else { Nil } + case smsGStruct(_, r, _, p) => List(createGImport(a, r, p)) + case smsMhStruct(r, _, p) => createMhImport(a, r, p) + case smsSStruct(r, _, p) => List(createImport(r, p)) + case smsViewsig(r, _, f, t) => val m = getArgMap(r) val fr = m.getOrElse("fromrepos", archString(a)) val tr = m.getOrElse("torepos", archString(a)) List(mkGImport(a, fr, f), mkGImport(a, tr, t)) + case smsViewnl(_, r, p) => List(createGImport(a, archString(a), p)) + case smsMhView(r, _, f, t) => val m = getArgMap(r) var ofp = m.get("frompath") @@ -225,6 +237,7 @@ trait STeXAnalysis { List(mkMhImport(a, fr, fp, f), mkMhImport(a, tr, tp, t)) case _ => Nil } + case smsView(r, f, t) => val m = getArgMap(r) val ofr = m.get("from") @@ -234,7 +247,9 @@ trait STeXAnalysis { List(createImport(fr, f), createImport(tr, t)) case _ => Nil } + case _ if smsRegs.findFirstIn(line).isDefined => List(STeXStructure(List(line + "%"), Nil)) + case _ => Nil } } diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index 7561f16acb..640346d951 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -6,7 +6,8 @@ import info.kwarc.mmt.api.utils._ import scala.io.{BufferedSource, Codec} import scala.util.matching.Regex -object STeXUtils { +object STeXUtils +{ val c : String = java.io.File.pathSeparator def mathHubDir(bt: BuildTask) : File = bt.archive.root.up.up.up @@ -53,8 +54,7 @@ object STeXUtils { else getLangAmbleFile(ambleFile(groupMetaInf(a)), lang) } - def readSourceRebust(f: File): BufferedSource = - scala.io.Source.fromFile(f)(Codec.UTF8) + def readSourceRebust(f: File): BufferedSource = scala.io.Source.fromFile(f)(Codec.UTF8) def stripComment(line: String): String = { val idx = line.indexOf('%') @@ -93,9 +93,9 @@ object STeXUtils { val useMhProblem : Regex = (bs + "usemhproblem" + optArg1).r val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r - val beginModnl : Regex = (bs + begin("m?h?modnl") + optArg1).r - val mhinputRef : Regex = (bs + "n?m?h?inputref" + optArg1).r - val tikzinput : Regex = (any + bs + "c?m?h?tikzinput" + optArg1).r + val beginModnl : Regex = (bs + begin("(?:mh)?modnl") + optArg1).r + val mhinputRef : Regex = (bs + "n?(?:mh)?inputref" + optArg1).r + val tikzinput : Regex = (any + bs + "c?(?:mh)?tikzinput" + optArg1).r private val smsKeys : List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ From 676b1f27865fe9a4e2c91810336c1ebf23be77cf Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Mon, 15 Apr 2019 12:20:37 +0200 Subject: [PATCH 45/63] usemhproblem ain't a thing --- src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala | 2 -- src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala | 1 - 2 files changed, 3 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 7b5f2e6b5e..f09f09bf43 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -92,8 +92,6 @@ trait STeXAnalysis { } case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) case guse(r, b) => mkDep(a, r, entryToPath(b)) - // ToDo: The use of tikzsvg in useMhProblem seems like a bug. Verify? - case useMhProblem(_, r, b) => createMhImport(a, r, b).flatMap(_.deps).map(toKeyDep(_, "tikzsvg")) case includeMhProblem(_, r, b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) case _ => Nil } diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index 640346d951..eebf329cf2 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -90,7 +90,6 @@ object STeXUtils val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r val guse : Regex = (bs + "guse" + opt + arg1).r - val useMhProblem : Regex = (bs + "usemhproblem" + optArg1).r val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r val beginModnl : Regex = (bs + begin("(?:mh)?modnl") + optArg1).r From 1027249933c3c85d099fd9f35147d4f933b86241 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Mon, 15 Apr 2019 16:52:49 +0200 Subject: [PATCH 46/63] includes and inputs of assignments and problems --- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 50 +++++++++++++++++-- .../src/info/kwarc/mmt/stex/STeXUtils.scala | 48 +++++++++++------- 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index f09f09bf43..4fb80b10b1 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -61,15 +61,47 @@ trait STeXAnalysis { case fd => fd } - protected def matchPathAndRep(a: Archive, inFile: File, line: String, parents: Set[File]): List[Dependency] = - line match { + protected def matchPathAndRepository(a: Archive, inFile: File, line: String, parents: Set[File]) : List[Dependency] = + { + line match + { case beginModnl(_, _, b) => List(mkFileDep(a, entryToPath(b))) + + // ToDo: These four (at least) rely on the included file being in the same directory. Fix that. case input(_, _, _, b) => + // As below with slight alterations for libinputs. val p = if (line.startsWith("\\lib")) STeXUtils.getAmbleFile(b + ".tex", a, None) else (inFile.up / b).setExtension("tex") val d = PhysicalDependency(p) d :: (if (!parents.contains(p)) getDeps(a, p, parents + p) else Nil) + + case includeAssignment(_,_,b) => + // "a13" -> File(a13.tex) + val pfile : File = (inFile.up / b).setExtension("tex") + // The including file naturally physically depends on the included file. + val pdep : Dependency = PhysicalDependency(pfile) + // All dependencies of the included file are now also dependencies of the including file. + if (!parents.contains(pfile)) { + pdep :: getDeps(a, pfile, parents + pfile) + } else { List(pdep) } + + case inputAssignment(_,_,b) => + // As above. + val pfile : File = (inFile.up / b).setExtension("tex") + val pdep : Dependency = PhysicalDependency(pfile) + if (!parents.contains(pfile)) { + pdep :: getDeps(a, pfile, parents + pfile) + } else { List(pdep) } + + case includeProblem(_,_,b) => + // As above. + val pfile : File = (inFile.up / b).setExtension("tex") + val pdep : Dependency = PhysicalDependency(pfile) + if (!parents.contains(pfile)) { + pdep :: getDeps(a, pfile, parents + pfile) + } else { List(pdep) } + case includeGraphics(_, _, b) => val gExts = List("png", "jpg", "eps", "pdf") val p = inFile.up / b @@ -81,8 +113,10 @@ trait STeXAnalysis { if (os.isEmpty) log(inFile + " misses graphics file: " + b) os.toList.map(s => PhysicalDependency(p.setExtension(s))) } + case importOrUseModule(r) => getArgMap(r).get("load").map(f => PhysicalDependency(File(f).setExtension(".sms"))).toList + case mhinputRef(_, r, b) => val fp = entryToPath(b) val alldeps = getDeps(a,fp.toFile,Set.empty) @@ -90,11 +124,19 @@ trait STeXAnalysis { case Some(id) => mkDep(a, id, fp) ::: alldeps case None => List(mkFileDep(a, fp)) ::: alldeps } + case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) + case guse(r, b) => mkDep(a, r, entryToPath(b)) - case includeMhProblem(_, r, b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) + + // These three are supposed to work exactly alike, see https://github.com/UniFormal/MMT/issues/465. + case inputMhAssignment(_,r,b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) + case includeMhAssignment(_,r,b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) + case includeMhProblem(_, r, b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) + case _ => Nil } + } private def mkImport(a: Archive, r: String, p: String, s: String, ext: String): String = "\\importmodule[load=" + a.root.up.up + "/" + r + "/source/" + p + ",ext=" + ext + "]" + s + "%" @@ -172,7 +214,7 @@ trait STeXAnalysis { val sl = matchSmsEntry(a, l) sl.foreach(combine) if (key != "sms") { - val od = matchPathAndRep(a, in, l, parents) + val od = matchPathAndRepository(a, in, l, parents) od.foreach(d => combine(STeXStructure(Nil, List(d)))) } } diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala index eebf329cf2..062789d7f6 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXUtils.scala @@ -83,18 +83,30 @@ object STeXUtils private val any : String = ".*" private val arg1 : String = arg + any private val optArg1 : String = opt0 + arg1 - private val bs : String = "\\\\" + private val bs : String = "\\\\" // backslash private val oStar : String = "\\*?" - val input : Regex = (bs + "(lib)?input" + oStar + optArg1).r - val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r - val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r - val guse : Regex = (bs + "guse" + opt + arg1).r - val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r - val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r - val beginModnl : Regex = (bs + begin("(?:mh)?modnl") + optArg1).r - val mhinputRef : Regex = (bs + "n?(?:mh)?inputref" + optArg1).r - val tikzinput : Regex = (any + bs + "c?(?:mh)?tikzinput" + optArg1).r + val input : Regex = (bs + "(lib)?input" + oStar + optArg1).r + val includeGraphics : Regex = (bs + "includegraphics" + oStar + optArg1).r + val importOrUseModule : Regex = (bs + "(import|use)Module" + opt + any).r + + val useMhModule : Regex = (bs + "usemhmodule" + opt + arg1).r + val guse : Regex = (bs + "guse" + opt + arg1).r + + val importMhModule : Regex = (bs + "importmhmodule" + opt + "(.*?)").r + val gimport : Regex = (bs + "gimport" + oStar + optArg1).r + + val inputAssignment : Regex = (bs + "inputassignment" + optArg1).r + val includeAssignment : Regex = (bs + "includeassignment" + optArg1).r + val includeProblem : Regex = (bs + "includeproblem" + optArg1).r + + val inputMhAssignment : Regex = (bs + "inputmhassignment" + optArg1).r + val includeMhAssignment : Regex = (bs + "includemhassignment" + optArg1).r + val includeMhProblem : Regex = (bs + "includemhproblem" + optArg1).r + + val beginModnl : Regex = (bs + begin("(?:mh)?modnl") + optArg1).r + val mhinputRef : Regex = (bs + "n?(?:mh)?inputref" + optArg1).r + val tikzinput : Regex = (any + bs + "c?(?:mh)?tikzinput" + optArg1).r private val smsKeys : List[String] = List("gadopt", "symvariant", "gimport") ++ List("sym", "abbr", "key", "listkey").map(_ + "def") ++ @@ -110,15 +122,13 @@ object STeXUtils private def optArg2(s: String): String = bs + begin(s) + opt + arg + arg - val importMhModule : Regex = (bs + "importmhmodule" + opt + "(.*?)").r - val gimport : Regex = (bs + "gimport" + oStar + optArg1).r - val smsSStruct : Regex = optArg2("sstructure").r - val smsGStruct : Regex = (bs + begin("gstructure") + opt0 + arg + arg).r - val smsMhStruct : Regex = optArg2("mhstructure").r - val smsViewsig : Regex = (optArg2("gviewsig") + arg).r - val smsViewnl : Regex = (bs + begin("gviewnl") + opt0 + arg + any).r - val smsMhView : Regex = (optArg2("mhview") + arg).r - val smsView : Regex = optArg2("view").r + val smsSStruct : Regex = optArg2("sstructure").r + val smsGStruct : Regex = (bs + begin("gstructure") + opt0 + arg + arg).r + val smsMhStruct : Regex = optArg2("mhstructure").r + val smsViewsig : Regex = (optArg2("gviewsig") + arg).r + val smsViewnl : Regex = (bs + begin("gviewnl") + opt0 + arg + any).r + val smsMhView : Regex = (optArg2("mhview") + arg).r + val smsView : Regex = optArg2("view").r def entryToPath(p: String) : FilePath = File(p).setExtension("tex").toFilePath From cf53b96cfb87a8a4e69bab0aff58917ab613e5bd Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Mon, 15 Apr 2019 18:57:13 +0200 Subject: [PATCH 47/63] mhReposClosure for includeMhAssignment, inputMhAssignment and includeMhProblem --- .../info/kwarc/mmt/stex/STeXAnalysis.scala | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 4fb80b10b1..55240ec1f9 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -56,25 +56,52 @@ trait STeXAnalysis { } } + def mhReposClosure(archive: Archive, parents : Set[File], r : String, b : String) : List[Dependency] = + { + // Single dependency + val nu_dep : List[Dependency] = mhRepos(archive, r, b) + + // Find distant archive given by r. + val distantArchive : Archive = Option(r).map(getArgMap) match + { + case Some(m) => + // Compare mkDep + assert(m.get("mhrepos").isDefined) + val root = archive.root.up.up / m("mhrepos") + controller.addArchive(root) + val thearchive = controller.backend.getArchive(root) + assert(thearchive.isDefined) + thearchive.get + + case None => archive // fallback + } + + val theFile : File = if (archive == distantArchive) { entryToPath(b).toFile } + else { (distantArchive.root / "source" / b).setExtension("tex") } + + val closure : List[Dependency] = getDeps(distantArchive,theFile,parents + theFile) + nu_dep ::: closure + } + protected def toKeyDep(d: Dependency, key: String): Dependency = d match { case FileBuildDependency(_, ar, fp) => FileBuildDependency(key, ar, fp) case fd => fd } - protected def matchPathAndRepository(a: Archive, inFile: File, line: String, parents: Set[File]) : List[Dependency] = + protected def matchPathAndRepo(archive: Archive, inFile: File, line: String, parents: Set[File]) : List[Dependency] = { line match { - case beginModnl(_, _, b) => List(mkFileDep(a, entryToPath(b))) + case beginModnl(_, _, b) => List(mkFileDep(archive, entryToPath(b))) - // ToDo: These four (at least) rely on the included file being in the same directory. Fix that. + // The next four take relative paths so it's okay not to include that. case input(_, _, _, b) => // As below with slight alterations for libinputs. val p = if (line.startsWith("\\lib")) - STeXUtils.getAmbleFile(b + ".tex", a, None) + STeXUtils.getAmbleFile(b + ".tex", archive, None) else (inFile.up / b).setExtension("tex") val d = PhysicalDependency(p) - d :: (if (!parents.contains(p)) getDeps(a, p, parents + p) else Nil) + d :: (if (!parents.contains(p)) getDeps(archive, p, parents + p) else Nil) case includeAssignment(_,_,b) => // "a13" -> File(a13.tex) @@ -83,7 +110,7 @@ trait STeXAnalysis { val pdep : Dependency = PhysicalDependency(pfile) // All dependencies of the included file are now also dependencies of the including file. if (!parents.contains(pfile)) { - pdep :: getDeps(a, pfile, parents + pfile) + pdep :: getDeps(archive, pfile, parents + pfile) } else { List(pdep) } case inputAssignment(_,_,b) => @@ -91,7 +118,7 @@ trait STeXAnalysis { val pfile : File = (inFile.up / b).setExtension("tex") val pdep : Dependency = PhysicalDependency(pfile) if (!parents.contains(pfile)) { - pdep :: getDeps(a, pfile, parents + pfile) + pdep :: getDeps(archive, pfile, parents + pfile) } else { List(pdep) } case includeProblem(_,_,b) => @@ -99,7 +126,7 @@ trait STeXAnalysis { val pfile : File = (inFile.up / b).setExtension("tex") val pdep : Dependency = PhysicalDependency(pfile) if (!parents.contains(pfile)) { - pdep :: getDeps(a, pfile, parents + pfile) + pdep :: getDeps(archive, pfile, parents + pfile) } else { List(pdep) } case includeGraphics(_, _, b) => @@ -119,20 +146,20 @@ trait STeXAnalysis { case mhinputRef(_, r, b) => val fp = entryToPath(b) - val alldeps = getDeps(a,fp.toFile,Set.empty) + val alldeps = getDeps(archive,fp.toFile,Set.empty) Option(r) match { - case Some(id) => mkDep(a, id, fp) ::: alldeps - case None => List(mkFileDep(a, fp)) ::: alldeps + case Some(id) => mkDep(archive, id, fp) ::: alldeps + case None => List(mkFileDep(archive, fp)) ::: alldeps } - case tikzinput(_, r, b) => mhRepos(a, r, b).map(toKeyDep(_, "tikzsvg")) + case tikzinput(_, r, b) => mhRepos(archive, r, b).map(toKeyDep(_, "tikzsvg")) - case guse(r, b) => mkDep(a, r, entryToPath(b)) + case guse(r, b) => mkDep(archive, r, entryToPath(b)) // These three are supposed to work exactly alike, see https://github.com/UniFormal/MMT/issues/465. - case inputMhAssignment(_,r,b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) - case includeMhAssignment(_,r,b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) - case includeMhProblem(_, r, b) => mhRepos(a, r, b) ::: getDeps(a,entryToPath(b).toFile,Set.empty) + case inputMhAssignment(_,r,b) => mhReposClosure(archive, parents, r, b) + case includeMhAssignment(_,r,b) => mhReposClosure(archive, parents, r, b) + case includeMhProblem(_, r, b) => mhReposClosure(archive, parents, r, b) case _ => Nil } @@ -214,7 +241,7 @@ trait STeXAnalysis { val sl = matchSmsEntry(a, l) sl.foreach(combine) if (key != "sms") { - val od = matchPathAndRepository(a, in, l, parents) + val od = matchPathAndRepo(a, in, l, parents) od.foreach(d => combine(STeXStructure(Nil, List(d)))) } } From dd6fbb6d8ff6df7bfd2705d38e9be45c91b64cbb Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Tue, 16 Apr 2019 15:10:24 +0200 Subject: [PATCH 48/63] revamp method rebuildNeeded --- .../mmt/api/archives/BuildDependencies.scala | 1 + .../kwarc/mmt/api/archives/BuildTarget.scala | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildDependencies.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildDependencies.scala index 2f871b72ad..739e0b3c4e 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildDependencies.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildDependencies.scala @@ -29,6 +29,7 @@ sealed abstract class BuildDependency extends Dependency { */ case class FileBuildDependency(key: String, archive: Archive, inPath: FilePath) extends BuildDependency { def toJString: String = inPath.toString + " (" + key + ")" + // ToDo: The controller isn't actually used here, I think it can go. -- jbetzend def getErrorFile(controller: Controller): File = (archive / errors / key / inPath).addExtension("err") } diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index 51a7895b12..f07c9bcb50 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -478,21 +478,33 @@ abstract class TraversingBuildTarget extends BuildTarget { /** auxiliary method of runBuildTaskIfNeeded: implements the semantics of Update to determine whether a task has to be built */ // TODO specify the semantics of Update - private def rebuildNeeded(deps: Set[Dependency], bt: BuildTask, level: Level): Boolean = { - val errorFile = bt.asDependency.getErrorFile(controller) - val errs = hadErrors(errorFile, level) - val mod = modified(bt.inFile, errorFile) - val ex = !bt.outFile.exists() - level <= Level.Force || ex || mod || errs || - deps.exists { + private def rebuildNeeded(deps: Set[Dependency], bt: BuildTask, level: Level): Boolean = + { + val errorFile : File = bt.asDependency.getErrorFile(controller) + + lazy val forced : Boolean = level <= Level.Force + lazy val outex : Boolean = { + // usually, we build outfiles, that don't exist. + // However, for .deps files, we don't need to. + val ext = bt.outFile.getExtension + !bt.outFile.exists() && (if (ext.isDefined) { ext.get != "deps" } else true) + } + lazy val modded : Boolean = modified(bt.inFile, errorFile) + lazy val errors : Boolean = hadErrors(errorFile, level) + + lazy val depsModded : Boolean = deps.exists { case bd: BuildDependency => val errFile = bd.getErrorFile(controller) modified(errFile, errorFile) case PhysicalDependency(fFile) => modified(fFile, errorFile) case _ => false // for now - } || bt.isDir && bt.children.getOrElse(Nil).exists { bf => + } + + lazy val isDir : Boolean = bt.isDir && bt.children.getOrElse(Nil).exists { bf => modified(bf.asDependency.getErrorFile(controller), errorFile) } + + forced || outex || modded || errors || depsModded || isDir } /** auxiliary method of runBuildTaskIfNeeded */ From bd7fa95ed3fd342ced29ea08a961ae576c4fd0d6 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 18 Apr 2019 15:44:58 +0200 Subject: [PATCH 49/63] forget tikzsvg dependencies unless in latexml and small refactorings --- .../info/kwarc/mmt/api/archives/BuildTarget.scala | 15 +++++++++------ .../src/info/kwarc/mmt/stex/STeXAnalysis.scala | 14 ++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala index f07c9bcb50..5d811a61e5 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/BuildTarget.scala @@ -492,14 +492,17 @@ abstract class TraversingBuildTarget extends BuildTarget { lazy val modded : Boolean = modified(bt.inFile, errorFile) lazy val errors : Boolean = hadErrors(errorFile, level) - lazy val depsModded : Boolean = deps.exists { - case bd: BuildDependency => - val errFile = bd.getErrorFile(controller) - modified(errFile, errorFile) - case PhysicalDependency(fFile) => modified(fFile, errorFile) - case _ => false // for now + def singleDepModded(dep : Dependency) : Boolean = dep match { + case bd: BuildDependency => + val errFile = bd.getErrorFile(controller) + modified(errFile, errorFile) + + case PhysicalDependency(fFile) => modified(fFile, errorFile) + case _ => false // for now } + lazy val depsModded : Boolean = deps.exists(singleDepModded) + lazy val isDir : Boolean = bt.isDir && bt.children.getOrElse(Nil).exists { bf => modified(bf.asDependency.getErrorFile(controller), errorFile) } diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala index 55240ec1f9..e4656afa15 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/STeXAnalysis.scala @@ -152,7 +152,7 @@ trait STeXAnalysis { case None => List(mkFileDep(archive, fp)) ::: alldeps } - case tikzinput(_, r, b) => mhRepos(archive, r, b).map(toKeyDep(_, "tikzsvg")) + case tikzinput(_, r, b) => mhRepos(archive, r, b).map(toKeyDep(_, key = "tikzsvg")) case guse(r, b) => mkDep(archive, r, entryToPath(b)) @@ -240,9 +240,15 @@ trait STeXAnalysis { { val sl = matchSmsEntry(a, l) sl.foreach(combine) - if (key != "sms") { - val od = matchPathAndRepo(a, in, l, parents) - od.foreach(d => combine(STeXStructure(Nil, List(d)))) + if (key != "sms") + { + val od : List[Dependency] = matchPathAndRepo(a, in, l, parents) + // Forget all tikzsvg dependencies unless we're inside a latexml target. + val odp : List[Dependency] = if (key == "latexml") { od } else { od.filter { + case FileBuildDependency(akey, _, _) => akey != "tikzsvg" + case _ => true + }} + odp.foreach(d => combine(STeXStructure(Nil, List(d)))) } } } From 482e0ddb2ce8b489e3ddd5fa85ec46c2e987a564 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 18 Apr 2019 17:39:59 +0200 Subject: [PATCH 50/63] more refactorings --- .../kwarc/mmt/stex/LaTeXBuildTarget.scala | 32 ++++++++++--------- .../src/info/kwarc/mmt/stex/LaTeXML.scala | 16 ++++++---- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala index 8ad850e5aa..276d4302ae 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXBuildTarget.scala @@ -19,19 +19,21 @@ import scala.concurrent.duration._ import scala.sys.process.{ProcessBuilder, ProcessLogger} /** common code for sms, latexml und pdf generation */ -abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments { - val localpathsFile = "localpaths.tex" - val inDim = source - var pipeOutput: Boolean = false - val pipeOutputOption: String = "pipe-worker-output" +abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis with BuildTargetArguments +{ + val localpathsFile : String = "localpaths.tex" + val inDim : ArchiveDimension = source + var pipeOutput : Boolean = false + val pipeOutputOption : String = "pipe-worker-output" + /** timout in seconds */ - private val timeoutDefault: Int = 300 - protected var timeoutVal: Int = timeoutDefault - protected val timeoutOption: String = "timeout" - protected var nameOfExecutable: String = "" + private val timeoutDefault : Int = 300 + protected var timeoutVal : Int = timeoutDefault + protected val timeoutOption : String = "timeout" + protected var nameOfExecutable : String = "" protected case class LatexError(s: String, l: String) extends ExtensionError(key, s) { - override val extraMessage = l + override val extraMessage : String = l } protected def commonOpts: OptionDescrs = List( @@ -46,9 +48,9 @@ abstract class LaTeXBuildTarget extends TraversingBuildTarget with STeXAnalysis override def start(args: List[String]) { anaStartArgs(args) pipeOutput = optionsMap.get(pipeOutputOption).isDefined - optionsMap.get(timeoutOption).foreach { case v => timeoutVal = v.getIntVal } - optionsMap.get(key).foreach { case v => nameOfExecutable = v.getStringVal } - optionsMap.get("execute").foreach { case v => + optionsMap.get(timeoutOption).foreach(v => timeoutVal = v.getIntVal) + optionsMap.get(key).foreach(v => nameOfExecutable = v.getStringVal) + optionsMap.get("execute").foreach { v => if (nameOfExecutable.isEmpty) nameOfExecutable = v.getStringVal else logError("executable already set by: --" + key + "=" + nameOfExecutable) } @@ -199,7 +201,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { val outDim: ArchiveDimension = source override val outExt = "tex" - override def getFolderOutFile(a: Archive, inPath: FilePath) = a / outDim / inPath + override def getFolderOutFile(a: Archive, inPath: FilePath) : File = a / outDim / inPath // we do nothing for single files def reallyBuildFile(bt: BuildTask): BuildResult = BuildEmpty("nothing to do for files") @@ -251,7 +253,7 @@ abstract class LaTeXDirTarget extends LaTeXBuildTarget { override def buildDepsFirst(a: Archive, up: Update, in: FilePath = EmptyPath) { a.traverse[Unit](inDim, in, TraverseMode(includeFile, includeDir, parallel))({ - case _ => + _ => }, { case (c@Current(inDir, inPath), _) => buildDir(a, inPath, inDir, force = false) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala index 4e4ac853a6..8492c59f72 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala @@ -19,7 +19,7 @@ class AllPdf extends LaTeXDirTarget { BuildResult.empty } - override def estimateResult(bt: BuildTask) = { + override def estimateResult(bt: BuildTask): BuildSuccess = { if (bt.isDir) { val a = bt.archive val ls = getAllFiles(bt).map(f => FileBuildDependency("pdflatex", a, bt.inPath / f)) @@ -259,7 +259,7 @@ class LaTeXML extends LaTeXBuildTarget { var optLevel: Option[Level.Level] = None var msg: List[String] = Nil var newMsg = true - var region = SourceRegion.none + var region : SourceRegion = SourceRegion.none var phase = 1 def phaseToString(p: Int): String = "latexml-" + (p match { @@ -557,14 +557,16 @@ class PdfLatex extends LaTeXBuildTarget { } } -class TikzSvg extends PdfLatex { - override val key = "tikzsvg" - override val outExt = "svg" - override val outDim = source +class TikzSvg extends PdfLatex +{ + override val key : String = "tikzsvg" + override val outExt : String = "svg" + override val outDim : ArchiveDimension = source override def includeDir(n: String): Boolean = n.endsWith("tikz") - override def reallyBuildFile(bt: BuildTask): BuildResult = { + override def reallyBuildFile(bt: BuildTask): BuildResult = + { val pdfFile = bt.inFile.setExtension("pdf") val svgFile = bt.inFile.setExtension("svg") bt.outFile.delete() From 4b8fdcc82665b12e823cd6b29dca07d413c66d5f Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 18 Apr 2019 18:37:36 +0200 Subject: [PATCH 51/63] move SVGs generated from tikz to content dimension --- .../info/kwarc/mmt/api/archives/package.scala | 19 ++++++++++--------- .../src/info/kwarc/mmt/stex/LaTeXML.scala | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/package.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/package.scala index a75039374a..dbaa84c157 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/archives/package.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/archives/package.scala @@ -12,13 +12,14 @@ import archives._ * * The list of currently open archives (from which MMT will load content if needed) is maintained by the [[backend.Backend]]. */ -package object archives { - val source = RedirectableDimension("source") - val content = RedirectableDimension("content") - val narration = RedirectableDimension("narration") - val relational = RedirectableDimension("relational") - val notational = RedirectableDimension("notations") - val errors = RedirectableDimension("errors") - val export = RedirectableDimension("export") - val flat = RedirectableDimension("flat") +package object archives +{ + val source : ArchiveDimension = RedirectableDimension("source") + val content : ArchiveDimension = RedirectableDimension("content") + val narration : ArchiveDimension = RedirectableDimension("narration") + val relational : ArchiveDimension = RedirectableDimension("relational") + val notational : ArchiveDimension = RedirectableDimension("notations") + val errors : ArchiveDimension = RedirectableDimension("errors") + val export : ArchiveDimension = RedirectableDimension("export") + val flat : ArchiveDimension = RedirectableDimension("flat") } diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala index 8492c59f72..f6b5b32f69 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala @@ -561,17 +561,26 @@ class TikzSvg extends PdfLatex { override val key : String = "tikzsvg" override val outExt : String = "svg" - override val outDim : ArchiveDimension = source + override val outDim : ArchiveDimension = Dim("content", "images") override def includeDir(n: String): Boolean = n.endsWith("tikz") override def reallyBuildFile(bt: BuildTask): BuildResult = { - val pdfFile = bt.inFile.setExtension("pdf") - val svgFile = bt.inFile.setExtension("svg") + // SVG file is generated content and goes elsewhere. + val svgFile : File = bt.outFile + + // ToDo: This pdf is ~technically~ also generated content, + // suggesting it should be elsewhere. But a bunch + // of things assume it's a sibling from the inFile so + // its complicated. Link? Copy? Ignore? + val pdfFile : File = bt.inFile.setExtension("pdf") + + bt.outFile.delete() createLocalPaths(bt) val output = new StringBuffer() + try { val exit = runPdflatex(bt, output) if (exit != 0) { @@ -583,6 +592,7 @@ class TikzSvg extends PdfLatex val exitConvert = timeout(pb, procLogger(output, pipeOutput = pipeOutput)) if (exitConvert == 0 && svgFile.length() > 0) logSuccess(bt.outPath) + else { bt.errorCont(LatexError(if (exitConvert != 0) "exit code " + exitConvert else "no svg created", output.toString)) From e4d5f1106066df12eb17803b137483ce44913c15 Mon Sep 17 00:00:00 2001 From: Jonas Betzendahl Date: Thu, 18 Apr 2019 18:43:38 +0200 Subject: [PATCH 52/63] removed 'images' from outDimension --- src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala index f6b5b32f69..a07f059690 100644 --- a/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala +++ b/src/mmt-stex/src/info/kwarc/mmt/stex/LaTeXML.scala @@ -561,21 +561,18 @@ class TikzSvg extends PdfLatex { override val key : String = "tikzsvg" override val outExt : String = "svg" - override val outDim : ArchiveDimension = Dim("content", "images") + override val outDim : ArchiveDimension = content override def includeDir(n: String): Boolean = n.endsWith("tikz") override def reallyBuildFile(bt: BuildTask): BuildResult = { - // SVG file is generated content and goes elsewhere. - val svgFile : File = bt.outFile - // ToDo: This pdf is ~technically~ also generated content, // suggesting it should be elsewhere. But a bunch // of things assume it's a sibling from the inFile so // its complicated. Link? Copy? Ignore? val pdfFile : File = bt.inFile.setExtension("pdf") - + val svgFile : File = bt.outFile bt.outFile.delete() createLocalPaths(bt) From e7dabd00d6653e3baba03ee25d21f91460c7501e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Tue, 23 Apr 2019 10:52:06 +0200 Subject: [PATCH 53/63] Set up CodeGenerator for Jane use case --- .../src/info/kwarc/mmt/sql/SQLBridge.scala | 21 ++--- .../src/info/kwarc/mmt/sql/Syntax.scala | 3 +- .../info/kwarc/mmt/sql/codegen/CodeFile.scala | 10 ++- .../kwarc/mmt/sql/codegen/CodeGenerator.scala | 25 +++--- .../kwarc/mmt/sql/codegen/ColumnCode.scala | 35 +++++--- .../kwarc/mmt/sql/codegen/DatabaseCode.scala | 73 ++++++++------- .../info/kwarc/mmt/sql/codegen/JoinCode.scala | 11 +++ .../kwarc/mmt/sql/codegen/TableCode.scala | 88 +++++++++---------- .../kwarc/mmt/sql/codegen/TableInfo.scala | 20 +++++ .../info/kwarc/mmt/sql/codegen/Tables.scala | 54 ++++++++++++ 10 files changed, 230 insertions(+), 110 deletions(-) create mode 100644 src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala create mode 100644 src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala create mode 100644 src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala index d9351ba598..a297b9ad33 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/SQLBridge.scala @@ -24,12 +24,13 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin def theoryToTable(t: Theory): Table = { val datasetName: Option[String] = SchemaLang.datasetNameAnnotator.get(t) val schemaGroup: Option[String] = SchemaLang.schemaGroupAnnotator.get(t) + val includes = t.getAllIncludesWithoutMeta.map(_._1) val cols = t.getConstants flatMap { case c: Constant => constantToColumn(c).toList } - Table(t.path, datasetName, schemaGroup, cols) + Table(t.path, datasetName, schemaGroup, cols, includes) } - + def constantToColumn(c: Constant): Option[Column] = { val codecTerm = Codecs.codecAnnotator.get(c).getOrElse(return None) // no codec given val foreignKeyTerm: Option[MPath] = SchemaLang.foreignKeyAnnotator.get(c).map(_.toMPath) @@ -63,20 +64,20 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin val isHidden = SchemaLang.hidden.get(c) val collection = if (SchemaLang.collection.get(c)) { Some(CollectionInfo(c.path,c.metadata)) - } else + } else None // TODO foreign key val col = Column(c.path, mathType, codecTermChecked, dbtype, foreignKeyTerm, isOpaque, !isHidden, collection) Some(col) } - + private def defaultCodec(rt: RealizedType): Term = rt.synType match { case OMS(MathData.bool) => OMS(Codecs.BoolIdent) case OMS(MathData.int) => OMS(Codecs.IntIdent) case OMS(MathData.string) => OMS(Codecs.StringIdent) case OMS(MathData.uuid) => OMS(Codecs.UUIDIdent) } - + /** * converts a term over a schema theory into the corresponding SQL expression and the codec that allows decoding it */ @@ -107,7 +108,7 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin val (params,argsToEncode) = args.splitAt(cp.mathParams.length) /* TODO recursive call should alternate with matching if the free variables in an inCodec have already been solved, they are passed as the expected codec - if an expected codec is passed, it is matched against the outCodec */ + if an expected codec is passed, it is matched against the outCodec */ val (argsE, argsC) = args.map {a => termToExpr(table, a, None)}.unzip val res = matcher(context, cp.context) {eq => ((params zip cp.mathParams) forall {case (a,b) => eq(a,b)}) && @@ -124,9 +125,9 @@ class SQLBridge(controller: Controller, rules: RuleSet, commProps: List[Commutin throw CannotTranslate(tm) } } - + def termToFilter(table: Table, tm: Term): Filter = { - // determine type and codec for each subterm, then translate according to commutativity annotations + // determine type and codec for each subterm, then translate according to commutativity annotations val (e,_) = termToExpr(table, tm, None) Filter(e) } @@ -145,8 +146,8 @@ object SQLBridge { case b: BaseType[_] => OMS(utils.invlistmap(basetypes, b).get) case ArrayType(t) => array(typeToTerm(t)) } - - /** MPath of example schema */ + + /** MPath of example schema */ val example = SchemaLang._base ? "Example" /** convert a theory to a table */ def test(thyP: MPath) = { diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala index a5172bedf1..267df820f2 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/Syntax.scala @@ -11,8 +11,9 @@ import scala.language.existentials * @param schemaGroup metadatum * @param columns sequence of all columns * @param collections sequence of all collections + * @param includes list of all includes (in particular, referenced tables) */ -case class Table(path: MPath, datasetName: Option[String], schemaGroup: Option[String], columns: Seq[Column]) { +case class Table(path: MPath, datasetName: Option[String], schemaGroup: Option[String], columns: Seq[Column], includes: List[MPath]) { /** db name of the table, underscore style */ def name = path.name.toString /** retrieve all columns that are collections */ diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala index 1185d1faab..1cc8714ec8 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeFile.scala @@ -7,10 +7,12 @@ case class CodeFile(replacements: Map[String, String], templatePath: String, des def writeToFile(write: Boolean): Unit = { // TODO should it be line by line? if (write) { - val template = scala.io.Source.fromFile(templatePath).mkString - val result = replacements.foldLeft(template)((str, mapItem) => { - str.replaceAllLiterally(mapItem._1, mapItem._2) - }) + val handle = scala.io.Source.fromFile(templatePath) + val result = try { + replacements.foldLeft(handle.mkString)((str, mapItem) => { + str.replaceAllLiterally(mapItem._1, mapItem._2) + }) + } finally handle.close() val actualDestination = destinationPath.getOrElse(templatePath) val pw = new PrintWriter(new java.io.File(actualDestination)) diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala index f35204de6e..e12468907a 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala @@ -1,8 +1,11 @@ package info.kwarc.mmt.sql.codegen +import info.kwarc.mmt.api.MPath import info.kwarc.mmt.api.frontend.Controller import info.kwarc.mmt.api.modules.Theory -import info.kwarc.mmt.sql.{SQLBridge, SchemaLang, Table} +import info.kwarc.mmt.sql.{Column, SQLBridge, SchemaLang, Table} + +import scala.collection.mutable object CodeGenerator { @@ -27,23 +30,23 @@ object CodeGenerator { ) val jdbcInfo = JDBCInfo("jdbc:postgresql://localhost:5432/discretezoo2", "discretezoo", "D!screteZ00") val prefix = "MBGEN" - val generate = true val controller = Controller.make(true, true, List()) // remove later controller.handleLine(s"build $archiveId mmt-omdoc") - val tableCodes = controller.backend.getArchive(archiveId).get.allContent.map(controller.getO).collect({ - case Some(theory : Theory) if isInputTheory(theory, schemaGroup) => { - SQLBridge.test2(theory.path, controller) match { - case table: Table => Some(TableCode(prefix, dirPaths.dbPackagePath, table)) - case _ => None - } - } - }).collect({ case Some(t: TableCode) => t }) + // add input theories to search + val paths = controller.backend.getArchive(archiveId).get.allContent.map(controller.getO).collect({ + case Some(theory : Theory) if isInputTheory(theory, schemaGroup) => theory.path + }) + + val tables = new Tables(prefix, dirPaths.dbPackagePath, p => SQLBridge.test2(p, controller)) + val tableCodes = tables.processTables(paths) + + tables.print() val dbCode = DatabaseCode(dirPaths, prefix, tableCodes, jdbcInfo) - dbCode.writeAll(generate) + dbCode.writeAll(true) } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala index 62a8d20188..a52ba0ccd6 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala @@ -3,10 +3,9 @@ package info.kwarc.mmt.sql.codegen import info.kwarc.mmt.api.objects.{OMA, OMS} import info.kwarc.mmt.sql.Column -case class ColumnCode(column: Column) { +case class ColumnCode(column: Column, join: Option[JoinCode] = None) { private def nameQuotes = s""""${column.name}"""" - private def nameDb: String = column.name.toUpperCase private def typeString: String = { if (column.dbtype.toString == "List[List[Int]]" || column.dbtype.toString == "List[Int]") s"List[Int]" @@ -18,24 +17,34 @@ case class ColumnCode(column: Column) { case OMA(_, codecArgs) => codecArgs.head.toMPath.name.last.toString } + def name: String = column.name + def nameDb: String = column.name.toUpperCase + def isDisplayedByDefault: Boolean = column.isDisplayedByDefault + // JsonSupport def jsonWriterMapItem: String = s"""Some($nameQuotes -> o.$nameCamelCase.toJson)""" // CaseClass def caseClassField: String = s" $nameCamelCase: $typeString" - def selectMapCaseClass: String = s""" "${column.name}" -> $nameCamelCase""" + def selectMapCaseClass: String = s""" "$name" -> $nameCamelCase""" // PlainQueryObject - def getResultItem: String = typeString match { - case "UUID" => "r.nextObject.asInstanceOf[UUID]" - case "List[Int]" => "r.<<[Seq[Int]].toList" - case _ => "r.<<" + def getResultItem: String = { + if (join.nonEmpty) "None" + else typeString match { + case "UUID" => "r.nextObject.asInstanceOf[UUID]" + case "List[Int]" => "r.<<[Seq[Int]].toList" + case _ => "r.<<" + } } // TableClass - def nameCamelCase: String = "_([a-z\\d])".r.replaceAllIn(column.name, _.group(1).toUpperCase()) - def accessorMethod: String = s"""def $nameCamelCase: Rep[$typeString] = column[$typeString]("$nameDb")""" - def selectMapTableClass: String = s""" "${column.name}" -> this.$nameCamelCase""" + def nameCamelCase: String = ColumnCode.camelCase(name) + def accessorMethod: String = { + val fk = join.map(_.fkMethod).map(m => s"\n$m").getOrElse("") + s""" def $nameCamelCase: Rep[$typeString] = column[$typeString]("$nameDb")$fk""" + } + def selectMapTableClass: String = s""" "$name" -> this.$nameCamelCase""" // Frontend def jsonObjectProperties: String = { @@ -48,3 +57,9 @@ case class ColumnCode(column: Column) { } } + +object ColumnCode { + + def camelCase(s: String): String = "_([a-z\\d])".r.replaceAllIn(s, _.group(1).toUpperCase()) + +} diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala index ae8442f4dd..ff9af81c9a 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala @@ -2,7 +2,9 @@ package info.kwarc.mmt.sql.codegen import java.io.PrintWriter -case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode], dbInfo: JDBCInfo) { +import info.kwarc.mmt.api.MPath + +case class DatabaseCode(paths: ProjectPaths, prefix: String, tables: Map[MPath, TableCode], dbInfo: JDBCInfo) { private def writeToFile(f: String, s: String, write: Boolean): Unit = { if (write) { @@ -13,46 +15,57 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode def writeAll(generate: Boolean = true): Unit = { // backend - CodeFile(zooCreateRepl, s"${paths.backendPackagePath}/Create.scala").writeToFile(generate) - CodeFile(jsonSupportRepl, s"${paths.backendPackagePath}/JsonSupport.scala").writeToFile(generate) - CodeFile(zooDbRepl, s"${paths.dbPackagePath}/ZooDb.scala").writeToFile(generate) - tables.foreach(t => { - val tPath = tablePackagePath(t.tableName) + Seq((zooCreateRepl, "Create"), (jsonSupportRepl, "JsonSupport"), (zooDbRepl, "db/ZooDb")) + .foreach(t => CodeFile(t._1, s"${paths.backendPackagePath}/${t._2}.scala").writeToFile(generate)) + val tempDir = s"${paths.dbPackagePath}/temp" + def tableTempPath(s: String) = s"$tempDir/$s.scala" + tables.foreach(mapItem => { + val t = mapItem._2 + val tPath = tablePackagePath(t.info.name) val dir = new java.io.File(tPath) - def tableTempPath(s: String) = s"${paths.dbPackagePath}/temp/$s.scala" - def tableFilePath(s: String) = Some(s"$tPath/${t.tableName}$s.scala") - if (generate) { - if (!dir.exists()) dir.mkdir() - } - val name = t.table.name - CodeFile(t.caseClassRepl, tableTempPath("CaseClass"), tableFilePath("")).writeToFile(generate) - CodeFile(t.plainQueryRepl, tableTempPath("PlainQueryObject"), tableFilePath("PlainQuery")).writeToFile(generate) - CodeFile(t.tableClassRepl, tableTempPath("TableClass"), tableFilePath("Table")).writeToFile(generate) + if (generate && !dir.exists()) dir.mkdir() +// val name = t.table.name + Seq( + (t.caseClassRepl, "CaseClass", ""), + (t.plainQueryRepl, "PlainQueryObject", "PlainQuery"), + (t.tableClassRepl, "TableClass", "Table") + ).foreach(f => + CodeFile(f._1, tableTempPath(f._2), Some(s"$tPath/${t.info.name}${f._3}.scala")).writeToFile(generate) + ) + }) + + // delete temp dir + Seq(tableTempPath("CaseClass"), tableTempPath("PlainQueryObject"), tableTempPath("TableClass")).foreach(tp => { + val temp = new java.io.File(tp) + temp.delete() }) + val dir = new java.io.File(tempDir) + println("delete:", dir.delete()) + // frontend - writeToFile(s"${paths.frontendPath}/objectProperties.json", objectPropertiesJSON, generate) - writeToFile(s"${paths.frontendPath}/collectionsData.json", collectionDataJSON, generate) - writeToFile(s"${paths.frontendPath}/settings.json", settingsJSON, generate) + writeToFile(s"${paths.frontendPath}/config/objectProperties.json", objectPropertiesJSON, generate) + writeToFile(s"${paths.frontendPath}/config/collectionsData.json", collectionDataJSON, generate) + writeToFile(s"${paths.frontendPath}/config/settings.json", settingsJSON, generate) } // helpers - private def tablePackagePrefix: String = name + private def tablePackagePrefix: String = prefix private def tablePackagePath(name: String) = s"${paths.dbPackagePath}/$tablePackagePrefix$name" - private val tableObjects = tables.map(_.zooDbObject).mkString(",\n") + private val tableObjects = tables.map(_._2.dbTableObject).mkString("\n") // backend code private def zooCreateRepl: Map[String, String] = Map( "//tableObjects" -> tableObjects, - "//importTablePackages" -> tables.map(_.zooCreateImport).mkString(",\n"), - "//schemaCreateList" -> tables.map(_.zooSchemaCreate).mkString(", ") + "//importTablePackages" -> tables.map(_._2.tableClassImport).mkString("\n"), + "//schemaCreateList" -> tables.map(_._2.zooSchemaCreate).mkString(", ") ) private def jsonSupportRepl: Map[String, String] = Map( - "//importTablePackages" -> tables.map(_.jsonSupportImport).mkString(",\n"), - "//casesToJson" -> tables.map(_.jsonSupportMap).mkString("\n") + "//importTablePackages" -> tables.map(_._2.jsonSupportImport).mkString("\n"), + "//casesToJson" -> tables.map(_._2.jsonSupportMap).mkString("\n") ) @@ -61,15 +74,15 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode "%jdbc%" -> dbInfo.jdbc, "%user%" -> dbInfo.user, "%pass%" -> dbInfo.pass, - "//importTablePackages" -> tables.map(_.zooDbImport).mkString(",\n"), - "//getQueryMatches" -> tables.map(_.dbGetQueryMatches).mkString("\n"), - "//countQueryMatches" -> tables.map(_.dbCountQueryMatches).mkString("\n") + "//importTablePackages" -> tables.map(_._2.zooDbImport).mkString("\n"), + "//getQueryMatches" -> tables.map(_._2.dbGetQueryMatches).mkString("\n"), + "//countQueryMatches" -> tables.map(_._2.dbCountQueryMatches).mkString("\n") ) // frontend jsons private def objectPropertiesJSON: String = { - val code = tables.map(_.jsonObjectProperties).mkString(",\n") + val code = tables.map(_._2.jsonObjectProperties).mkString(",\n") s"""{ |$code |} @@ -77,7 +90,7 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode } private def collectionDataJSON: String = { - val code = tables.map(_.collectionsData).mkString(",\n") + val code = tables.map(_._2.collectionsData).mkString(",\n") s"""{ |$code |} @@ -85,7 +98,7 @@ case class DatabaseCode(paths: ProjectPaths, name: String, tables: Seq[TableCode } private def settingsJSON: String = { - val code = tables.map(_.defaultColumns).mkString(",\n") + val code = tables.map(_._2.defaultColumns).mkString(",\n") s"""{ | "title": "Search", | "defaultColumns": { diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala new file mode 100644 index 0000000000..96191c5de7 --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala @@ -0,0 +1,11 @@ +package info.kwarc.mmt.sql.codegen + +case class JoinCode(tbInfo: TableInfo, columnName: String) { + + def fkMethod: String = { + val camelName = ColumnCode.camelCase(columnName) + val fk = s""" def fk${tbInfo.name} = foreignKey("${tbInfo.dbTableName}_FK", $camelName, ${tbInfo.tableObject})""" + s"""$fk(_.ID, onUpdate=ForeignKeyAction.Restrict, onDelete=ForeignKeyAction.Cascade)""" + } + +} diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala index 869e298ff0..b6f58f15f7 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala @@ -2,69 +2,69 @@ package info.kwarc.mmt.sql.codegen import info.kwarc.mmt.sql.{Column, Table} -case class TableCode(prefix: String, dbPackagePath: String, table: Table) { - - def tableName: String = table.name - def tablePackageName : String = prefix + tableName - - private def packageString = s"xyz.discretezoo.web.db.$tablePackageName" // package for the table specific files - private def columnCodeList: Seq[ColumnCode] = table.columns.map(ColumnCode) - private def tableObject: String = s"tb$tableName" +case class TableCode( info: TableInfo, + dbPackagePath: String, + columns: Seq[ColumnCode], + datasetName: String, + joins: Seq[TableInfo] +) { private val baseRepl: Map[String, String] = Map( - "//package" -> s"package $packageString", - "%caseClass%" -> caseClass, - "%dbTableName%" -> s"${prefix}_${tableName.toUpperCase}" // table name in the database + "//package" -> s"package ${info.packageString}", + "%caseClass%" -> info.caseClass ) // CaseClass - private def caseClass: String = table.name def caseClassRepl: Map[String, String] = baseRepl ++ Map( - "//cols" -> columnCodeList.map(_.caseClassField).mkString(",\n"), - "//selectMap" -> columnCodeList.map(_.selectMapCaseClass).mkString(",\n") + "//cols" -> columns.map(_.caseClassField).mkString(",\n"), + "//selectMap" -> columns.map(_.selectMapCaseClass).mkString(",\n") ) // PlainQueryObject - private def plainQueryObject: String = s"${tableName}PlainQuery" + private def plainQueryObject: String = s"${info.name}PlainQuery" def plainQueryRepl: Map[String, String] = baseRepl ++ Map( "%plainQueryObject%" -> plainQueryObject, - "%tableName%" -> tableName, - "%getResultParameters%" -> table.columns.map(c => ColumnCode(c).getResultItem).mkString(", ") + "%tableName%" -> info.name, + "%getResultParameters%" -> columns.map(_.getResultItem).mkString(", "), + "%sqlFrom%" -> info.dbTableName, // table name in the database + "//columns" -> ("ID" +: columns.filter(_.join.isEmpty).map(_.nameDb)).map(c => s""" |"${info.dbTableName}"."$c"""").mkString(",\n") ) // TableClass - private def tableClass = s"${tableName}Table" def tableClassRepl: Map[String, String] = baseRepl ++ Map( - "//accessorMethods" -> columnCodeList.map(_.accessorMethod).mkString("\n"), - "//caseClassMapParameters" -> columnCodeList.map(_.nameCamelCase).mkString(" ::\n"), - "//selectMap" -> columnCodeList.map(_.selectMapTableClass).mkString(",\n"), - "%tableClass%" -> tableClass + "//joinImports" -> joins.map(_.joinImport).mkString("\n"), + "//joinQueries" -> joins.map(_.joinQuery).mkString("\n"), + "//accessorMethods" -> columns.map(_.accessorMethod).mkString("\n"), + "//caseClassMapParameters" -> columns.map(_.nameCamelCase).mkString(" ::\n"), + "//selectMap" -> columns.map(_.selectMapTableClass).mkString(",\n"), + "%dbTableName%" -> info.dbTableName, // table name in the database + "%tableClass%" -> info.tableClass ) // Create - def zooCreateImport: String = s"import $packageString.$tableClass" - def zooSchemaCreate: String = s"$tableObject.schema.create" + def tableClassImport: String = s"import ${info.packageString}.${info.tableClass}" + def zooSchemaCreate: String = s"${info.tableObject}.schema.create" // ZooDb - def zooDbImport: String = s"import $packageString.{$plainQueryObject, $tableClass}" - def zooDbObject: String = s"object $tableObject extends TableQuery(new $tableClass(_))" + def zooDbImport: String = s"import ${info.packageString}.{$plainQueryObject, ${info.tableClass}}" + def dbTableObject: String = s" object ${info.tableObject} extends TableQuery(new ${info.tableClass}(_))" def dbCountQueryMatches: String = - s""" case ("$tableObject", true) => $plainQueryObject.count(qp) - | case ("$tableObject", false) => tb$tableName.dynamicQueryCount(qp).length.result""".stripMargin + s""" case ("${info.tableObject}", true) => $plainQueryObject.count(qp) + | case ("${info.tableObject}", false) => tb${info.name}.dynamicQueryCount(qp).length.result""".stripMargin def dbGetQueryMatches: String = - s""" case ("$tableObject", true) => $plainQueryObject.get(rp) - | case ("$tableObject", false) => tb$tableName.dynamicQueryResults(rp).result""".stripMargin + s""" case ("${info.tableObject}", true) => $plainQueryObject.get(rp) + | case ("${info.tableObject}", false) => tb${info.name}.dynamicQueryResults(rp).result""".stripMargin -// TODO def inCollectionMap: String = collections.map(_.inCollectionItem).mkString(",\n") + // TODO def inCollectionMap: String = collections.map(_.inCollectionItem).mkString(",\n") // JsonSupport - def jsonSupportImport: String = s"import $packageString.$caseClass" + def jsonSupportImport: String = s"import ${info.packageString}.${info.caseClass}" def jsonSupportMap: String = { - val columns = columnCodeList.map(c => s" ${c.jsonWriterMapItem}").mkString(",\n") - s"""case o: $caseClass => JsObject( + val cols = columns.map(c => s" ${c.jsonWriterMapItem}").mkString(",\n") + s"""case o: ${info.caseClass} => JsObject( | List( - |$columns + |$cols | ).flatten: _* | )""".stripMargin } @@ -72,26 +72,26 @@ case class TableCode(prefix: String, dbPackagePath: String, table: Table) { // from here on react stuff def jsonObjectProperties: String = { - val columns = columnCodeList.map(_.jsonObjectProperties).mkString(",\n") - s""""$tableObject": { - |$columns + val cols = columns.map(_.jsonObjectProperties).mkString(",\n") + s""""${info.tableObject}": { + |$cols |} """.stripMargin } def collectionsData: String = { - s""""$tableObject": { - | "$tableName": { - | "id": "$tableName", - | "name": "${table.datasetName}" + s""""${info.tableObject}": { + | "${info.name}": { + | "id": "${info.name}", + | "name": "$datasetName" | } |} """.stripMargin } def defaultColumns: String = { - val columns = table.columns.filter(_.isDisplayedByDefault).map(c => s""""${c.name}"""").mkString(", ") - s""" "$tableObject": [$columns]""".stripMargin + val cols = columns.filter(_.isDisplayedByDefault).map(c => s""""${c.name}"""").mkString(", ") + s""" "${info.tableObject}": [$cols]""".stripMargin } } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala new file mode 100644 index 0000000000..d0a88496bb --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala @@ -0,0 +1,20 @@ +package info.kwarc.mmt.sql.codegen + +case class TableInfo(prefix: String, name: String) { + + def tablePackageName: String = prefix + name + def dbTableName: String = s"${prefix}_${name.toUpperCase}" + def packageString = s"xyz.discretezoo.web.db.$tablePackageName" // package for the table specific files + + def caseClass: String = name + def tableObject: String = s"tb$name" + def tableClass: String = s"${name}Table" + + def joinImport: String = s"import $packageString.$tableClass" + def joinQuery: String = s" val $tableObject = TableQuery[$tableClass]" + + def dbObject(name: String): String = { + s"object $tableObject extends TableQuery(new $tableClass(_))" + } + +} diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala new file mode 100644 index 0000000000..e3e86375a2 --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala @@ -0,0 +1,54 @@ +package info.kwarc.mmt.sql.codegen + +import info.kwarc.mmt.api.MPath +import info.kwarc.mmt.sql.Table + +import scala.collection.mutable + +class Tables(prefix: String, dbPackagePath: String, readerCallback: MPath => Any) { + + // - None: haven't attempted retrieval yet + // - Some(None): retrieved, not table + private val pathsToTables: mutable.Map[MPath, Option[Option[TableCode]]] = mutable.Map[MPath, Option[Option[TableCode]]]() + + def processTables(paths: Seq[MPath] = Seq()): Map[MPath, TableCode] = { + if (paths.nonEmpty) paths.foreach(addPath) + while (unprocessed.nonEmpty) { + unprocessed.foreach(path => { + readerCallback(path) match { + case table: Table => + table.includes.foreach(addPath) + processPath(path, Some(getTableCode(table))) + case _ => processPath(path, None) + } + }) + } + pathsToTables.collect({ case (p: MPath, Some(Some(t: TableCode))) => (p, t) }).toMap + } + + private def getTableCode(table: Table): TableCode = { + val joins = table.columns.map(c => (c, c.foreignKey)) + .collect({ case (c, Some(p: MPath)) => (c, readerCallback(p))}) + .collect({ case (c, t: Table) => JoinCode(TableInfo(prefix, t.name), c.name) }) + val cols = table.columns.map(c => ColumnCode(c, joins.find(_.columnName == c.name))) + TableCode(TableInfo(prefix, table.name), dbPackagePath, cols, table.datasetName.getOrElse(""), joins.map(_.tbInfo)) + } + + private def addPath(p: MPath): Unit = { + if (!pathsToTables.contains(p)) pathsToTables += (p -> None) + } + + private def processPath(p: MPath, t: Option[TableCode]): Unit = { + if (pathsToTables.contains(p)) pathsToTables -= p + pathsToTables += (p -> Some(t)) + } + + private def unprocessed: Seq[MPath] = pathsToTables.collect({ case (p, None) => p }).toSeq + + def print(): Unit = pathsToTables.foreach({ + case (p, None) => println(s"Unchecked: $p") + case (p, Some(None)) => println(s"Not table: $p") + case (p, Some(Some(t))) => println(s"Table: ${t.info.name}") + }) + +} From e6fa4b852bf948d00d31a8454295943d4c19d3cf Mon Sep 17 00:00:00 2001 From: Makarius Date: Wed, 1 May 2019 10:51:34 +0200 Subject: [PATCH 54/63] more robust Isabelle tool --- src/mmt-isabelle/lib/Tools/mmt_build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mmt-isabelle/lib/Tools/mmt_build b/src/mmt-isabelle/lib/Tools/mmt_build index 69278fde9d..327bd779fd 100755 --- a/src/mmt-isabelle/lib/Tools/mmt_build +++ b/src/mmt-isabelle/lib/Tools/mmt_build @@ -16,8 +16,8 @@ cd "$ISABELLE_MMT_ROOT/deploy" rm -rf tmp mkdir tmp cd tmp -jar xf ../mmt.jar +isabelle_jdk jar xf ../mmt.jar rm -r isabelle org/jline org/tukaani scala -jar cf ../mmt.jar . +isabelle_jdk jar cf ../mmt.jar . cd .. rm -rf tmp From 6b3eedad5d5410579e13b1d9076ed0480baa0486 Mon Sep 17 00:00:00 2001 From: Florian Rabe Date: Mon, 6 May 2019 11:11:52 +0200 Subject: [PATCH 55/63] --- src/latex-mmt/paper/conc.tex | 4 +- src/latex-mmt/paper/copyright.pdf | Bin 0 -> 177468 bytes src/latex-mmt/paper/intro.tex | 9 ++-- src/latex-mmt/paper/paper.pdf | Bin 245850 -> 251531 bytes src/latex-mmt/paper/paper.tex | 5 +- src/latex-mmt/paper/paper.tex.mmt | 2 +- src/latex-mmt/paper/system.tex | 73 +++++++++++++++++------------- 7 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 src/latex-mmt/paper/copyright.pdf diff --git a/src/latex-mmt/paper/conc.tex b/src/latex-mmt/paper/conc.tex index 79e099d6ae..ee2abb8884 100644 --- a/src/latex-mmt/paper/conc.tex +++ b/src/latex-mmt/paper/conc.tex @@ -1,6 +1,6 @@ We have presented a system that allows embedding formal \mmt content inside \latex documents. The formal content is type-checked in a way that does not affect any existing \latex work flows and results in higher quality \latex than could be easily produced manually. -Moreover, the formal content may use and be used by any \mmt content formalized elsewhere, which allows the interlinking across document formats. +Moreover, the formal content may use and be used by any \mmt content formalized elsewhere, which allows interlinking across document formats. Of course, we are not able to verify the informal parts of a document this way --- only those formulas that are written in \mmt syntax are checked. But our design supports both gradual formalization and parallel formal-informal representations. @@ -11,4 +11,4 @@ It is intriguing to apply the same technology to formal proofs. This is already possible for formal proof terms, but those often bear little resemblance to informal proofs. Once \mmt supports a language for structured proofs, that could be used to write formal proofs in \mmttex. -Morever, future work could apply \mmt as a middleware between \latex and other tools, e.g., \mmt could run a computation through a computer algebra to verify a computation in \latex. +Morever, future work could apply \mmt as a middleware between \latex and other tools, e.g., \mmt could run a computation through a computer algebra system to verify a computation in \latex. diff --git a/src/latex-mmt/paper/copyright.pdf b/src/latex-mmt/paper/copyright.pdf new file mode 100644 index 0000000000000000000000000000000000000000..16bcbfde42f206810a265d3912cf639275469e58 GIT binary patch literal 177468 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%0DQB5Gx6E*Lt{L`v@z#GDln4dOej+je^LE#GiRA2 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%0A2m=No`wN( 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

2vU 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

)tix!H2VK_9lvPO|O+5)TRw*G*XVDCQl$w@ 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 literal 0 HcmV?d00001 diff --git a/src/latex-mmt/paper/intro.tex b/src/latex-mmt/paper/intro.tex index 1ba92bbb3f..d9ade3c20e 100644 --- a/src/latex-mmt/paper/intro.tex +++ b/src/latex-mmt/paper/intro.tex @@ -3,10 +3,14 @@ A major open problem in mathematical document authoring is to elegantly combine formal and informal mathematical knowledge. Multiple proof assistants and controlled natural language systems have developed custom formats for that purpose, e.g., \cite{isabelle_documentoriented,mizar,plato,naproche}. +Other languages $L$ allow for integrating \latex into $L$-source files in a literate programming style (e.g., lhs2tex\footnote{\url{https://www.andres-loeh.de/lhs2tex/}} for Haskell) or for integrating $L$-source chunks into \latex files (e.g., SageTeX\footnote{\url{https://github.com/sagemath/sagetex}} for SageMath). +The design of \mmttex is close to the latter, i.e., to combine \mmt and \latex sources. + %Mizar's document format \cite{mizar} has always been designed to closely resemble common narrative mathematical language. %Recent work for Isabelle opens up the Isabelle kernel in order to permit better integration with other applications, in particular document-oriented editors\cite{isabelle_documentoriented}. %%\cite{largeformalwikis} develops a general Wiki infrastructure that can use Mizar and Coq as verification backends. -%In the Plato system \cite{plato}, TeXMacs is used as a document-oriented frontend whose documents are parsed and verified by the Omega proof assistant. +%In the Plato system \cite{plato}, TeXMacs is used as a document-oriented frontend whose documents are parsed and verified by a proof assistant. + %Also, several proof assistants can export a {\LaTeX} version of their native input language (e.g., Isabelle and Mizar). %Similarly, most can export documents and libraries as HTML, which provides added-value features like navigation and dynamic display. %The exported documents do not fully utilize the narrative flexibility of {\LaTeX} or HTML and are not meant for further editing. @@ -16,8 +20,7 @@ %Therefore, the formalization of a mathematical document $D$ in a proof assistant usually requires the -- expensive -- formalization of the background theory that connects $D$ to the foundation. %Moreover, the fixed implementation is limited to its input language, which in turn cannot be fully understood by any other system. -The goal of \mmttex is not to introduce a new format but to use \latex documents as the primary user-written documents. -That makes it most similar to \sTeX \cite{stex}, where authors add content markup that allows converting a \latex to an OMDoc document. +The goal of \mmttex is very similar to \sTeX \cite{stex}, where authors add content markup that allows converting a \latex to an OMDoc document. %Via \latex ML \cite{latexml}, this permits generating \omdoc from the same source code that produces PDF documents. %\omdoc itself \cite{omdoc} is an XML format that integrates both content and narration markup. %Both \omdoc and sTeX do not define a reference semantics that would permit verifying the documents. diff --git a/src/latex-mmt/paper/paper.pdf b/src/latex-mmt/paper/paper.pdf index 66eac364a2718ec7966d25fee5569c272cad8a2f..5a2d11967476b63b62e99f3a1d826118076b31bc 100644 GIT binary patch delta 76165 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�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 tm U --> tm U}{[x,y] x*y'}{1 / 2 prec 50} \end{lstlisting} -\begin{mmttheory}{GroupDefs}{ex:?SFOLEQ} +\begin{mmttheory}{Division}{ex:?SFOLEQ} \mmtinclude{?Group} \mmtconstant{division}{tm U --> tm U --> tm U}{[x,y] x*y'}{1 / 2 prec 50} \noindent -Note that we have not closed the theory yet. +Note that we have not closed the theory yet, i.e., future formal objects will be processed in the scope of !Division!. \medskip \textbf{Presentation-relevant macros} result in changes to the pdf document. -The most important such macro is one that takes a math formula in \mmt syntax and parses, type-checks, and renders it. -For this macro, we provide special notation using an active character: -if we write !"$F$"! instead of $\mathdollar F\mathdollar$, then $F$ is considered to be a math formula in \mmt syntax and processed by \mmt. -Consider the formula !"forall [x] x / x = e"!. \mmt parses it relative to the current theory, type-checks, and substitutes it with \latex commands that are rendered as "forall [x] x / x = e". +The most important such macro provided by \texttt{mmttex.sty} is one that takes a math formula in \mmt syntax and parses, type-checks, and renders it. +For this macro, we provide special syntax that allows using quotes instead of dollars to have formulas processed by \mmt: +if we write !"$F$"! (including the quotes) instead of $\mathdollar F\mathdollar$, then $F$ is considered to be a math formula in \mmt syntax and processed by \mmt. +For example, the formula !"forall [x] x / x = e"! is parsed by \mmt relative to the current theory, i.e., !Division!; then \mmt type-checks it and substitutes it with \latex commands. In the previous sentence, the \latex source of the quoted formula is additionally wrapped into a verbatim macro to avoid processing by \mmt; if we remove that wrapper, the quoted formula is rendered into pdf as "forall [x] x / x = e". -Type checking infers the type of !tm U! of the bound variable !x! and the sort argument of equality. -These are attached to the formula as tooltips. +Type checking the above formula infers the type !tm U! of the bound variable !x!. +This is shown as a tooltip when hovering over the binding location of !x!. (Tooltip display is supported by many but not all pdf viewers. -Unfortunately, pdf tooltips are limited to strings so that we cannot show, e.g., MathML even though we could generate it easily.) +Unfortunately, pdf tooltips are limited to strings so that we cannot show tooltips containing \latex or MathML renderings even though we could generate them easily.) +Similarly, the sort argument of equality is inferred. Moreover, every operator carries a hyperlink to the point of its declaration. %This permits easy type and definition lookup by clicking on symbols. Currently, these links point to the \mmt server, which is assumed to run locally. @@ -116,12 +123,13 @@ \subsection{Formal Content in LaTeX Documents} %This has the additional benefit that the added value is preserved even in the printed version. \medskip +\noindent This is implemented as follows: \begin{compactenum} - \item An \mmt formula !"$F$"! simply produces a macro like !\mmt@X! for a running counter !X!. + \item An \mmt formula !"$F$"! simply produces a macro call !\mmt@X! for a running counter !X!. If that macro is undefined, a placeholder is shown and the user is warned that a second compilation is needed. - Additionally, a definition !mmt@X = $F$! is generated and placed into \texttt{doc.tex.mmt}. - \item When \texttt{latex-mmt} processes that definition, it additionally generates a macro definition !\newcommand{\mmt@X}{$\ov{F}$}! \texttt{doc.tex.mmt.sty}, where $\ov{F}$ is the intended \latex representation of $F$. + Additionally, a definition !mmt@X = $F$! in \mmt syntax is placed into \texttt{doc.tex.mmt}. + \item When \texttt{latex-mmt} processes that definition, it additionally generates a macro definition !\newcommand{\mmt@X}{$\ov{F}$}! in \texttt{doc.tex.mmt.sty}, where $\ov{F}$ is the intended \latex representation of $F$. \item During the next compilation, \mmt@X produces the rendering of $\ov{F}$. If $F$ did not type-check, additional a \latex error is produced with the error message. \end{compactenum} @@ -144,21 +152,22 @@ \subsection{Converting MMT Content To LaTeX} %In a second step, the background is made available to \latex in the form of \latex packages. %\mmt already provides a build management infrastructure that allows writing exporters to other formats. -We run \texttt{latex-mmt} on every theory $T$ that is part of the background knowledge, e.g., !SFOLEQ!, and on those in \texttt{doc.tex.mmt}, resulting in one \latex package (sty file) each. +We run \texttt{latex-mmt} on every theory $T$ that is part of the background knowledge, e.g., !SFOLEQ!, and on all theories that are part of \texttt{doc.tex.mmt}, resulting in one \latex package (sty file) each. This package contains one \lstinline|\RequirePackage| macro for every dependency and one \lstinline|\newcommand| macro for every constant declaration. \texttt{doc.tex.mmt.sty} is automatically included at the beginning of the document and thus brings all necessary generated \latex packages into scope. The generated \lstinline|\newcommand| macros use (only) the notation of the constant. -For example, for the constant !operator!, the generated command is essentially !\newcommand{\operator}[2]{#1*#2}!. -Technically, however, it is much more complex: +For example, for the constant named !operator! from above, the generated command is essentially !\newcommand{\operator}[2]{#1*#2}!. +Technically, however, the macro definition is much more complex: Firstly, instead of !#1*#2!, we produce a a macro definition that generates the right tooltips, hyperreferences, etc. -Secondly, we use the fully qualified \mmt URI as the \latex macro name to avoid ambiguity when multiple theories define constants of the same local name. +Secondly, instead of !\operator!, we use the fully qualified \mmt URI as the \latex macro name to avoid ambiguity when multiple theories define constants of the same local name. -This is an important technical difference between \mmttex and \sTeX \cite{stex}: the latter intentionally generates short human-friendly macro names because they are meant to be called by humans, which requires relatively complex scope management. +The latter is an important technical difference between \mmttex and \sTeX \cite{stex}: \sTeX intentionally generates short human-friendly macro names because they are meant to be called by humans. +That requires relatively complex scope management and dynamic loading of macro definitions to avoid ambiguity. But that is inessential in our case because our macros are called by generated \latex commands (namely those in the definiens of !\mmt@X!). -But it would be easy to add macros for creating aliases with human-friendly names. +Nonetheless, it would be easy to add macros to \texttt{mmttex.sty} for creating aliases with human-friendly names. -The conversion from \mmt to \latex can be easily run in batch mode so that any content available in \mmt form can be easily used as background knowledge in \latex documents. +The conversion from \mmt to \latex can be easily run in batch mode so that any content available in \mmt can be easily used as background knowledge in \latex documents. %sTeX uses a more complex mechanism that defines macros only when a theory is included and undefines them when the theory goes out of scope. %This does not solve the name clash problem and is more brittle when flexibly switching scopes in a document. From f8c765d02ad46d748e47571a67f062f1349a07a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Thu, 9 May 2019 13:40:35 +0200 Subject: [PATCH 56/63] Generate SQL code for views in SQL bridge --- .../kwarc/mmt/sql/codegen/CodeGenerator.scala | 9 +-- .../kwarc/mmt/sql/codegen/CodeHelper.scala | 13 ++++ .../kwarc/mmt/sql/codegen/ColumnCode.scala | 16 ++--- .../kwarc/mmt/sql/codegen/DatabaseCode.scala | 30 +++++---- .../info/kwarc/mmt/sql/codegen/JoinCode.scala | 4 +- .../kwarc/mmt/sql/codegen/TableCode.scala | 24 +++++-- .../kwarc/mmt/sql/codegen/TableInfo.scala | 3 + .../info/kwarc/mmt/sql/codegen/Tables.scala | 67 ++++++++++++++----- 8 files changed, 115 insertions(+), 51 deletions(-) create mode 100644 src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeHelper.scala diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala index e12468907a..c075501e2a 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeGenerator.scala @@ -40,13 +40,8 @@ object CodeGenerator { case Some(theory : Theory) if isInputTheory(theory, schemaGroup) => theory.path }) - val tables = new Tables(prefix, dirPaths.dbPackagePath, p => SQLBridge.test2(p, controller)) - val tableCodes = tables.processTables(paths) - - tables.print() - - val dbCode = DatabaseCode(dirPaths, prefix, tableCodes, jdbcInfo) - dbCode.writeAll(true) + val tables = new Tables(prefix, dirPaths, p => SQLBridge.test2(p, controller), jdbcInfo, paths) + tables.databaseCode.writeAll(true) } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeHelper.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeHelper.scala new file mode 100644 index 0000000000..c6f108b54f --- /dev/null +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/CodeHelper.scala @@ -0,0 +1,13 @@ +package info.kwarc.mmt.sql.codegen + +import info.kwarc.mmt.sql.Column + +trait CodeHelper { + + val quotes = "\"\"\"" + + def camelCase(s: String): String = "_([a-z\\d])".r.replaceAllIn(s, _.group(1).toUpperCase()) + def quoted(s: String): String = s""""$s"""" + def columnNameDB(c: Column): String = quoted(c.name.toUpperCase) + +} diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala index a52ba0ccd6..01a60e3e9b 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/ColumnCode.scala @@ -3,9 +3,9 @@ package info.kwarc.mmt.sql.codegen import info.kwarc.mmt.api.objects.{OMA, OMS} import info.kwarc.mmt.sql.Column -case class ColumnCode(column: Column, join: Option[JoinCode] = None) { +case class ColumnCode(column: Column, join: Option[JoinCode] = None) extends CodeHelper { - private def nameQuotes = s""""${column.name}"""" + private def nameQuotes: String = quoted(column.name) private def typeString: String = { if (column.dbtype.toString == "List[List[Int]]" || column.dbtype.toString == "List[Int]") s"List[Int]" @@ -18,7 +18,7 @@ case class ColumnCode(column: Column, join: Option[JoinCode] = None) { } def name: String = column.name - def nameDb: String = column.name.toUpperCase + def nameDbQuoted: String = columnNameDB(column) def isDisplayedByDefault: Boolean = column.isDisplayedByDefault // JsonSupport @@ -39,10 +39,10 @@ case class ColumnCode(column: Column, join: Option[JoinCode] = None) { } // TableClass - def nameCamelCase: String = ColumnCode.camelCase(name) + def nameCamelCase: String = camelCase(name) def accessorMethod: String = { val fk = join.map(_.fkMethod).map(m => s"\n$m").getOrElse("") - s""" def $nameCamelCase: Rep[$typeString] = column[$typeString]("$nameDb")$fk""" + s""" def $nameCamelCase: Rep[$typeString] = column[$typeString]($nameDbQuoted)$fk""" } def selectMapTableClass: String = s""" "$name" -> this.$nameCamelCase""" @@ -57,9 +57,3 @@ case class ColumnCode(column: Column, join: Option[JoinCode] = None) { } } - -object ColumnCode { - - def camelCase(s: String): String = "_([a-z\\d])".r.replaceAllIn(s, _.group(1).toUpperCase()) - -} diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala index ff9af81c9a..47a746b97e 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala @@ -2,15 +2,22 @@ package info.kwarc.mmt.sql.codegen import java.io.PrintWriter -import info.kwarc.mmt.api.MPath - -case class DatabaseCode(paths: ProjectPaths, prefix: String, tables: Map[MPath, TableCode], dbInfo: JDBCInfo) { +case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableInfo, TableCode], dbInfo: JDBCInfo, views: Seq[(String, String)]) { private def writeToFile(f: String, s: String, write: Boolean): Unit = { if (write) { val pw = new PrintWriter(new java.io.File(f)) try pw.write(s) finally pw.close() } + else println(s) + } + + private def tableFiles(t: TableCode): Seq[(Map[String, String], String, String)] = { + Seq( + (t.caseClassRepl, "CaseClass", ""), + (t.plainQueryRepl, "PlainQueryObject", "PlainQuery"), + (t.tableClassRepl, "TableClass", "Table") + ) } def writeAll(generate: Boolean = true): Unit = { @@ -25,13 +32,10 @@ case class DatabaseCode(paths: ProjectPaths, prefix: String, tables: Map[MPath, val dir = new java.io.File(tPath) if (generate && !dir.exists()) dir.mkdir() // val name = t.table.name - Seq( - (t.caseClassRepl, "CaseClass", ""), - (t.plainQueryRepl, "PlainQueryObject", "PlainQuery"), - (t.tableClassRepl, "TableClass", "Table") - ).foreach(f => - CodeFile(f._1, tableTempPath(f._2), Some(s"$tPath/${t.info.name}${f._3}.scala")).writeToFile(generate) - ) + tableFiles(t).foreach(f => { + val out = Some(s"$tPath/${t.info.name}${f._3}.scala") + CodeFile(f._1, tableTempPath(f._2), out).writeToFile(generate) + }) }) // delete temp dir @@ -57,10 +61,12 @@ case class DatabaseCode(paths: ProjectPaths, prefix: String, tables: Map[MPath, // backend code - private def zooCreateRepl: Map[String, String] = Map( + def zooCreateRepl: Map[String, String] = Map( "//tableObjects" -> tableObjects, + "//views" -> views.map(_._2).mkString("\n"), "//importTablePackages" -> tables.map(_._2.tableClassImport).mkString("\n"), - "//schemaCreateList" -> tables.map(_._2.zooSchemaCreate).mkString(", ") + "//schemaCreateList" -> tables.map(_._2.zooSchemaCreate).mkString(", "), + "//viewCreateList" -> views.map(_._1).mkString(", ") ) private def jsonSupportRepl: Map[String, String] = Map( diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala index 96191c5de7..404a2a171e 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/JoinCode.scala @@ -1,9 +1,9 @@ package info.kwarc.mmt.sql.codegen -case class JoinCode(tbInfo: TableInfo, columnName: String) { +case class JoinCode(tbInfo: TableInfo, columnName: String) extends CodeHelper { def fkMethod: String = { - val camelName = ColumnCode.camelCase(columnName) + val camelName = camelCase(columnName) val fk = s""" def fk${tbInfo.name} = foreignKey("${tbInfo.dbTableName}_FK", $camelName, ${tbInfo.tableObject})""" s"""$fk(_.ID, onUpdate=ForeignKeyAction.Restrict, onDelete=ForeignKeyAction.Cascade)""" } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala index b6f58f15f7..ad75677db1 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala @@ -1,13 +1,11 @@ package info.kwarc.mmt.sql.codegen -import info.kwarc.mmt.sql.{Column, Table} - case class TableCode( info: TableInfo, dbPackagePath: String, columns: Seq[ColumnCode], datasetName: String, joins: Seq[TableInfo] -) { +) extends CodeHelper { private val baseRepl: Map[String, String] = Map( "//package" -> s"package ${info.packageString}", @@ -27,7 +25,7 @@ case class TableCode( info: TableInfo, "%tableName%" -> info.name, "%getResultParameters%" -> columns.map(_.getResultItem).mkString(", "), "%sqlFrom%" -> info.dbTableName, // table name in the database - "//columns" -> ("ID" +: columns.filter(_.join.isEmpty).map(_.nameDb)).map(c => s""" |"${info.dbTableName}"."$c"""").mkString(",\n") + "//columns" -> ("ID" +: columns.filter(_.join.isEmpty).map(_.nameDbQuoted)).map(c => s""" |${quoted(info.dbTableName)}.$c""").mkString(",\n") ) // TableClass @@ -94,4 +92,22 @@ case class TableCode( info: TableInfo, s""" "${info.tableObject}": [$cols]""".stripMargin } + def dbColumns: Seq[String] = columns.map(c => s"${quoted(info.dbTableName)}.${c.nameDbQuoted}") + def dbJoinSQL: String = { + columns.collect({ + case ColumnCode(c, Some(j)) => (c, j) + }).map(t => { + val tbName = quoted(t._2.tbInfo.dbTableName) + s"""JOIN $tbName ON $tbName."ID" = ${quoted(info.dbTableName)}.${columnNameDB(t._1)}""" + }).mkString("\n") + } + + override def toString: String = + s"""TableCode: ${info.prefix} ${info.name} ($dbPackagePath) "$datasetName" + |#columns: + |${columns.map(_.toString).mkString("\n")} + |#joins: + |${joins.map(t => s"${t.prefix} ${t.name}").mkString("\n")} + """.stripMargin + } diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala index d0a88496bb..a4a5c221b7 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableInfo.scala @@ -4,10 +4,13 @@ case class TableInfo(prefix: String, name: String) { def tablePackageName: String = prefix + name def dbTableName: String = s"${prefix}_${name.toUpperCase}" + def viewName = s"${name}View" + def dbViewName: String = s"${prefix}_${viewName.toUpperCase}" def packageString = s"xyz.discretezoo.web.db.$tablePackageName" // package for the table specific files def caseClass: String = name def tableObject: String = s"tb$name" + def viewObject: String = s"view$name" def tableClass: String = s"${name}Table" def joinImport: String = s"import $packageString.$tableClass" diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala index e3e86375a2..4c3e0a66dd 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala @@ -5,14 +5,43 @@ import info.kwarc.mmt.sql.Table import scala.collection.mutable -class Tables(prefix: String, dbPackagePath: String, readerCallback: MPath => Any) { +class Tables(prefix: String, dirPaths: ProjectPaths, readerCallback: MPath => Any, jdbcInfo: JDBCInfo, inputTheories: Seq[MPath] = Seq()) extends CodeHelper { // - None: haven't attempted retrieval yet // - Some(None): retrieved, not table - private val pathsToTables: mutable.Map[MPath, Option[Option[TableCode]]] = mutable.Map[MPath, Option[Option[TableCode]]]() + private val dbPackagePath: String = dirPaths.dbPackagePath + private val builder: mutable.Map[MPath, Option[Option[TableCode]]] = mutable.Map[MPath, Option[Option[TableCode]]]() - def processTables(paths: Seq[MPath] = Seq()): Map[MPath, TableCode] = { - if (paths.nonEmpty) paths.foreach(addPath) + val tableMap: Map[TableInfo, TableCode] = processTables() + val viewMap: Map[TableInfo, TableCode] = processViews() + val views: Seq[(String, String)] = tableMap.filter(_._2.joins.nonEmpty).map(generateView).toSeq + + def databaseCode: DatabaseCode = DatabaseCode(prefix, dirPaths, tableMap, jdbcInfo, views) + + def printBuilder(): Unit = builder.foreach({ + case (p, None) => println(s"Unchecked: $p") + case (p, Some(None)) => println(s"Not table: $p") + case (p, Some(Some(t))) => println(s"Table: ${t.info.name}") + }) + + private def generateView(tableMapPair: (TableInfo, TableCode)): (String, String) = { + val i = tableMapPair._1 + val t = tableMapPair._2 + val joins = t.joins + val columnList = t.dbColumns ++ joins.flatMap(j => tableMap(j).dbColumns) + val viewSQL = s""" val ${i.viewObject}: DBIO[Int] = + | sqlu${quotes} + | CREATE VIEW ${quoted(i.dbViewName)} AS + | SELECT ${columnList.mkString(", ")} + | FROM ${quoted(i.dbTableName)} + | ${t.dbJoinSQL}; + | $quotes + """.stripMargin + (i.viewObject, viewSQL) + } + + private def processTables(): Map[TableInfo, TableCode] = { + if (inputTheories.nonEmpty) inputTheories.foreach(addPath) while (unprocessed.nonEmpty) { unprocessed.foreach(path => { readerCallback(path) match { @@ -23,7 +52,21 @@ class Tables(prefix: String, dbPackagePath: String, readerCallback: MPath => Any } }) } - pathsToTables.collect({ case (p: MPath, Some(Some(t: TableCode))) => (p, t) }).toMap + builder.collect({ case (_, Some(Some(t: TableCode))) => (t.info, t) }).toMap + } + + private def processViews(): Map[TableInfo, TableCode] = { + inputTheories.map(builder).collect({ + case Some(Some(t: TableCode)) => t + }).filter(_.joins.nonEmpty).map(t => getView(t.info)).map(view => (view.info, view)).toMap + } + + // assume that the column names are unique + private def getView(t: TableInfo): TableCode = { + val tbCode = tableMap(t) + def getColumns(t: TableInfo) = tableMap(t).columns.filter(_.join.isEmpty) + val cols = getColumns(t) ++ tbCode.joins.flatMap(getColumns) + TableCode(TableInfo(t.prefix, t.viewName), dbPackagePath, cols, tbCode.datasetName, Seq()) } private def getTableCode(table: Table): TableCode = { @@ -35,20 +78,14 @@ class Tables(prefix: String, dbPackagePath: String, readerCallback: MPath => Any } private def addPath(p: MPath): Unit = { - if (!pathsToTables.contains(p)) pathsToTables += (p -> None) + if (!builder.contains(p)) builder += (p -> None) } private def processPath(p: MPath, t: Option[TableCode]): Unit = { - if (pathsToTables.contains(p)) pathsToTables -= p - pathsToTables += (p -> Some(t)) + if (builder.contains(p)) builder -= p + builder += (p -> Some(t)) } - private def unprocessed: Seq[MPath] = pathsToTables.collect({ case (p, None) => p }).toSeq - - def print(): Unit = pathsToTables.foreach({ - case (p, None) => println(s"Unchecked: $p") - case (p, Some(None)) => println(s"Not table: $p") - case (p, Some(Some(t))) => println(s"Table: ${t.info.name}") - }) + private def unprocessed: Seq[MPath] = builder.collect({ case (p, None) => p }).toSeq } From da1d91942801ab4ecaf3bfb7b7736222f3b71922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20Berc=CC=8Cic=CC=8C?= Date: Thu, 9 May 2019 17:41:05 +0200 Subject: [PATCH 57/63] Add simplified foreign key case for SQL bridge --- .../kwarc/mmt/sql/codegen/DatabaseCode.scala | 42 ++++++++++--------- .../kwarc/mmt/sql/codegen/TableCode.scala | 10 +++-- .../info/kwarc/mmt/sql/codegen/Tables.scala | 33 ++++++++------- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala index 47a746b97e..12d8143a78 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/DatabaseCode.scala @@ -2,7 +2,7 @@ package info.kwarc.mmt.sql.codegen import java.io.PrintWriter -case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableInfo, TableCode], dbInfo: JDBCInfo, views: Seq[(String, String)]) { +case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Seq[Seq[TableCode]], dbInfo: JDBCInfo, viewCreation: Seq[(String, String)]) { private def writeToFile(f: String, s: String, write: Boolean): Unit = { if (write) { @@ -26,16 +26,16 @@ case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableIn .foreach(t => CodeFile(t._1, s"${paths.backendPackagePath}/${t._2}.scala").writeToFile(generate)) val tempDir = s"${paths.dbPackagePath}/temp" def tableTempPath(s: String) = s"$tempDir/$s.scala" - tables.foreach(mapItem => { - val t = mapItem._2 + tables.foreach(s => { + val t = s.head val tPath = tablePackagePath(t.info.name) val dir = new java.io.File(tPath) if (generate && !dir.exists()) dir.mkdir() // val name = t.table.name - tableFiles(t).foreach(f => { + s.foreach(t => tableFiles(t).foreach(f => { val out = Some(s"$tPath/${t.info.name}${f._3}.scala") CodeFile(f._1, tableTempPath(f._2), out).writeToFile(generate) - }) + })) }) // delete temp dir @@ -56,39 +56,41 @@ case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableIn private def tablePackagePrefix: String = prefix private def tablePackagePath(name: String) = s"${paths.dbPackagePath}/$tablePackagePrefix$name" + private def allTableCodes: Seq[TableCode] = tables.flatten + private def nonViewTableCodes: Seq[TableCode] = tables.map(_.head) - private val tableObjects = tables.map(_._2.dbTableObject).mkString("\n") + private def tableObjects(seq: Seq[TableCode]) = seq.map(_.dbTableObject).mkString("\n") // backend code def zooCreateRepl: Map[String, String] = Map( - "//tableObjects" -> tableObjects, - "//views" -> views.map(_._2).mkString("\n"), - "//importTablePackages" -> tables.map(_._2.tableClassImport).mkString("\n"), - "//schemaCreateList" -> tables.map(_._2.zooSchemaCreate).mkString(", "), - "//viewCreateList" -> views.map(_._1).mkString(", ") + "//tableObjects" -> tableObjects(nonViewTableCodes), + "//views" -> viewCreation.map(_._2).mkString("\n"), + "//importTablePackages" -> nonViewTableCodes.map(_.tableClassImport).mkString("\n"), + "//schemaCreateList" -> nonViewTableCodes.map(_.zooSchemaCreate).mkString(", "), + "//viewCreateList" -> viewCreation.map(_._1).mkString(", ") ) private def jsonSupportRepl: Map[String, String] = Map( - "//importTablePackages" -> tables.map(_._2.jsonSupportImport).mkString("\n"), - "//casesToJson" -> tables.map(_._2.jsonSupportMap).mkString("\n") + "//importTablePackages" -> allTableCodes.map(_.jsonSupportImport).mkString("\n"), + "//casesToJson" -> allTableCodes.map(_.jsonSupportMap).mkString("\n") ) private def zooDbRepl: Map[String, String] = Map( - "//tableObjects" -> tableObjects, + "//tableObjects" -> tableObjects(allTableCodes), "%jdbc%" -> dbInfo.jdbc, "%user%" -> dbInfo.user, "%pass%" -> dbInfo.pass, - "//importTablePackages" -> tables.map(_._2.zooDbImport).mkString("\n"), - "//getQueryMatches" -> tables.map(_._2.dbGetQueryMatches).mkString("\n"), - "//countQueryMatches" -> tables.map(_._2.dbCountQueryMatches).mkString("\n") + "//importTablePackages" -> allTableCodes.map(_.zooDbImport).mkString("\n"), + "//getQueryMatches" -> allTableCodes.map(_.dbGetQueryMatches).mkString("\n"), + "//countQueryMatches" -> allTableCodes.map(_.dbCountQueryMatches).mkString("\n") ) // frontend jsons private def objectPropertiesJSON: String = { - val code = tables.map(_._2.jsonObjectProperties).mkString(",\n") + val code = allTableCodes.map(_.jsonObjectProperties).mkString(",\n") s"""{ |$code |} @@ -96,7 +98,7 @@ case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableIn } private def collectionDataJSON: String = { - val code = tables.map(_._2.collectionsData).mkString(",\n") + val code = allTableCodes.map(_.collectionsData).mkString(",\n") s"""{ |$code |} @@ -104,7 +106,7 @@ case class DatabaseCode(prefix: String, paths: ProjectPaths, tables: Map[TableIn } private def settingsJSON: String = { - val code = tables.map(_._2.defaultColumns).mkString(",\n") + val code = allTableCodes.map(_.defaultColumns).mkString(",\n") s"""{ | "title": "Search", | "defaultColumns": { diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala index ad75677db1..7c607b86f4 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/TableCode.scala @@ -25,7 +25,7 @@ case class TableCode( info: TableInfo, "%tableName%" -> info.name, "%getResultParameters%" -> columns.map(_.getResultItem).mkString(", "), "%sqlFrom%" -> info.dbTableName, // table name in the database - "//columns" -> ("ID" +: columns.filter(_.join.isEmpty).map(_.nameDbQuoted)).map(c => s""" |${quoted(info.dbTableName)}.$c""").mkString(",\n") + "//columns" -> (quoted("ID") +: columns.filter(_.join.isEmpty).map(_.nameDbQuoted)).map(c => s""" |${quoted(info.dbTableName)}.$c""").mkString(",\n") ) // TableClass @@ -41,7 +41,7 @@ case class TableCode( info: TableInfo, // Create def tableClassImport: String = s"import ${info.packageString}.${info.tableClass}" - def zooSchemaCreate: String = s"${info.tableObject}.schema.create" + def zooSchemaCreate: String = s"${info.tableObject}.schema.createIfNotExists" // ZooDb def zooDbImport: String = s"import ${info.packageString}.{$plainQueryObject, ${info.tableClass}}" @@ -92,7 +92,11 @@ case class TableCode( info: TableInfo, s""" "${info.tableObject}": [$cols]""".stripMargin } - def dbColumns: Seq[String] = columns.map(c => s"${quoted(info.dbTableName)}.${c.nameDbQuoted}") + def dbColumns(withID: Boolean): Seq[String] = { + val maybeID = if (withID) Seq(quoted("ID")) else Seq() + (maybeID ++ columns.filter(_.join.isEmpty).map(_.nameDbQuoted)).map(c => s"${quoted(info.dbTableName)}.$c") + } + def dbJoinSQL: String = { columns.collect({ case ColumnCode(c, Some(j)) => (c, j) diff --git a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala index 4c3e0a66dd..23ae5dea98 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/sql/codegen/Tables.scala @@ -11,12 +11,18 @@ class Tables(prefix: String, dirPaths: ProjectPaths, readerCallback: MPath => An // - Some(None): retrieved, not table private val dbPackagePath: String = dirPaths.dbPackagePath private val builder: mutable.Map[MPath, Option[Option[TableCode]]] = mutable.Map[MPath, Option[Option[TableCode]]]() + private def inputTableCodes: Seq[TableCode] = inputTheories.map(builder).collect({ + case Some(Some(t: TableCode)) => t + }) val tableMap: Map[TableInfo, TableCode] = processTables() val viewMap: Map[TableInfo, TableCode] = processViews() - val views: Seq[(String, String)] = tableMap.filter(_._2.joins.nonEmpty).map(generateView).toSeq + val views: Seq[(String, String)] = tableMap.values.filter(_.joins.nonEmpty).map(generateView).toSeq - def databaseCode: DatabaseCode = DatabaseCode(prefix, dirPaths, tableMap, jdbcInfo, views) + def databaseCode: DatabaseCode = { + val tables = tableMap.map(t => Seq(t._2) ++ viewMap.get(t._2.info)).toSeq + DatabaseCode(prefix, dirPaths, tables, jdbcInfo, views) + } def printBuilder(): Unit = builder.foreach({ case (p, None) => println(s"Unchecked: $p") @@ -24,20 +30,18 @@ class Tables(prefix: String, dirPaths: ProjectPaths, readerCallback: MPath => An case (p, Some(Some(t))) => println(s"Table: ${t.info.name}") }) - private def generateView(tableMapPair: (TableInfo, TableCode)): (String, String) = { - val i = tableMapPair._1 - val t = tableMapPair._2 + private def generateView(t: TableCode): (String, String) = { val joins = t.joins - val columnList = t.dbColumns ++ joins.flatMap(j => tableMap(j).dbColumns) - val viewSQL = s""" val ${i.viewObject}: DBIO[Int] = - | sqlu${quotes} - | CREATE VIEW ${quoted(i.dbViewName)} AS + val columnList = t.dbColumns(true) ++ joins.flatMap(j => tableMap(j).dbColumns(false)) + val viewSQL = s""" val ${t.info.viewObject}: DBIO[Int] = + | sqlu$quotes + | CREATE VIEW ${quoted(t.info.dbViewName)} AS | SELECT ${columnList.mkString(", ")} - | FROM ${quoted(i.dbTableName)} + | FROM ${quoted(t.info.dbTableName)} | ${t.dbJoinSQL}; | $quotes """.stripMargin - (i.viewObject, viewSQL) + (t.info.viewObject, viewSQL) } private def processTables(): Map[TableInfo, TableCode] = { @@ -56,9 +60,10 @@ class Tables(prefix: String, dirPaths: ProjectPaths, readerCallback: MPath => An } private def processViews(): Map[TableInfo, TableCode] = { - inputTheories.map(builder).collect({ - case Some(Some(t: TableCode)) => t - }).filter(_.joins.nonEmpty).map(t => getView(t.info)).map(view => (view.info, view)).toMap + inputTableCodes.filter(_.joins.nonEmpty).map(t => { + val view = getView(t.info) + (t.info, view) + }).toMap } // assume that the column names are unique From d3589e4b4235cb776f7d0289376b13ed762bd96c Mon Sep 17 00:00:00 2001 From: Yasmine Sharoda Date: Fri, 10 May 2019 11:22:32 -0400 Subject: [PATCH 58/63] Added asSubstitution function to case class OML, returns Sub(name,df) --- src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala index abeb356489..94a614678e 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala @@ -407,6 +407,7 @@ case class OML(name: LocalName, tp: Option[Term], df: Option[Term], nt: Option[T def substitute(sub: Substitution)(implicit sa: SubstitutionApplier) = OML(name, tp map (_ ^^ sub), df map (_ ^^ sub),nt,featureOpt) def toCMLQVars(implicit qvars: Context) = def toNode = vd.toNode.copy(label = "OML") + def toSubstitution : Sub = Sub(name,df.get) } object OML { From ab78761848a27bdbbea517ff48b6e21eb3cdcba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=BCller?= Date: Tue, 14 May 2019 13:55:44 +0200 Subject: [PATCH 59/63] GAP related things --- src/mmt-api/resources/mmtrc | 4 +- .../info/kwarc/mmt/odk/GAP/JSONImporter.scala | 58 ++++++++++++++----- .../info/kwarc/mmt/odk/GAP/Translator.scala | 13 ++++- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/mmt-api/resources/mmtrc b/src/mmt-api/resources/mmtrc index 760cb5aeac..e8b6e3358b 100644 --- a/src/mmt-api/resources/mmtrc +++ b/src/mmt-api/resources/mmtrc @@ -43,6 +43,6 @@ models http://models.latin.omdoc.org/ // entries for MitM systems, moved here from mitm/config.default.json #mitm -GAP localhost 26133 +GAP field.informatik.uni-erlangen.de 26133 Sage localhost 26136 -Singular localhost 26135 \ No newline at end of file +Singular field.informatik.uni-erlangen.de 26135 \ No newline at end of file diff --git a/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/JSONImporter.scala b/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/JSONImporter.scala index c4a45ff30e..c4abed43f5 100644 --- a/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/JSONImporter.scala +++ b/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/JSONImporter.scala @@ -20,7 +20,7 @@ sealed abstract class ParsedObject { case class ParsedMethod(op : String, filters : List[List[String]], comment : String, rank : Int) { val dependencies = op :: filters.flatten.distinct } -case class ParsedProperty(name : String, aka : List[String], implied : List[String], isTrue :Boolean = false,location : (String,Int)) extends ParsedObject { +case class ParsedProperty(name : String, aka : List[String], implied : List[String], isTrue :Boolean = false,location : (String,Int),methods:List[ParsedMethod]) extends ParsedObject { val dependencies = implied val tp = "GAPProperty" } @@ -33,7 +33,7 @@ case class ParsedCategory(name : String, aka : List[String], implied : List[Stri val dependencies = implied val tp = "GAPCategory" } -case class ParsedAttribute(name : String, aka : List[String], filters : List[String],location : (String,Int)) extends ParsedObject { +case class ParsedAttribute(name : String, aka : List[String], filters : List[String],location : (String,Int),methods:List[ParsedMethod]) extends ParsedObject { val dependencies = filters val tp = "GAPAttribute" } @@ -46,6 +46,11 @@ case class ParsedFilter(name : String, aka : List[String], implied : List[String val tp = "GAPFilter" } +case class ParsedConstructor(name : String, aka : List[String],location : (String,Int),filters: List[String],methods:List[ParsedMethod]) extends ParsedObject { + override val dependencies: List[String] = filters + val tp = "Constructor" +} + case class ParsedDefinedFilter(name : String, aka : List[String], conjof : List[String],location : (String,Int)) extends ParsedObject { val dependencies = conjof val tp = "GAPDefinedFilter" @@ -153,11 +158,13 @@ class GAPJSONImporter extends Importer { */ - val locations = { + val locations = try { val locobj = try { obj.getAsList(classOf[JSONObject],"locations").head } catch { case e : Exception => obj.getAs(classOf[JSONObject],"location") } - (locobj.getAsString("file"),locobj.getAsInt("line").toInt) + (locobj.getAsString("file"),Try(locobj.getAsInt("line").toInt).getOrElse(0)) + } catch { + case ParseError(_) => ("",0) } // TODO: don't just take the head! @@ -165,12 +172,14 @@ class GAPJSONImporter extends Importer { case "GAP_AndFilter" => val conjs = obj.getAsList(classOf[String],"conjunction_of").filter(redfilter(name)) (ParsedDefinedFilter(name,aka,conjs,locations),List("conjunction_of")) - case "GAP_Property" | "GAP_TrueProperty" => + case "GAP_Property" | "GAP_TrueProperty"| "Property" => val impls = Try(obj.getAsList(classOf[String],"implied")).getOrElse( obj.getAsList(classOf[String],"filters")).filter(redfilter(name)) - (ParsedProperty(name,aka,impls,if (tp == "GAP_TrueProperty") true else false,locations),List("implied","filters")) + val methods = doMethods(obj.getAs(classOf[JSONObject],"methods"),name) + allmethods :::= methods + (ParsedProperty(name,aka,impls,if (tp == "GAP_TrueProperty") true else false,locations,methods),List("implied","filters","methods")) - case "GAP_Operation" => + case "GAP_Operation"| "Operation" => val filters = obj.getAsList(classOf[JSONArray],"filters") map (_.values.toList map { case JSONArray(ls@_*) => (ls.toList map { case JSONString(s) => s @@ -182,9 +191,11 @@ class GAPJSONImporter extends Importer { allmethods :::= methods (ParsedOperation(name,aka,filters,methods,locations),List("filters","methods")) - case "GAP_Attribute" => + case "GAP_Attribute"| "Attribute" => val filters = obj.getAsList(classOf[String],"filters").filter(redfilter(name)) - (ParsedAttribute(name,aka,filters,locations),List("filters")) + val methods = doMethods(obj.getAs(classOf[JSONObject],"methods"),name) + allmethods :::= methods + (ParsedAttribute(name,aka,filters,locations,methods),List("filters","methods")) case "GAP_Representation" => val implied = obj.getAsList(classOf[String],"implied").filter(redfilter(name)) @@ -198,10 +209,19 @@ class GAPJSONImporter extends Importer { val implied = obj.getAsList(classOf[String],"implied").filter(redfilter(name)) (ParsedFilter(name,aka,implied,locations),List("implied")) + case "Constructor" => + val filters = obj.getAsList(classOf[String],"filters").filter(redfilter(name)) + val methods = doMethods(obj.getAs(classOf[JSONObject],"methods"),name) + allmethods :::= methods + (ParsedConstructor(name,aka,locations,filters,methods),List("filters","methods")) + + case "Function" => + // TODO? + return case _ => throw new ParseError("Type not yet implemented: " + tp + " in " + obj) } val missings = obj.map.filter(p => !("name" :: "type" :: "aka" :: "location" :: "locations" :: eaten).contains(p._1.value)) - if (missings.nonEmpty) throw new ParseError("Type " + tp + " has additional fields " + missings) + if (missings.nonEmpty) println("Type " + tp + " has additional fields " + missings) // throw new ParseError("Type " + tp + " has additional fields " + missings) // log("Added: " + ret) all ::= ret } @@ -247,9 +267,9 @@ class GAPJSONImporter extends Importer { new DeclaredFilter(LocalName(name),implied map deps,loc) case ParsedRepresentation(name,aka,implied,loc) => new GAPRepresentation(LocalName(name),implied map deps,loc) - case ParsedProperty(name,aka,implied,istrue,loc) => + case ParsedProperty(name,aka,implied,istrue,loc,methods) => new DeclaredProperty(LocalName(name),implied map deps,istrue,loc) - case ParsedAttribute(name,aka,filters,loc) => + case ParsedAttribute(name,aka,filters,loc,methods) => new DeclaredAttribute(LocalName(name), filters map deps,loc) case ParsedCategory(name,aka,implied,locations) => new DeclaredCategory(LocalName(name), implied map deps,locations) @@ -257,6 +277,8 @@ class GAPJSONImporter extends Importer { new DeclaredOperation(LocalName(name),filters map (_ map (_ map deps)),locations) case ParsedDefinedFilter(name,aka,conjof,location) => new DefinedFilter(LocalName(name), conjof map deps,location) + case ParsedConstructor(name, aka, location, filters, methods) => + new Constructor(LocalName(name),filters map deps,location) } dones += ((po,ret)) ret @@ -438,8 +460,10 @@ class DeclaredFilter(val name : LocalName, val implied : List[GAPObject], val lo class DefinedFilter(val name : LocalName, val conjs : List[GAPObject], val locations : (String,Int)) extends DeclaredObject with GAPFilter { override def toString = "DefinedFilter " + name + " " + locations val dependencies = conjs.flatMap(_.getInner).distinct - def defi : Term = if (conjs.length == 1) Translator.objtotype(conjs.head) else - conjs.init.foldRight(Translator.objtotype(conjs.last))((o,t) => GAP.termconj(Translator.objtotype(o),t)) + def defi : Option[Term] = if (conjs.isEmpty) { + None + } else if (conjs.length == 1) Some(Translator.objtotype(conjs.head)) else + Some(conjs.init.foldRight(Translator.objtotype(conjs.last))((o,t) => GAP.termconj(Translator.objtotype(o),t))) } trait GAPCategory extends GAPFilter @@ -470,6 +494,12 @@ class DeclaredAttribute(val name : LocalName, val filters: List[GAPObject], val override def toString = "DeclaredAttribute " + name + " " + locations val dependencies = filters.flatMap(_.getInner).distinct } +class Constructor (val name : LocalName, val filters: List[GAPObject], val locations : (String,Int)) + extends DeclaredObject { + override def toString: String = "Constructor " + name + " " + locations + override val dependencies: List[DeclaredObject] = filters.flatMap(_.getInner).distinct + val returntype : Option[GAPObject] = None +} trait GAPProperty extends GAPAttribute class DeclaredProperty(name : LocalName, implied : List[GAPObject], val istrue : Boolean, locations : (String,Int)) extends DeclaredAttribute(name,implied,locations) with GAPProperty { diff --git a/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/Translator.scala b/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/Translator.scala index 1592238824..663dddd4bb 100644 --- a/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/Translator.scala +++ b/src/mmt-odk/src/info/kwarc/mmt/odk/GAP/Translator.scala @@ -65,7 +65,7 @@ class Translator(controller: Controller, bt: BuildTask, index: Document => Unit, obj.dependencies.foreach(doObject) val (deps,cs) = obj match { case df : DefinedFilter => - var consts = List(Constant(OMMOD(df.path.module),df.name,Nil,Some(GAP.filter),Some(df.defi),None)) + var consts = List(Constant(OMMOD(df.path.module),df.name,Nil,Some(GAP.filter),df.defi,None)) addDependencies(df.path.module,df.dependencies) (IsFilter(consts.last.path) :: doImpls(consts.head,df.dependencies),consts) @@ -86,6 +86,17 @@ class Translator(controller: Controller, bt: BuildTask, index: Document => Unit, } addDependencies(a.path.module,obj.dependencies) (IsAttribute(consts.last.path) :: doImpls(consts.head,a.dependencies),consts) + case a : Constructor => + val filters = a.filters + var consts = List(Constant(OMMOD(a.path.module),a.name,Nil,Some(Arrow(GAP.obj,GAP.obj)),None,None)) + //addConstant(c) + if (a.returntype.isDefined) { + consts ::= Constant(OMMOD(a.path.module),LocalName(a.name + "_type"),Nil,Some( + typeax(a,List(filters),a.returntype.get) + ),None,None) + } + addDependencies(a.path.module,obj.dependencies) + (doImpls(consts.head,a.dependencies),consts) case op : DeclaredOperation => opcounter = 0 val filters = op.filters From e78c33d2149a3eb3ff3e9f7bd7f0564b5501d438 Mon Sep 17 00:00:00 2001 From: Makarius Date: Tue, 14 May 2019 14:38:14 +0200 Subject: [PATCH 60/63] updates for Isabelle2019 (June 2019) --- src/mmt-isabelle/README.md | 49 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/mmt-isabelle/README.md b/src/mmt-isabelle/README.md index 58b3fa6db0..55d486ebf8 100644 --- a/src/mmt-isabelle/README.md +++ b/src/mmt-isabelle/README.md @@ -3,24 +3,27 @@ Isabelle/MMT ## Requirements -Until the next official release after Isabelle2018 (presumably Isabelle2019 -in June 2019), Isabelle/MMT requires a suitable development snapshot from -https://isabelle.sketis.net/devel/release_snapshot or a repository clone -from http://isabelle.in.tum.de/repos/isabelle -- see also its -`README_REPOSITORY` file with the all-important **Quick start in 30min**. - -In particular, the following versions from Nov-2018 should fit together: - - * Isabelle/1bee990d443c from https://isabelle.in.tum.de/repos/isabelle - * AFP/1566e3fc3118 from https://bitbucket.org/isa-afp/afp-devel - * MMT/55f297239a45 from https://github.com/UniFormal/MMT/commits/devel - * MathHub/MMT/urtheories/efe7963422a3 from +Isabelle/MMT requires a version of Isabelle that fits precisely to it. For the +current stable release that is Isabelle2019 (June 2019). For intermediate +repository versions, a suitable Isabelle development snapshot is required, e.g. +from https://isabelle.sketis.net/devel/release_snapshot or a repository clone +from https://isabelle.sketis.net/repos/isabelle -- see also its +`README_REPOSITORY` file within the Isabelle repository with the all-important +**Quick start in 30min**. + +In particular, the following versions from May-2019 should fit together: + + * Isabelle/805250bb7363 from https://isabelle.sketis.net/repos/isabelle-release + * AFP/2170a6647f04 from https://isabelle.sketis.net/repos/afp-devel + * MMT/da1d91942801 from https://github.com/UniFormal/MMT/commits/devel + * MathHub/MMT/urtheories/01102b90e8bd from https://gl.mathhub.info/MMT/urtheories/commits/devel -The corresponding OMDoc content is available here: +The corresponding OMDoc content is available here (commit messages refer to the +underlying versions of Isabelle + AFP): - * https://gl.mathhub.info/Isabelle/Distribution (version 3faa7f9c7742) - * https://gl.mathhub.info/Isabelle/AFP (version 30a0dc372eaa) + * https://gl.mathhub.info/Isabelle/Distribution + * https://gl.mathhub.info/Isabelle/AFP ## Setup @@ -52,8 +55,7 @@ Here are some example invocations of the main command-line tools: * importer: isabelle mmt_import -B ZF - isabelle mmt_import HOL - isabelle mmt_import -B HOL-Analysis + isabelle mmt_import -g main isabelle mmt_import -o record_proofs=2 -B HOL-Proofs * HTTP server to browse the results: @@ -82,7 +84,7 @@ Recall that Isabelle consists of two processes: Big examples require generous heap space for both, typically 30 GB. Note that both platforms have a discontinuity when switching from short 32-bit -pointers to full 64-bit ones: *4 GB* for Poly/ML and *32 GB* for Java. Going +pointers to full 64-bit ones: *16 GB* for Poly/ML and *32 GB* for Java. Going beyond that doubles the baseline memory requirements. The subsequent setup works well for hardware with 64 GB of main memory: @@ -100,7 +102,6 @@ The subsequent setup works well for hardware with 64 GB of main memory: Examples: isabelle mmt_import -a -X doc -X no_doc - isabelle mmt_import -d '$AFP' -B HOL-Analysis -X doc -X no_doc -X slow isabelle mmt_import -d '$AFP' -a -X doc -X no_doc -X slow isabelle mmt_import -d '$AFP' -a -X doc -X no_doc -X very_slow @@ -123,7 +124,7 @@ serves as catch-all pattern. Since all sessions in AFP are guaranteed to belong to the chapter `AFP`, the following works for Isabelle + AFP as one big import process: - isabelle mmt_import -d '$AFP' -A content/MathHub -C AFP=AFP -C _=Distribution -a -X doc -X no_doc -X slow -x HOL-ODE-Numerics -x Diophantine_Eqns_Lin_Hom -x HLDE + isabelle mmt_import -d '$AFP' -A content/MathHub -C AFP=AFP -C _=Distribution -a -X doc -X no_doc -X very_slow Note that other Isabelle applications may have their own chapter naming scheme, or re-use official Isabelle chapter names; if nothing is specified, @@ -149,7 +150,8 @@ shown to end-users by default. ## isabelle mmt_build This is a thin wrapper for `sbt mmt/deploy` within the formal Isabelle -environment and the correct directory in the MMT source tree. It also +environment and the correct directory in the MMT source tree; it trims the +resulting jar to avoid duplicates of Scala libraries. Furthermore, it ensures that Isabelle/Scala has been properly bootstrapped beforehand (e.g. when working from the Isabelle repository). @@ -173,7 +175,6 @@ directories. Its command-line usage is as follows: -a select all sessions -d DIR include session directory -g NAME select session group NAME - -l NAME logic session name (default: "Pure") -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) -v verbose mode -x NAME exclude session NAME and all descendants @@ -205,10 +206,6 @@ descriptions). Option `-v` enables verbose mode, similar to `isabelle build`. -Option `-l` allows to use a different base logic images than the `Pure`. It -rarely makes sense to use something else, because formal content between -`Pure` and the base logic is *missing* from the import. - ## isabelle mmt_server From d1f8847d98403c653199332f124a2ddf2d35b22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=BCller?= Date: Tue, 14 May 2019 17:01:32 +0200 Subject: [PATCH 61/63] fixed jupyter/replserver issues --- .../main/info/kwarc/mmt/api/parser/StructureParser.scala | 8 ++++++-- .../src/main/info/kwarc/mmt/api/web/REPLServer.scala | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/parser/StructureParser.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/parser/StructureParser.scala index 4d1d880cd4..7614b50cfb 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/parser/StructureParser.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/parser/StructureParser.scala @@ -195,11 +195,15 @@ class KeywordBasedParser(objectParser: ObjectParser) extends Parser(objectParser case IsDoc(dp) => val doc = controller.globalLookup.getAs(classOf[Document],dp) readInDocument(doc)(state) - (doc.getDeclarations.last,state) + (doc.getDeclarations.lastOption.getOrElse { + throw ParseError(dp + " is empty: " + state) + },state) case IsMod(mp, rd) => val mod = controller.globalLookup.getAs(classOf[ModuleOrLink],mp) readInModule(mod, mod.getInnerContext, new Features(Nil,Nil))(state) - (mod.getDeclarations.last, state) + (mod.getDeclarations.lastOption.getOrElse { + throw ParseError(mp + " is empty: " + state) + }, state) } } diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/web/REPLServer.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/web/REPLServer.scala index cdbc207834..dc3ea04058 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/web/REPLServer.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/web/REPLServer.scala @@ -27,6 +27,7 @@ class REPLSession(val doc: Document, val id: String, interpreter: TwoStepInterpr val se = interpreter(ps)(errorCont) se match { case r: MRef => currentScope = IsMod(r.target, LocalName.empty) + case Include(_) => case m: ModuleOrLink => currentScope = IsMod(m.modulePath, LocalName.empty) case nm: NestedModule => currentScope = IsMod(nm.module.path, LocalName.empty) case _ => From 55122fb703bde43e3bfe7ff46300d51c305a7e85 Mon Sep 17 00:00:00 2001 From: Yasmine Sharoda Date: Thu, 16 May 2019 02:52:19 -0400 Subject: [PATCH 62/63] Impl Combine-Over --- .../mmt/api/objects/AnonymousModules.scala | 71 +++++++++--- .../main/info/kwarc/mmt/api/objects/Obj.scala | 1 - .../mmt/moduleexpressions/Combinators.scala | 105 +++++++++--------- .../DiagramDefinitions.scala | 2 +- .../src/info/kwarc/mmt/test/DiagTest.scala | 2 +- 5 files changed, 109 insertions(+), 72 deletions(-) diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/AnonymousModules.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/AnonymousModules.scala index 41635ab7d1..ff2aa860c7 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/AnonymousModules.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/AnonymousModules.scala @@ -11,6 +11,7 @@ import utils._ trait AnonymousBody extends ElementContainer[OML] with DefaultLookup[OML] { val decls: List[OML] def getDeclarations = decls + def toSubstitution : Substitution = decls.map{case OML(name,_,df,_,_) => Sub(name,df.get)} def toTerm: Term override def toString = getDeclarations.mkString("{", ", ", "}") } @@ -41,6 +42,14 @@ class AnonymousTheory(val mt: Option[MPath], val decls: List[OML]) extends Anony val m = mt.map(_.toString).getOrElse("") m + super.toString } + def canEqual(a: Any) = a.isInstanceOf[DiagramNode] + override def equals(that: Any): Boolean = + that match { + case that: AnonymousTheory => (that.mt == this.mt) && (that.decls == this.decls) + case _ => false + } + override def hashCode: Int = { mt.hashCode() + decls.hashCode() } + } /** bridges between [[AnonymousTheory]] and [[Term]] */ @@ -64,6 +73,7 @@ object AnonymousTheoryCombinator { class AnonymousMorphism(val decls: List[OML]) extends AnonymousBody { def toTerm = AnonymousMorphismCombinator(decls) def add(d: OML) = new AnonymousMorphism(decls ::: List(d)) + } /** bridges between [[AnonymousMorphism]] and [[Term]] */ @@ -91,6 +101,13 @@ sealed abstract class DiagramElement { case class DiagramNode(label: LocalName, theory: AnonymousTheory) extends DiagramElement { def toTerm = OML(label, Some(TheoryType(Nil)), Some(theory.toTerm)) override def toString = s"$label:THEY=$theory" + def canEqual(a: Any) = a.isInstanceOf[DiagramNode] + override def equals(that: Any): Boolean = + that match { + case that: DiagramNode => (that.label == this.label) && (that.theory == this.theory) + case _ => false + } + override def hashCode: Int = { label.hashCode() + theory.hashCode() } } /** an arrow in an [[AnonymousDiagram]] * @param label the label of this arrow @@ -139,22 +156,47 @@ case class AnonymousDiagram(val nodes: List[DiagramNode], val arrows: List[Diagr def getAllArrowsTo(label : LocalName) : List[DiagramArrow] = { arrows.filter(a => a.to == label) } - def viewOf(from:DiagramNode,to:DiagramNode): List[OML] = { - /* Finding the rename arrows */ - val distTo : List[DiagramArrow] = getDistArrowsTo(to.label) - val rens: List[OML] = distTo.flatMap(a => a.morphism.decls) - val new_decls = from.theory.decls.map { curr => - var curr_ren = rens.find(_.name == curr.name) - var new_decl = curr - while (curr_ren != None) { - var df = curr_ren.get.df // TODO: Do we want to have getOrElse here? - new_decl = df.get.asInstanceOf[OML] - curr_ren = rens.find(oml => (oml.name == new_decl.name)) - } - new_decl + /* Finding the path between two nodes using their labels */ + def path(sourceLabel : LocalName, targetLabel : LocalName) : List[DiagramArrow] ={ + arrows.find(a => a.to == targetLabel) match { + case None => Nil + case Some(a) => + a :: (if (a.label == sourceLabel) Nil else path(sourceLabel,a.label)) } - new_decls } + + /* A function to compose two substitutions + * The function assume that assign1 has no duplicate assignments for the same symbol. + */ + def compose(assign1 : List[OML],assign2 : List[OML]): List[OML] = { + val new_assigns : List[OML] = assign1.map{ curr : OML => + var temp = curr.name + val it = assign2.iterator + while(it.hasNext){ + val n = it.next() + if(temp == n.name){ + temp = n.df.asInstanceOf[OML].name + } + } + val new_decl = new OML(temp,None,None,None) + new OML(curr.name,curr.tp,Some(new_decl),curr.nt,curr.featureOpt) + } + new_assigns + } + + /* Finding the views + * - each view is a list of assignments of terms to constants TODO: we consider now only constants to constants + * - find the path (list of views) from the source to the target + * - calculate the flat list of assignments in these views (order matters for cases like [a |-> b, b |-> c]) + * An assignment name = definition is represented as an OML(name, type, definition, notationOpt, featureOpt) + * */ + def viewOf(source : DiagramNode, target : DiagramNode): List[OML] = { + + val arrows: List[DiagramArrow] = path(source.label, target.label) + val assignments: List[OML] = arrows.flatMap(a => a.morphism.decls) + compose(source.theory.decls,assignments) + } + def getDistArrowWithNodes: Option[(DiagramNode,DiagramNode,DiagramArrow)] = getDistArrow flatMap {a => getArrowWithNodes(a.label)} def getElements = nodes:::arrows /** replaces every label l with r(l) */ @@ -204,7 +246,6 @@ object AnonymousDiagramCombinator { Some(ad) case _ => None } - } object OMLList { diff --git a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala index 94a614678e..abeb356489 100644 --- a/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala +++ b/src/mmt-api/src/main/info/kwarc/mmt/api/objects/Obj.scala @@ -407,7 +407,6 @@ case class OML(name: LocalName, tp: Option[Term], df: Option[Term], nt: Option[T def substitute(sub: Substitution)(implicit sa: SubstitutionApplier) = OML(name, tp map (_ ^^ sub), df map (_ ^^ sub),nt,featureOpt) def toCMLQVars(implicit qvars: Context) = def toNode = vd.toNode.copy(label = "OML") - def toSubstitution : Sub = Sub(name,df.get) } object OML { diff --git a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala index 46b0a2c69e..0c1366fef4 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/Combinators.scala @@ -312,11 +312,11 @@ object ComputeRename extends ComputationRule(Rename.path) { trait Pushout extends ConstantScala { val parent = Combinators._path - def apply(d1: Term, r1: List[Term], d2: Term, r2: List[Term]) = { - path(d1 :: r1 ::: List(d2) ::: r2) + def apply(d1: Term, r1: List[Term], d2: Term, r2: List[Term], over : Term) = { + path(d1 :: r1 ::: List(d2) ::: r2 ::: List(over)) } - def unapply(t: Term): Option[(Term,List[Term],Term,List[Term])] = t match { + def unapply(t: Term): Option[(Term,List[Term],Term,List[Term],Term)] = t match { case OMA(OMS(this.path), args) => var left = args val d1 = left.headOption.getOrElse(return None) @@ -327,14 +327,16 @@ trait Pushout extends ConstantScala { left = left.tail val r2 = left.takeWhile(t => Rename1.unapply(t).isDefined) left = left.drop(r2.length) + val over = left.headOption.getOrElse(return None) + left = left.tail if (left.nonEmpty) return None - Some((d1,r1,d2,r2)) + Some((d1,r1,d2,r2,over)) case _ => None } } object PushoutUtils { - case class BranchInfo(anondiag: AnonymousDiagram, dom: LocalName, distNode: DiagramNode, + case class BranchInfo(anondiag: AnonymousDiagram, distNode: DiagramNode, distTo: List[DiagramArrow], renames: List[(LocalName,Term)]) { def extend(label: LocalName, po: DiagramNode) = { //val morph = new AnonymousMorphism(po.theory.decls.diff(distNode.theory.decls)) @@ -345,10 +347,9 @@ object PushoutUtils { def collectBranchInfo(solver: CheckingCallback,d: Term,rename : List[Term])(implicit stack: Stack, history: History): Option[BranchInfo] = { val ren = Common.asSubstitution(rename) val ad = Common.asAnonymousDiagram(solver, d).getOrElse(return None) - val dom = ad.getDistArrow.getOrElse(return None).from val distNode = ad.getDistNode.getOrElse(return None) val distTo = ad.getDistArrowsTo(distNode.label) - Some(PushoutUtils.BranchInfo(ad,dom,distNode,distTo,ren)) + Some(PushoutUtils.BranchInfo(ad,distNode,distTo,ren)) } } @@ -363,61 +364,59 @@ object Combine extends Pushout { object ComputeCombine extends ComputationRule(Combine.path) { def apply(solver: CheckingCallback)(tm: Term, covered: Boolean)(implicit stack: Stack, history: History): Simplifiability = { - val Combine(d1,r1,d2,r2) = tm - - /* Calculate branch info */ - val b1 : BranchInfo = PushoutUtils.collectBranchInfo(solver,d1,r1).getOrElse(return Recurse) - val b2 : BranchInfo = PushoutUtils.collectBranchInfo(solver,d2,r2).getOrElse(return Recurse) - - /* Finding the common source (\Gamma) */ - val extArrows1 = b1.distTo.filter(_.label.toString.contains("extend")) - val extArrows2 = b2.distTo.filter(_.label.toString.contains("extend")) - val List(sN1,sN2) = List(b1,b2).map(b => b.distTo.filter(_.label.toString.contains("extend")).map(a => a.from)) - val sourceName : LocalName = (sN1 intersect sN2).head - val source : DiagramNode = sourceName match { - case Common.ExistingName(s) => b1.anondiag.getNode(sourceName).get - case _ => return Recurse - } + /**************** Input pieces *****************/ + val Combine(d1, r1, d2, r2, over) = tm + val List(ad1, ad2, ad_over) = List(d1, d2, over).map(d => Common.asAnonymousDiagram(solver, d).get) // TODO: Handle errors here + + /**************** Calculate the views from the source to the two nodes (after renaming) ****************/ + val List(renames1,renames2) : List[List[OML]] = List(r1,r2).map{r => Common.asSubstitution(r).map{case (o,n) => OML(o,None,Some(n))}} + val List(in_view1,in_view2) : List[List[OML]] = List(ad1,ad2).map{d => d.viewOf(ad_over.getDistNode.get, d.getDistNode.get)} // TODO: Handle errors here + val view1: List[OML] = ad1.compose(in_view1,renames1) + val view2: List[OML] = ad2.compose(in_view2,renames2) + + /**************** Check The Guard *******************/ + /* - Now, view1 and view2 have the list of assignments from the source to the targets (after applying the renames) + * - We compare them to make sure the names matches + * - Order does not matter, so we convert the list to a set. + * */ + if (view1.toSet != view2.toSet) throw (new GeneralError("Wrong renames")) + + /**************** Define the new theory ***************/ + // apply the rename + def view_as_sub(v : List[OML]) = v.map{case OML(o,_,Some(n),_,_) => (o,n)} + val List(renThry1,renThry2) : List[AnonymousTheory] = List(ad1,ad2).map(b => b.getDistNode.get.theory.rename(view_as_sub(view1):_*)) + + /* Calculate the pushout as distinct union + * TODO: Find a better way to choose the meta-theory + * */ + val new_decls : List[OML] = (renThry1.decls union renThry2.decls).distinct + val new_theory : AnonymousTheory = new AnonymousTheory(ad1.getDistNode.get.theory.mt,new_decls) + + /****************** Build the new diagram *******************/ + val dist_node: DiagramNode = DiagramNode(Combine.nodeLabel, new_theory) + // TODO: The assignments in the arrow need to be the identity + val impl_arrow = DiagramArrow(Combine.arrowLabel, ad_over.distNode.get, dist_node.label, new AnonymousMorphism(Nil), true) - /* Applying renames on the theories */ - val List(renamedThry1,renamedThry2) = List(b1,b2).map(b => b.distNode.theory.rename(b.renames:_*)) + /* This is used in case theories are not built in a tiny-theories way. In this case, we need to generate names for the arrows. */ + val jointDiag: AnonymousDiagram = (Common.prefixLabels(ad1, LocalName("left")) union Common.prefixLabels(ad2, LocalName("right"))) - /* Checking for the guard: - * If two decls are renamed to the same name, they should be the same in the source node */ - // Going through the rename arrows: - // val List(view_ad1,view_ad2) = List(b1,b2).map(b => b.anondiag.viewOf(source,b.distNode)) - val view_ad1 = b1.anondiag.viewOf(source,new DiagramNode(LocalName("n1"),renamedThry1)) - val view_ad2 = b2.anondiag.viewOf(source,new DiagramNode(LocalName("n2"),renamedThry2)) - if(view_ad1 != view_ad2) throw (new GeneralError("Wrong renames")) + val List(map1,map2) = List(view1,view2).map{v => Common.asSubstitution(v).map { case (o, n) => OML(o, None, Some(n))}} - /* Theory with the new declarations */ - val new_thy = renamedThry1 union renamedThry2 + val arrow1 = DiagramArrow(Combine.arrowLabel1, ad1.distNode.get, dist_node.label, new AnonymousMorphism(map1), false) + val arrow2 = DiagramArrow(Combine.arrowLabel2, ad2.distNode.get, dist_node.label, new AnonymousMorphism(map2), false) - /* TODO: How to choose the meta-theory? We need to have a constraint that one of them contains the other? */ - val result_node = DiagramNode(Combine.nodeLabel, new_thy) - val diag = DiagramArrow(Combine.arrowLabel, source.label, result_node.label, new AnonymousMorphism(Nil), true) + val result_diag = new AnonymousDiagram(jointDiag.nodes ::: List(dist_node), jointDiag.arrows ::: List(arrow1, arrow2, impl_arrow), Some(dist_node.label)) - /* This is used in case theories are not built in a tiny-theories way. In this case, we need to generate names for the arrows. */ - val jointDiag = Common.prefixLabels(b1.anondiag, LocalName("left")) union Common.prefixLabels(b2.anondiag, LocalName("right")) - /* val List(dom1,dom2) = List(ad1,ad2) map {d => - val fromT = d.getDistArrow.getOrElse(return Recurse).from - fromT match { - case Common.ExistingName(p) => p - case _ => return Recurse - } - }*/ - val arrow1 = b1.extend(Combine.arrowLabel1, result_node) - val arrow2 = b2.extend(Combine.arrowLabel2, result_node) - val ad = new AnonymousDiagram(jointDiag.nodes ::: List(result_node), jointDiag.arrows:::List(arrow1,arrow2, diag), Some(result_node.label)) - Simplify(ad.toTerm) + Simplify(result_diag.toTerm) } + } object Mixin extends Pushout { val name = "mixin" val nodeLabel = LocalName("pres") - val arrowLabel1 = LocalName("extend") + val arrowLabel1 = LocalName("extend1") val arrowLabel2 = LocalName("view") val arrowLabel = LocalName("extend") } @@ -430,7 +429,7 @@ object Mixin extends Pushout { * inclusion from B to Translate(m,T) */ - +/* object ComputeMixin extends ComputationRule(Mixin.path) { def apply(solver: CheckingCallback)(tm: Term, covered: Boolean)(implicit stack: Stack, history: History): Simplifiability = { /* Both combine and translate takes two diagrams and two renames. The difference is in the content of the second diagram */ @@ -450,8 +449,6 @@ object ComputeMixin extends ComputationRule(Mixin.path) { val view_renamed : AnonymousMorphism = new AnonymousMorphism(Common.applySubstitution(view.morphism.decls,ren2)) val view_from : DiagramNode = ad2.getNode(view.from).getOrElse(return Recurse) - val iter = view.morphism.decls.map(oml => oml.df) - /** TODO: THis part needs to be CHANGED */ val mor = view.morphism.decls.map {oml => (LocalName(oml.name),oml.df.getOrElse(return Recurse))} val morAsSub = view.morphism.decls.flatMap{oml => oml.df.toList.map {d => Sub(oml.name, d)}} @@ -469,7 +466,7 @@ object ComputeMixin extends ComputationRule(Mixin.path) { Simplify(result.toTerm) } } - +*/ // TODO better name /** see [[Mixin]] */ object Expand extends BinaryConstantScala(Combinators._path, "expand") diff --git a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/DiagramDefinitions.scala b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/DiagramDefinitions.scala index 50e5019ddc..3206fa0c13 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/DiagramDefinitions.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/moduleexpressions/DiagramDefinitions.scala @@ -19,7 +19,7 @@ class DiagramDefinition extends ModuleLevelFeature(DiagramDefinition.feature) { def check(dm: DerivedModule)(implicit env: ExtendedCheckingEnvironment) {} override def modules(dm: DerivedModule) = { - val diag = dm.dfC.normalized.getOrElse {throw LocalError("no definiens found")} + val diag : Term = dm.dfC.normalized.getOrElse {throw LocalError("no definiens found")} val ad = diag match { case AnonymousDiagramCombinator(ad) => ad case df => throw LocalError("definiens not a diagram: " + controller.presenter.asString(df)) // TODO should use proper error handler diff --git a/src/mmt-lf/src/info/kwarc/mmt/test/DiagTest.scala b/src/mmt-lf/src/info/kwarc/mmt/test/DiagTest.scala index d81b8941b6..0533713e01 100644 --- a/src/mmt-lf/src/info/kwarc/mmt/test/DiagTest.scala +++ b/src/mmt-lf/src/info/kwarc/mmt/test/DiagTest.scala @@ -6,6 +6,6 @@ object DiagTest extends MMTIntegrationTest()( ExtensionSpec("info.kwarc.mmt.lf.Plugin") ) { def main(): Unit = { - shouldCheck("MMT/mathscheme", "Example.mmt")() + shouldCheck("MMT/Mathscheme", "Example.mmt")() } } From 2795ad823b531248b0e15df0e3d26277888154ce Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Wed, 22 May 2019 11:14:51 +0200 Subject: [PATCH 63/63] Version Bump to 17.0.0 --- src/mmt-api/resources/versioning/system.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmt-api/resources/versioning/system.txt b/src/mmt-api/resources/versioning/system.txt index 946789e619..aac58983e6 100644 --- a/src/mmt-api/resources/versioning/system.txt +++ b/src/mmt-api/resources/versioning/system.txt @@ -1 +1 @@ -16.0.0 +17.0.0