Skip to content

Commit

Permalink
Merge bc7b7a3 into 951d2fd
Browse files Browse the repository at this point in the history
  • Loading branch information
goto40 committed Jun 4, 2019
2 parents 951d2fd + bc7b7a3 commit 28fbd51
Show file tree
Hide file tree
Showing 9 changed files with 729 additions and 57 deletions.
44 changes: 41 additions & 3 deletions docs/scoping.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,44 @@ We provide some standard scope providers:
to model inheritance or chained lookups**.
Example: see [tests/test_scoping/test_local_scope.py](https://github.com/textX/textX/blob/master/tests/functional/test_scoping/test_local_scope.py).

### Custom name resolution

In some cases, the name of an element needs to be derived from some model data (instead of
being specified explicitly via a `name` attribute). The FQN based scope providers and
the `RelativeName` and `RelativeNameExt` scope providers can get an additional argument
`name_resolver`, which is a callable to determine the name of an object
(see `textx.scoping.providers.default_name_resolver` for the
default implementation and documentation). In short, this function allows to **deduce
the name of an object** and also to **postpone the name resolution**, if the name depends
on unresolved references.

* The FQN scope providers make a type unspecific search of named containers to guide their
way through the model. To **prevent circular deperendencies** (of references waiting for
a postponed name to be resolved), we **restrict the name resolution logic to be
applied to the final element only of a FQN reference string**: E.g., for
`"pack1.pack2.class1"`, the name resolution logic will only
be applied to the final element (`"class1"`).
* The `RelativeName` and the `RelativeNameExt` scope providers do not need such a restriction,
since they operate on a small set of reference names.

**Functionality:** When reference resolution searches for a name for which the scope
resolution logic is applied, **all non-postponed names are taken into account**.

* **(1)** If postponed names are found,
the reference to be resolved it Postponed.
* **(2)** Else, if a match is found, that object is used.
* **(3)** Else, no match is found on that level.

Custom name resolution is not available for `PlainName` based scope providers.


### Note on Uniqueness of Model Element Names

If the model-string provides non unique names of elements, the created model
is undefined with respect to references involving these non-unique names.
The model object in that case is a valid, and the uniqueness of element names
can (and must!) be checked using a validator (object processor) after model
parsing and scope resolution.

### Note on Uniqueness of Model Elements (global repository)

Expand Down Expand Up @@ -280,7 +318,7 @@ If you want to **reference an element not directly modelled** (instantiated), yo
instantiate or load this element or information somewhere. This information can be,
e.g., information from a non-textx model, such as a JSON file
(see: [test_reference_to_nontextx_attribute.py](https://github.com/textX/textX/blob/master/tests/functional/test_scoping/test_reference_to_nontextx_attribute.py)).
Since you need to resolve a reference (e.g. to an ```[OBJECT]``` in the
Since you need to resolve a reference (e.g. to an `[OBJECT]` in the
given example), you cannot rely on object processors, since they are executed
*after* reference resolution. Thus, scope providers need to take care of
that (e.g., take care of loading the JSON data).
Expand All @@ -298,7 +336,7 @@ a reference" by another "non-inventing reference",
you must take into account that these elements
may have not yet been created. This can be achieved in the same
way as handling unresolved references in a scope provider (with the
```Postponed``` mechanism). This use case was motivated by
`Postponed` mechanism). This use case was motivated by
[#167](https://github.com/textX/textX/issues/167).

An example of such a meta model is given in
Expand All @@ -320,6 +358,6 @@ In an extension of the grammar we then also allow

**Implementation:** Since it is unclear if a
nonexistent person may be created by a not yet resolved "knows"-relationship reference,
we have to postpone the resolution of a failed greeting (return ```Postponed```).
we have to postpone the resolution of a failed greeting (return `Postponed`).
The reference resolution mechanism will detect if a state is reached
with only postponed references and will then raise an error, as expected.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package interfaces {
interface B
interface C
}
package base {
interface A
interface D
component Start {
out output1 (format A)
}
component Middle {
in input2 (format A,interfaces.B,interfaces.C)
out output2 (format interfaces.B,interfaces.C,D)
}
component End {
in input3 (format interfaces.B,interfaces.C,D)
}
}

package usage {
instance start : base.Start
instance action1 : base.Middle
instance action2 : base.Middle
instance action3 : base.Middle
instance end : base.End

connect start.OUTPUT1 to action1.INPUT2
connect action1.OUTPUT2 to action2.INPUT2
connect action2.OUTPUT2 to action3.INPUT2
connect action3.OUTPUT2 to end.INPUT3
}
43 changes: 43 additions & 0 deletions tests/functional/test_scoping/test_local_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,49 @@ def test_model_with_local_scope():
#################################


def test_model_with_local_scope_with_uppercase():
"""
This is a basic test for the local scope provider (good case).
Test if the name resolver works for a simple case (to upper)
"""
#################################
# META MODEL DEF
#################################

def uppercase_name_resolver(obj):
if obj is None:
return None
elif hasattr(obj, "name"):
return obj.name.upper()

my_meta_model = metamodel_from_file(
join(abspath(dirname(__file__)), 'components_model1',
'Components.tx'))
my_meta_model.register_scope_providers({
"*.*": scoping_providers.FQN(),
"Connection.from_port":
scoping_providers.RelativeName(
"from_inst.component.slots",
name_resolver=uppercase_name_resolver),
"Connection.to_port":
scoping_providers.RelativeName(
"to_inst.component.slots",
name_resolver=uppercase_name_resolver),
})

#################################
# MODEL PARSING
#################################

my_meta_model.model_from_file(
join(abspath(dirname(__file__)), "components_model1",
"example_uppercase.components"))

#################################
# END
#################################


def test_model_with_local_scope_and_error():
"""
This is a basic test for the local scope provider (bad case).
Expand Down
26 changes: 26 additions & 0 deletions tests/functional/test_scoping/test_name_resolver/file1.model
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import "file2.model"

package p1 {
component;
component unnamed_1;
component unnamed_4;

package p2 {
component unnamed_2;
component unnamed_3;
package p3 {
component; // unnamed_0
component; // unnamed_1
component; // unnamed_2

instance i0: unnamed_0
instance i1: unnamed_1
instance i2: unnamed_2
instance i3: unnamed_3 // from p2
instance i4: unnamed_4 // from p1
instance i5: p1.unnamed_4 // from p1
instance i6: p1.unnamed_0 // from p1
instance i7: p1.unnamed_3 // from p1 (other file)
}
}
}
6 changes: 6 additions & 0 deletions tests/functional/test_scoping/test_name_resolver/file2.model
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package p1 {
component;
component;
component;
component;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from __future__ import unicode_literals
import textx
from textx.scoping import Postponed
from textx.scoping.providers import default_name_resolver
from textx.scoping.tools import resolve_model_path


MyLanguage = r"""
Model
: (locations+=Location)*
(employees+=Employee)*
(positions+=Position)*
(projects+=Project)*
;
Project
: 'project' name=ID
('{'
('use' use=[Position])*
'}')?
;
Position
: 'define' 'position' employee=[Employee|FQN]
'->' location=[Location|FQN] ('as' name=ID)?
;
Employee
: 'employee' name=ID
;
Location
: 'location' name=ID
( '{'
(sub_location+=Location)+
'}')?
;
FQN
: ID('.' ID)*
;
Comment:
/\/\/.*$/
;
"""

MyCode = """
location Building
{
location Entrance
location Exit
}
employee Hans
employee Juergen
// Shall be referred to with the given name: "EntranceGuy"
define position Hans->Building.Entrance as EntranceGuy
// Shall be referred to with the autogenerated name:
// <Employee>"At"<LastLocation>
define position Juergen->Building.Exit
project SecurityProject
{
use EntranceGuy
use JuergenAtExit
}
"""


def test_issue193_auto_name():
meta_model = textx.metamodel_from_str(MyLanguage)

def position_name_generator(obj):
if obj is None:
return None
elif textx.textx_isinstance(obj, meta_model["Position"]):
if obj.name is not None and len(obj.name) > 0:
return obj.name
location = resolve_model_path(obj, "location")
employee = resolve_model_path(obj, "employee")
if type(location) is Postponed or type(employee) is Postponed:
return Postponed()
assert location is not None and employee is not None
return employee.name + "At" + location.name
else:
return default_name_resolver(obj)

meta_model.register_scope_providers({
"*.*": textx.scoping.providers.FQN(
name_resolver=position_name_generator),
})

model = meta_model.model_from_str(MyCode)
assert model, "Could not create model..."

0 comments on commit 28fbd51

Please sign in to comment.