Core Architecture: Major System: Code Generation - UA-ScriptEase/scriptease GitHub Wiki
- Code Generation generates game code based on the specified Translator and the constructed Story.
- It is multi threaded, so you cannot use anything static in any of the classes.
- Here are some of the biggest subsystems and a brief explanation of their purpose in Code Generation:
Language Dictionary: defines the language specific format of the code which should be output by code gen
- API Dictionary : provides resolution values to ScriptEase constructs. Things like Types, DoIt code, and Slots are taken from here.
- Context System : the context system provides code gen with a reference point to requests in Language Dictionary and DoIt code. For example: a request for a "name" or "children" mean something different depending on the context (scope) of the call.
- Fragments : pieces of formatted code to be resolved and inserted into the outputted game code. Code Generation breaks down Language Dictionary into these format fragments based on context (scope), lines, indents and other keywords.
- Game Module : specifies how to deal with the outputted game code (where to write it, save it, ...) among other things.
-
Start Code Generation is started by calling
generateScriptFile(Set rootBag, Translator translator)
which grabs all of theCodeBlocks(subject, slot, code)
from the model tree and the selected Translator. -
Semantic Check
SemanticAnalyzer
then processes the Story Roots with aStoryVisitor
, and determines any errors within the Story Tree. If this succeeds then code generation continues. -
Setup
generateScript(Context context)
is then called with the initial Context created bySemanticAnalyzer
. This starts the initial format resolution on the Language Dictionary defined "File" format which will recursively resolve until the code for the File has been generated. -
Format Resolution
FormatFragment
'sresolveFormat(Collection format, Context context)
andresolve(Context context)
methods are called recursively until there are all fragments have been resolved. This resolution is determined by the type ofFragment
(eg.SimpleFragment
,ScopeFragment
,SeriesFragment
,LiteralFragment
, ...), the context in which the fragment resides, and the dataLabel (desired information) requested by the Language Dictionary author in that Fragment. -
Compile and Save Upon completion of Fragment resolution, If the
CodeGeneration
had any errors, the compilation step is skipped and the user is informed. The module is then saved with the optional compile step.
Fragments and Contexts are the key components of code resolution, they specify which value is desired and where to place it in the code. Fragments are the little bits of data or processing that ScriptEase needs to do, while Contexts are descriptions of where that data comes from.
The following is a snapshot of SimpleFragment#resolve(...)
:
public String resolve(Context context) {
super.resolve(context);
final String dataLabel = this.getDirectiveText();
// IF+ELSE BLOCK (fragment data = <dataLabel>)
if (dataLabel.equals(TranslatorKeywordManager.XML_NAME_FORMAT))
return context.getUniqueName(this.legalRange);
else if (dataLabel.equals(TranslatorKeywordManager.XML_TYPE_FORMAT)) {
String type = context.getType();
if (type.equals(Context.UNIMPLEMENTED) && !defaultText.isEmpty())
return defaultText;
else
return type;
} else if (dataLabel.equals(TranslatorKeywordManager.XML_CODE_FORMAT))
return context.getCode();
else if (dataLabel.equals(TranslatorKeywordManager.XML_VALUE_FORMAT)) {
return context.getValue();
} else if (dataLabel
.equals(TranslatorKeywordManager.XML_CONDITION_FORMAT))
return context.getCondition();
else if (dataLabel.equals(TranslatorKeywordManager.XML_COMMENT_FORMAT))
return context.getNotes();
else if (dataLabel.equals(TranslatorKeywordManager.XML_SLOT))
return context.getSlot().getKeyword();
else if (dataLabel.equals(TranslatorKeywordManager.XML_FORMATTED_VALUE))
return context.getFormattedValue();
return "<Simple Fragment was unable to be resolved for data: "
+ dataLabel + " >";
}
Without going into much detail, we can see a long if/else block (... I know... ) which handles various data request cases. This defines all the possible data one can request from a SimpleFragment, which asks its Context for the resolution.
Here's what a KnowItContext
looks like:
public class KnowItContext extends StoryComponentContext {
.
. //Constructors and stuff
.
.
StoryComponent
// The part we actually care about
/**
* Get the KnowIt's Binding
*/
@Override
public KnowItBinding getBinding() {
return ((KnowIt) component).getBinding();
}
/**
* Get the KnowIt's Binding's Type
*/
@Override
public String getType() {
final APIDictionaryInterpreter apiDictionary;
final String bindingType;
apiDictionary = this.getTranslator().getApiDictionary();
bindingType = apiDictionary.retrieveTypeCodeSymbol(getBinding()
.getType());
if (bindingType != null)
return bindingType;
else
return apiDictionary.retrieveTypeCodeSymbol(((KnowIt) component)
.getDefaultType());
}
}
For example, if Language Dictionary says to insert the type of a DoIt
's parameter, we create a SimpleFragment
with the dataLabel = type
in a KnowItContext
. From the SimpleFragment
we would ask for the type, which knows to ask the KnowItContext
for the KnowIt
's Binding
's type.
Any request for a value which is undefined in the given Context returns an "Unimplemented"
string letting the Language Dictionary author know what they requested is unsupported. However given the structure of the Code Generation system, it should be easy to add new implementations and supported features.
When you want to add support for a value in a given XyzContext where XyzContext is a subclass of Context
, all you must do is override the request method in XyzContext. As long as the Language Dictionary scopes into the proper Context and you request the dataLabel
, it should resolve to the desired value.
In the case where you want to support additional Contexts, a few things must happen:
- Create a subClass of Context, which overrides the desired request methods.
- Add this Context to ContextFactory so it can be created
[DEPRECATED]Create a unique Scope (Context) keyword in the apiDictionary.dtd && languageDictionary.dtd and match it to a if/else block in ScopeContext. This will allow for the changing (scoping) of context from the dictionaries.- Make sure to scope into this new context in your Dictionary Authoring before requesting the desired dataLabel.
Code Generation generates names through the CodeGenerationNamifier
. The Namifier legalizes the chracters of displayName
of StoryComponent
s and appends integers to the end until they are unique and do not collide with any reserved words in the Language Dictionary. It then stores these in maps so we can reuse the same name when referring to the same component. Extensions can be done to separate unique name generation between functions and variables, as they often can share the same name (which removes unnecessary unification and adds code beautification).