00_metamodeling.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # Before we can create a model in muMLE, we have to create a meta-model.
  2. # Here's an example of a (silly) meta-model.
  3. # We use a textual concrete syntax:
  4. mm_cs = """
  5. # A class named 'A':
  6. A:Class
  7. # A class named 'B':
  8. B:Class
  9. # An association from 'A' to 'B':
  10. a2b:Association (A -> B) {
  11. # Every 'A' must be associated with at least one 'B'
  12. target_lower_cardinality = 1;
  13. }
  14. """
  15. # Now, we create a model that is an instance of our meta-model:
  16. m_cs = """
  17. myA:A
  18. myB:B
  19. myLnk:a2b (myA -> myB)
  20. """
  21. # Notice that the syntax for meta-model and model is the same: We always declare a named object/link, followed by a colon (:) and the name of the type. The type name refers to the name of an object/link in the meta-model of our model.
  22. # So far we've only created text strings in Python. To parse them as models, we first create our 'state', which is a mutable graph that will contain our models and meta-models:
  23. from state.devstate import DevState
  24. state = DevState()
  25. # Next, we must load the Simple Class Diagrams (SCD) meta-meta-model into our 'state'. The SCD meta-meta-model is a meta-model for our meta-model, and it is also a meta-model for itself.
  26. # The meta-meta-model is not specified in textual syntax because it is typed by itself. In textual syntax, it would contain things like:
  27. # Class:Class
  28. # which is an object typed by itself. The parser cannot handle this (or circular dependencies in general). Therefore, we load the meta-meta-model by mutating the 'state' directly at a very low level:
  29. from bootstrap.scd import bootstrap_scd
  30. print("Loading meta-meta-model...")
  31. mmm = bootstrap_scd(state)
  32. print("OK")
  33. # Now that the meta-meta-model has been loaded, we can parse our meta-model:
  34. from concrete_syntax.textual_od import parser
  35. print()
  36. print("Parsing meta-model...")
  37. mm = parser.parse_od(
  38. state,
  39. m_text=mm_cs, # the string of text to parse
  40. mm=mmm, # the meta-model of class diagrams (= our meta-meta-model)
  41. )
  42. print("OK")
  43. # And we can parse our model, the same way:
  44. print()
  45. print("Parsing model...")
  46. m = parser.parse_od(
  47. state,
  48. m_text=m_cs,
  49. mm=mm, # this time, the meta-model is the previous model we parsed
  50. )
  51. print("OK")
  52. # Now we can do a conformance check:
  53. from framework.conformance import Conformance, render_conformance_check_result
  54. print()
  55. print("Is our model a valid instance of our meta model?")
  56. conf = Conformance(state, m, mm)
  57. print(render_conformance_check_result(conf.check_nominal()))
  58. # Looks like it is OK!
  59. # We can also check if our meta-model is a valid class diagram:
  60. print()
  61. print("Is our meta-model a valid class diagram?")
  62. conf = Conformance(state, mm, mmm)
  63. print(render_conformance_check_result(conf.check_nominal()))
  64. # Also good.
  65. # Finally, we can even check if the meta-meta-model is a valid instance of itself (it should be):
  66. print()
  67. print("Is our meta-model a valid class diagram?")
  68. conf = Conformance(state, mmm, mmm)
  69. print(render_conformance_check_result(conf.check_nominal()))
  70. # All good!
  71. # Now let's make things a bit more interesting and introduce non-conformance:
  72. m2_cs = """
  73. myA:A
  74. myA2:A
  75. myB:B
  76. myLnk:a2b (myA -> myB)
  77. """
  78. # Parse it:
  79. m2 = parser.parse_od(
  80. state,
  81. m_text=m2_cs,
  82. mm=mm,
  83. )
  84. # The above model is non-conformant because 'myA2' should have at least one outgoing link of type 'a2b', but it doesn't.
  85. print()
  86. print("Is model 'm2' a valid instance of our meta-model? (it should not be)")
  87. conf = Conformance(state, m2, mm)
  88. print(render_conformance_check_result(conf.check_nominal()))
  89. # It should be non-conformant.
  90. # Finally, let's render everything as PlantUML:
  91. from concrete_syntax.plantuml import renderer as plantuml
  92. from concrete_syntax.plantuml.make_url import make_url
  93. uml = (""
  94. + plantuml.render_package("Meta-model", plantuml.render_class_diagram(state, mm))
  95. + plantuml.render_package("Model", plantuml.render_object_diagram(state, m, mm))
  96. + plantuml.render_trace_conformance(state, m, mm)
  97. # + plantuml.render_package("Meta-meta-model", plantuml.render_class_diagram(state, mmm))
  98. # + plantuml.render_trace_conformance(state, mm, mmm)
  99. )
  100. print()
  101. print("PlantUML output:", make_url(uml))
  102. # On to the next tutorial...