Mate is an object graph language implementation that allows you to define grammars representing software system structures and generate valid object instances from those grammars. To know more about Mate:
- Mate Language
- Mate Grammar Examples
- How Mate Works
- Derivation Model
- Object Graph Fuzzing
- Object Graph Parsing
- Object Graph Type Validation
- Dynamic Grammar Generation by Test Suites
Mate is built on a systematic, grammar-driven framework that decouples structural definitions from object instances or Mate definitions, enabling the generation of complex, state-consistent object graphs. It employs a feedback-oriented derivation process, in which the object grammar productions are iteratively refined using any metric.
With Mate, you can:
- Declare your own Object Graph Grammars
- Generate valid object instances from a grammar and historical reward ( Monte Carlo Tree Search )
- Mate Object Graphs derivations visualization
- Propagate rewards through the derivation tree to improve future generations
- Parse and validate object graphs against your Mate grammar
- Generate Mate Object graph Grammar definitions from test object suites
Metacello new
baseline: 'Mate';
repository: 'github://FedeLoch/Mate:main';
onConflictUseIncoming;
load.The default group loads the Mate package only. The paper experiments live in the Mate-Experiments package, and loading the experiments group also loads Moose and Ume:
Metacello new
baseline: 'Mate';
repository: 'github://FedeLoch/Mate:main';
onConflictUseIncoming;
load: 'experiments'.Person -> { age: Age }
Age -> { value: SmallInteger }Generating Person instances:
grammar := MatePersonGrammar new.
person := grammar gen: Person.
person age. "=> a SmallInteger, biased toward boundary values such as 0, -1 or SmallInteger maxVal"Player -> {
name: ByteString,
characterClass: Warrior | Rogue | Mage,
level: { type: SmallInteger, between: 0 and: 99 },
missingExperienceForNextLevel: { type: SmallInteger, greaterThan: 0 }
}Generating player instances:
grammar := MatePlayerGrammar new.
player := grammar gen: Player.
player name. "=> 'xyz123'"
player characterClass. "=> a Warrior"
player level. "=> 47 (always 0-99)"
player missingExperienceForNextLevel. "=> always > 0"greaterThan: and lessThan: exclude the bound, atLeast:, atMost: and between:and: include it.
BlElement -> {
children: BlChildrenArray,
parent: { refType: BlElement },
visuals: (BlCustomVisuals | BlDefaultVisuals),
constraints: BlLayoutCommonConstraints,
selfReferences: { children.array[ .parent ] }
}
BlChildrenArray -> {
array: { type: [ BlElement ], sizeBetween: 0 and: 10 }
}
BlCustomVisuals -> {
background: (BlPaintBackground | BlTransparentBackground),
geometry: (BlRectangleGeometry | BlEllipseGeometry | BlTriangleGeometry),
clipChildren: Boolean,
}
BlLayoutCommonConstraints -> {
position: Point,
vertical: BlLayoutCommonConstraintsAxis,
horizontal: BlLayoutCommonConstraintsAxis
}
BlLayoutCommonConstraintsAxis -> {
resizer: BlLayoutExactResizer
}
BlLayoutExactResizer -> {
size: { type: SmallFloat64, between: 100 and: 1000 }
}
BlPaintBackground -> {
paint: BlColorPaint
}
BlColorPaint -> {
color: Color
}
BlTriangleGeometry -> {
orientation: { oneOf: { #top . #right . #left . #bottom } }
}Generating Bloc Element instances:
grammar := MateBlocGrammar new.
element := grammar gen: BlElement.grammar := MateBlocGrammar new.
derivationTree := grammar gen: BlElement from: MateContext new.
derivationTree renderPropagate rewards through the derivation tree to improve future generations:
grammar := MatePlayerGrammar new.
derivationTree := grammar gen: Player from: MateContext new.
player := derivationTree instance.
"Propagating feedback for future generations"
grammar backpropagate: derivationTree improvement: 42."Comments are written between double quotes"
Example -> {
count: SmallInteger,
ratio: { type: Float, between: -0.5 and: 1.5e2 },
positive: { type: SmallInteger, greaterThan: 0 },
limit: { type: SmallInteger, atLeast: 1, atMost: 10 },
code: { type: SmallInteger, in: [ 200, 404, 500 ] },
label: ByteString?,
optionalCount: { type: SmallInteger, atLeast: 0, nullable: true },
shape: (BlEllipseGeometry | BlTriangleGeometry)?,
kind: { oneOf: { #small . #large . 'text' . 42 . -1 . true . nil } },
species: OrderedCollection class | Set class,
items: { type: [ SmallInteger | ByteString ], sizeBetween: 0 and: 5 },
pair: { type: [ Point ], size: 2 },
owner: { refType: Example },
selfReferences: { items[ .owner ] }
}| Construct | Meaning |
|---|---|
A | B |
Union of types, flattened and printed in alphabetical order |
T? / nullable: true |
The field may be generated as nil (see nilProbability:) |
between: a and: b, atLeast:, atMost: |
Inclusive numeric bounds |
greaterThan:, lessThan: |
Exclusive numeric bounds |
in: [ ... ] |
The value is one of the listed literals |
oneOf: { ... } |
The field is one of the listed literals (symbols, strings, numbers, booleans, nil, classes) |
[ T ] with sizeBetween: a and: b or size: n |
Array of T; without a size it defaults to sizeBetween: 0 and: 10 |
refType: T |
Reference filled by a selfReferences path, not generated |
X class, X classTrait |
Metaclass types |
Identifiers may contain letters, digits and _. Numbers may be negative and use decimals or exponents. Malformed input raises an error that includes the position where parsing failed.
Every generator draws from a single random generator owned by the grammar, so a seed replays the same sequence of objects:
grammar := MatePlayerGrammar new.
grammar seed: 1234.
players := (1 to: 100) collect: [ :i | grammar gen: Player ].Built-in generators are biased toward values that tend to expose bugs, and always respect the declared class and constraints:
| Type | Generated values |
|---|---|
SmallInteger, LargePositiveInteger, LargeNegativeInteger |
Boundary values (0, ±1, powers of two, 32/64-bit limits, minVal/maxVal, constraint bounds), small values, and uniform values over the whole range |
Float, SmallFloat64, BoxedFloat64 |
0.0, -0.0, ±1.0, fmax, denormals, NaN and ±infinity (only for classes that can hold them), constraint bounds and their neighbours, and values across many magnitudes |
ByteString, WideString, ByteSymbol, Character |
Empty, short and long strings with letters, digits, punctuation, control bytes and (for WideString) Unicode characters |
Point, Color, Boolean, Object, UndefinedObject |
Random instances |
Generation stops at maxDerivationHeight (12 by default). At the limit, a field takes a built-in option if its type allows one, otherwise it is set to nil. Nullable fields are set to nil with probability nilProbability (0.1 by default):
grammar generator maxDerivationHeight: 6; nilProbability: 0.2.grammar explorationPercentage. "coverage of the declarations reachable from the grammar's own types"
grammar explorationPercentageFrom: Player. "coverage of the declarations reachable from Player"parse: checks an object graph against the grammar and returns its derivation tree. It raises an error when a field has the wrong type, an array has the wrong size, a value violates a constraint, or a selfReferences path does not hold. It never modifies the object, accepts subclasses of declared types, and terminates on cyclic graphs.
grammar := MatePlayerGrammar new.
grammar parse: (Player new name: 'Fede'; level: 42; characterClass: Rogue new; missingExperienceForNextLevel: 1).
grammar parse: (Player new name: 'Fede'; level: 500; characterClass: Rogue new; missingExperienceForNextLevel: 1).
"=> Error: Value 500 of type SmallInteger violates MateConstraint( min: 0 max: 99)"grammar := MateOGGrammar fromObjects: { player1 . player2 . player3 }.
grammar mateDefinition.Inference merges the observed classes of each field into unions, merges array element types and size ranges (empty arrays add no evidence), records Booleans as Boolean, and records fields that hold classes as oneOf choices. The output of mateDefinition is sorted by type name and parses back into the same grammar.


