MPS TextGen Aspect
TextGen turns a model (usually the output of generation) into plain text files. It is how BaseLanguage becomes .java on disk, and how any text-targeting language serialises its models. Lives in <lang>/languageModels/textGen.mps, language jetbrains.mps.lang.textGen. Rule bodies are BaseLanguage + smodel + textgen-specific statements (append, indent buffer, with indent).
Prerequisite for any insert: the textGen model must exist (mps_mcp_create_model with modelName: "<lang>.textGen" — aspect ID textGen, case-sensitive, no @ suffix; see aspect-model-stereotypes.md) and must import jetbrains.mps.lang.textGen, jetbrains.mps.baseLanguage, and jetbrains.mps.lang.smodel as used languages before the first mps_mcp_insert_root_node_from_json. Missing any of these three causes node inserts to fail with unresolved-concept errors. See step 1 of the Common-Path Workflow.
Critical Directives
- One
ConceptTextGenDeclarationroot per concept you want to serialise. Only the file-generating root concept needsextension/filename/encoding; structural concepts inside the file need onlytextGenBlock. extensionis a function body returning a string, not a literal property. It mustreturna string.- TextGen dispatch is concept-exact, not polymorphic. An extending concept that wants the parent's serialisation must declare its own
ConceptTextGenDeclaration(even an empty one) — or its output will be silently missing. Seereferences/dispatch-and-base-component.md. - The indentation buffer is a per-output-file depth counter.
with indent/increase depth/decrease depthand thewithIndentflag onNodeAppendPartonly mutate the counter — whitespace is emitted only whenindent bufferis called. Always pairappend \n ;withindent buffer ;on the next line that should be indented. Seereferences/indentation-model.md. - Prefer
with indent { ... }over pairedincrease depth ; … ; decrease depth ;— the block form cannot leak depth on an earlyreturnor exception. Use the paired form only when the scope is not a block. - Use TextGen only when the final artifact is text. If your pipeline ends in a model-to-model transformation targeting another MPS language (e.g. BaseLanguage), you don't need TextGen — that language's own TextGen handles the final step.
BinaryWriteOperation(write) cannot be mixed with textappends in the sameConceptTextGenDeclaration. A rule emits either text or bytes, not both.- The older
$ref{node.reference<target>}syntax is deprecated. Use aNodeAppendPartover a resolved node (or over.name). - Edit textGen models through MPS MCP tools (
mps_mcp_insert_root_node_from_json,mps_mcp_update_node). Do not hand-edit.mpsfiles. - After edits run
mps_mcp_check_root_node_problemsand rebuild the language; regenerate consumers.
Common-Path Workflow
- Create a
textGenmodel (mps_mcp_create_modelwithmodelName: "<lang>.textGen"; aspect ID istextGen— case-sensitive, no@suffix) if absent, then before any insert add the three required used languages:jetbrains.mps.lang.textGen(forConceptTextGenDeclaration,AppendOperation, parts,NodeParameter, etc.),jetbrains.mps.baseLanguage(forStatementList,DotExpression, control flow, returns), andjetbrains.mps.lang.smodel(forSPropertyAccess,SLinkAccess,SLinkListAccessused to read node data). Also add the structure language whose concepts you serialise (referenced byconceptDeclarationand inside smodel accesses), andjetbrains.mps.baseLanguage.collectionsif you use maps/lists. Skipping any of the three core languages causesmps_mcp_insert_root_node_from_jsonto fail with unresolved-concept errors — don't add them piecemeal after a failure. - For each concept to serialise, insert a
ConceptTextGenDeclarationroot viamps_mcp_insert_root_node_from_json. The file-generating concept also needsextension, optionallyencoding/filename/filePath/layout/contextObjects. - Fill the
textGenBlockbody — aStatementListofappends,indent buffer,with indent { ... }, and standard BaseLanguage control flow. Seereferences/statements-and-appends.mdfor the part vocabulary andreferences/json-blueprints.mdfor AST shapes. - Push formatting heuristics ("should this go on a new line?") into the behavior aspect and call them back from textgen (
node.hasNewLineAfter()pattern). Seereferences/delegating-to-behavior.md. - For large-file languages (imports area, headers, shared state), declare a
TextUnitLayout, aUnitContextDeclarationin the baseLanguageTextGenDeclaration, and per-conceptUnitContextObjects. Seereferences/layout-and-context-objects.md. - Validate with
mps_mcp_check_root_node_problems, rebuild the language module, regenerate any consumer, and inspect the produced text.
TextGen vs. Generator
| Aspect | Role |
|---|---|
| Generator | Model → Model (typed AST transformation) |
| TextGen | Model → Text (final serialisation) |
A custom language usually has both: a generator rewrites into BaseLanguage + runtime API, and BaseLanguage's TextGen emits .java. Write your own TextGen only if you target text directly (as jetbrains.mps.core.xml does).
Related Skills
mps-aspect-generator— model-to-model step that usually precedes TextGen; emits the AST you serialise.mps-aspect-behavior— host for layout-heuristic helper methods called from textgen bodies.mps-model-manipulation— BaseLanguage + smodel + collections used inside textgen bodies (StatementList,DotExpression,SLinkAccess,SLinkListAccess,StaticMethodCall).mps-quotations— anti-quotations may appear insideQuotations used as${...}expression values.mps-aspect-structure-concepts— when introducing the concepts a textgen will serialise.
Reference Index
- Open
references/concept-textgen-root.mdwhen adding or editing aConceptTextGenDeclaration— slot purposes (extension,encoding,filename,filePath,layout,contextObjects,textGenBlock), the editor shorthand, and what each child holds. - Open
references/statements-and-appends.mdwhen writing the body —AppendOperationand its part vocabulary (ConstantStringAppendPart,NodeAppendPart,CollectionAppendPart,NewLineAppendPart), the$list{... with sep}form,BinaryWriteOperation(write),FoundErrorOperation(found error), control flow, and the smodel/behavior accessors usable inside a body. - Open
references/indentation-model.mdwhen indentation is wrong — the depth-counter mental model, rules of thumb (always pair\nwithindent buffer, preferwith indent { ... }, usewithIndentflag for one-shot dispatch), and worked examples fromIfStatement_TextGen,BlockStatement_TextGen, andClassConcept_TextGen. - Open
references/layout-and-context-objects.mdwhen emitting multi-region files (headers, imports, body) or threading per-generation state —TextUnitLayoutwithappend to AREA { ... },LanguageTextGenDeclaration,UnitContextDeclarationtypes, and per-conceptUnitContextObjectbindings (the imports-set pattern). Also covers attribute dispatch order (reverse containment) and${attributed node}. - Open
references/dispatch-and-base-component.mdfor concept-exact dispatch — why an extending concept needs its own (possibly empty)ConceptTextGenDeclaration, and how the baseLanguageTextGenDeclarationexposes reusableOperationDeclarations /UtilityMethodDeclarations /UnitContextDeclarations; inheriting between language base components viaextends. - Open
references/delegating-to-behavior.mdwhen textgen bodies grow tangled with formatting heuristics — the xml pattern of movinghasNewLineAfter/isMultiline/onNewLineinto behavior and calling them from textgen, plus utility classes (XmlCharEscape) for escaping/formatting. - Open
references/json-blueprints.mdwhen inserting nodes via MCP — full validated AST shapes for each statement/part (AppendOperationwith literal/expression/list parts,WithIndentOperation,IncreaseDepthOperation/DecreaseDepthOperation,NodeAppendPartwithwithIndent), the minimal file-generating-concept skeleton, layout/context skeletons, and the full validated concept reference (FQNs, concept refs, child roles). - Open
references/common-failures.mdwhen output is missing, indentation is broken, file extension is wrong, special characters appear unescaped, subclasses emit nothing, imports leak inline, attributes are ignored, orwriteandappendcollide.