Importer and Parser
When importing alphaTex into alphaTab it directly loads the provided code and, if successful, provides the loaded data model. Using the alphatex importer and parser more directly, allows access to more detailed information like the abstract syntax tree.
This information can be very useful when building certain tooling and integrations. The alphaTab language server itself also uses these components.
The code samples here are written in TypeScript but the APIs are similar on C# and Kotlin and can easily be mapped by adjusting the naming conventions.
Importer​
The importer is the main component for loading alphaTex into a full data model. It is available via alphaTab.importers.AlphaTexImporter.
The most basic usage of it is:
import * as alphaTab from '@coderline/alphatab';
// create importer
const importer = new alphaTab.importer.AlphaTabImporter();
importer.initFromString(`
\\title "Hello World"
C4 * 4
`, new alphaTab.Settings());
// load score
const score = importer.readScore();
console.log(score.title); // will print "Hello World"
Diagnostics​
The importer undergoes two phases:
- Parse the AST using
alphaTab.importer.alphaTex.AlphaTexImporter - Map the AST to the data model.
If everything goes well the score will be provided and additional non-error diagnostics are available on the importer. If something goes wrong during parsing or mapping, no score will be provided and an exception is thrown.
The main Error/Exception thrown will always be a alphaTab.importer.UnsupportedFormatError with a basic textual description of what went wrong.
If the exceptoin was caused by error diagnostics (e.g. parse errors) the inner exception (cause) is a alphaTab.importer.AlphaTexErrorWithDiagnostics
providing access to all diagnostics.
These exceptions mainly exist for cases where the importer or parser is not used directly. The diagnostics are also available on the importer directly.
import * as alphaTab from '@coderline/alphatab';
function printDiagnostic(a:alphaTab.importer.alphaTex.AlphaTexDiagnostic) {
const severityText = alphaTab.importer.alphaTex.AlphaTexDiagnosticsSeverity[a.severity];
const startText = a.start ? `Ln ${a.start.Line}, Col ${a.start.col}`: '';
const endText = a.end ? `Ln ${a.end.Line}, Col ${a.end.col}`: '';
const fullMessage = `[${severityText}] [AT${a.code}] ${a.message} (${startText} -> ${endText})`;
switch(a.severity) {
case alphaTab.importer.alphaTex.AlphaTexDiagnosticsSeverity.Hint:
console.info(fullMessage)
break;
case alphaTab.importer.alphaTex.AlphaTexDiagnosticsSeverity.Warning:
console.warn(fullMessage)
break;
case alphaTab.importer.alphaTex.AlphaTexDiagnosticsSeverity.Error:
console.error(fullMessage)
break;
}
}
// create importer
const importer = new alphaTab.importer.AlphaTabImporter();
importer.initFromString(`
\\invalid alphatex
`, new alphaTab.Settings());
try {
// load score
const score = importer.readScore();
} catch(e) {
console.error('Error parsing alphaTex: ', (e as Error).message);
console.error('Lexer diagnostics: ');
for(const d of importer.lexerDiagnostics) {
printDiagnostic(d);
}
console.error('Parser diagnostics: ');
for(const d of importer.parserDiagnostics) {
printDiagnostic(d);
}
console.error('Semantic diagnostics: ');
for(const d of importer.semanticDiagnostics) {
printDiagnostic(d);
}
}
Diagnostics give access to a range of information like the location in the code to which it maps, the severity, a technical code identifying the error and a textual error message.
AST​
The importer also allows access to the abstract syntax tree of the parsed code. The AST might also be available for only partially parsed code.
The following code should give an idea on how to access the AST by printing the whole hiearchy of nodes to the console:
import * as alphaTab from '@coderline/alphatab';
const importer = new alphaTab.importer.AlphaTexImporter();
importer.initFromString(`
\\title "Hello World"
C4 * 4
`, new alphaTab.Settings());
const score = importer.readScore();
const astNode = importer.scoreNode;
// a basic recursive node visitor printing the nodes
function visitNode(name:string, node:alphaTab.importer.alphaTex.IAlphaTexAstNode | undefined, indent:number) {
const indentText = ' '.repeat(indent);
const prefix = name ? `${indentText} ${name}:` : indentText;
if(!node) {
console.log(`${prefix} undefined`);
return;
}
const startText = a.start ? `Ln ${a.start.Line}, Col ${a.start.col}`: '';
const endText = a.end ? `Ln ${a.end.Line}, Col ${a.end.col}`: '';
const baseMessage = `${prefix} ${alphaTab.importer.alphaTex.AlphaTexNodeType[node.type]} (${startText} -> ${endText})`;
switch(node.nodeType) {
// Tokens
case alphaTab.importer.alphaTex.AlphaTexNodeType.Dot:
case alphaTab.importer.alphaTex.AlphaTexNodeType.Backslash:
case alphaTab.importer.alphaTex.AlphaTexNodeType.DoubleBackslash:
case alphaTab.importer.alphaTex.AlphaTexNodeType.Pipe:
case alphaTab.importer.alphaTex.AlphaTexNodeType.LBrace:
case alphaTab.importer.alphaTex.AlphaTexNodeType.RBrace:
case alphaTab.importer.alphaTex.AlphaTexNodeType.LParen:
case alphaTab.importer.alphaTex.AlphaTexNodeType.RParen:
case alphaTab.importer.alphaTex.AlphaTexNodeType.Colon:
case alphaTab.importer.alphaTex.AlphaTexNodeType.Asterisk:
console.log(baseMessage);
break;
// General Nodes
case alphaTab.importer.alphaTex.AlphaTexNodeType.Ident:
console.log(`${baseMessage} ${(node as alphaTab.importer.alphaTex.AlphaTexIdentifier).text}`);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Tag:
console.log(`${baseMessage}`);
const identifier = (node as alphaTab.importer.alphaTex.AlphaTexIdentifier):
visitNode('prefix' identifier.prefix, ident + 1);
visitNode('tag', identifier.tag, ident + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Meta :
console.log(`${baseMessage}`);
const meta = (node as alphaTab.importer.alphaTex.AlphaTexMetaDataNode);
visitNode('tag', meta.tag, ident + 1);
visitNode('arguments', meta.arguments, ident + 1);
visitNode('properties', meta.properties, ident + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Arguments:
console.log(`${baseMessage}`);
const args = (node as alphaTab.importer.alphaTex.AlphaTexArgumentList);
visitNode('openParenthesis', args.openParenthesis, ident + 1);
for(const [i,a] of args.arguments.entries()) {
visitNode(`arguments[${i}]`, a, ident + 1);
}
visitNode('closeParenthesis', args.closeParenthesis, ident + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Props:
console.log(`${baseMessage}`);
const props = (node as alphaTab.importer.alphaTex.AlphaTexPropertiesNode);
visitNode('openBrace', props.openBrace, ident + 1);
for(const [i,p] of props.properties.entries()) {
visitNode(`properties[${i}]`, p, ident + 1);
}
visitNode('closeBrace', args.closeBrace, ident + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Prop:
console.log(`${baseMessage}`);
const props = (node as alphaTab.importer.alphaTex.AlphaTexPropertyNode);
visitNode('property', props.property, ident + 1);
visitNode('arguments', props.arguments, ident + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Number:
console.log(`${baseMessage} ${(node as alphaTab.importer.alphaTex.AlphaTexNumberLiteral).value}`);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.String:
console.log(`${baseMessage} ${(node as alphaTab.importer.alphaTex.AlphaTexStringLiteral).text}`);
break;
// Semantic Nodes
case alphaTab.importer.alphaTex.AlphaTexNodeType.Score:
console.log(`${baseMessage}`);
const score = (node as alphaTab.importer.alphaTex.AlphaTexScoreNode);
for(const [i,b] of score.bars.entries()) {
visitNode(`bars[${i}]`, b, indent + 1);
}
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Bar:
console.log(`${baseMessage}`);
const bar = (node as alphaTab.importer.alphaTex.AlphaTexBarNode);
for(const [i,m] of bar.metaData.entries()) {
visitNode(`metaData[${i}]`, m, indent + 1);
}
for(const [i,b] of bar.beats.entries()) {
visitNode(`beats[${i}]`, b, indent + 1);
}
visitNode(bar.pipe, indent + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Beat:
console.log(`${baseMessage}`);
const beat = (node as alphaTab.importer.alphaTex.AlphaTexBeatNode);
visitNode('durationChange', beat.durationChange, indent + 1);
for(const [i,n] of beat.notes.entries()) {
visitNode(`notes[${i}]`, n, indent + 1);
}
visitNode('rest', beat.rest, indent + 1);
visitNode('durationDot', beat.durationDot, indent + 1);
visitNode('durationValue', beat.durationValue, indent + 1);
visitNode('beatMultiplier', beat.beatMultiplier, indent + 1);
visitNode('beatMultiplierValue', beat.beatMultiplierValue, indent + 1);
visitNode('beatEffects', beat.beatEffects, indent + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Duration:
console.log(`${baseMessage}`);
const duration = (node as alphaTab.importer.alphaTex.AlphaTexBeatDurationChangeNode);
visitNode('colon', duration.colon, indent + 1);
visitNode('value', duration.value, indent + 1);
visitNode('properties', duration.properties, indent + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.NoteList:
console.log(`${baseMessage}`);
const noteList = (node as alphaTab.importer.alphaTex.AlphaTexNoteListNode);
visitNode('openParenthesis', noteList.openParenthesis, indent + 1);
for(const [i,n] of noteList.notes.entries()) {
visitNode(`notes[${i}]`, n, indent + 1);
}
visitNode('closeParenthesis', noteList.openParenthesis, indent + 1);
break;
case alphaTab.importer.alphaTex.AlphaTexNodeType.Note:
console.log(`${baseMessage}`);
const note = (node as alphaTab.importer.alphaTex.AlphaTexNoteNode);
visitNode('noteValue', noteList.noteValue, indent + 1);
visitNode('noteStringDot', noteList.noteStringDot, indent + 1);
visitNode('noteString', noteList.noteString, indent + 1);
visitNode('noteEffects', noteList.noteEffects, indent + 1);
break;
}
}
visitNode('', astNode, 0);
The output of the above code is:
Score (Ln 1, Col 1 -> Ln 3, Col 11)
bars[0]: Bar (Ln 2, Col 5 -> Ln 3, Col 11)
metaData[0]: Meta (Ln 2, Col 5 -> Ln 2, Col 24)
tag: Tag (Ln 2, Col 5 -> Ln 2, Col 11)
prefix: Backslash (Ln 2, Col 5 -> Ln 2, Col 6)
tag: Ident (Ln 2, Col 6 -> Ln 2, Col 11) title
arguments: Arguments (Ln 2, Col 12 -> Ln 2, Col 24)
openParenthesis: undefined
arguments[0]: String (Ln 2, Col 12 -> Ln 2, Col 24) Hello World
closeParenthesis: undefined
properties: undefined
beats[0]: Beat (Ln 3, Col 5 -> Ln 3, Col 11)
durationChange: undefined
notes: NoteList (Ln 3, Col 5 -> Ln 3, Col 7)
openParenthesis: undefined
notes[0]: Note (Ln 3, Col 5 -> Ln 3, Col 7)
noteValue: Ident (Ln 3, Col 5 -> Ln 3, Col 7) C4
noteStringDot: undefined
noteString: undefined
noteEffects: undefined
closeParenthesis: undefined
rest: undefined
durationDot: undefined
durationValue: undefined
beatMultiplier: Asterisk (Ln 3, Col 8 -> Ln 3, Col 9)
beatMultiplierValue: Number (Ln 3, Col 10 -> Ln 3, Col 11) 4
beatEffects: undefined
pipe: undefined
From here it is up to you to decide what to do with this information.
Parser​
The next lower level below the importer is the parser available via alphaTab.importer.alphaTex.AlphaTexParser.
The usage is fairly simple and the output will give you again access to the AST:
import * as alphaTab from '@coderline/alphatab';
const parser = alphaTab.importer.alphaTex.AlphaTexParser(`
\\title "Hello World"
C4 * 4
`);
const ast = parser.read();
const lexerDiagnostics = parser.lexerDiagnostics;
const parserDiagnostics = parser.parserDiagnostics;