-
Notifications
You must be signed in to change notification settings - Fork 0
Tokens
Tokens are the foundation of every compiler. They represent patterns in the input text, such as numbers, identifiers, or operators. In CDTk, a TokenSet is used to define all tokens for your language, serving as the first step in the compilation pipeline.
A token consists of:
- A name: A unique identifier for the token type.
- A regex pattern: Determines which text the token matches.
For example:
-
"Identifier"matchesmyVariable. -
"Number"matches42or3.14. -
"Plus"matches+.
CDTk processes the input text by converting it into a sequence of tokens. For instance:
var tokens = new TokenSet
{
new Token("Identifier", @"[A-Za-z_][A-Za-z0-9_]*"),
new Token("Number", @"\d+"),
new Token("Plus", @"\+"),
new Token("Equals", @"=")
};
// Input: x = 42
// Output Tokens: Identifier("x"), Equals("="), Number("42")In CDTk, all tokens are grouped into a TokenSet, which is used to tokenize input text. Here’s how to define one:
var tokens = new TokenSet
{
new Token("Identifier", @"[A-Za-z_][A-Za-z0-9_]*"), // Matches variable names
new Token("Number", @"\d+"), // Matches integers
new Token("Plus", @"\+"), // Matches +
new Token("Equals", @"="), // Matches =
new Token("Whitespace", @"\s+").Ignore() // Skips whitespace
};- Name: Tokens must have a unique name for identification.
- Regex Pattern: Defines the text pattern to match.
-
Order Matters: Tokens are matched in the order they are defined in the
TokenSet.
Here are some common token definitions for different programming constructs:
Identifiers and keywords often share similar patterns but must be carefully prioritized:
new Token("Keyword", @"if|while|return"), // Matches keywords
new Token("Identifier", @"[A-Za-z_][A-Za-z0-9_]*") // Matches variable names and labelsnew Token("Integer", @"\d+"); // Whole numbers
new Token("Float", @"\d+\.\d+([eE][+-]?\d+)?"); // Floating-point numbers with optional scientific notation
new Token("Hexadecimal", @"0[xX][0-9A-Fa-f]+");new Token("String", @"""([^""\\]|\\.)*"""); // Double-quoted strings
new Token("Char", @"'([^'\\]|\\.)'"); // Single-quoted charactersnew Token("Equals", @"=="); // Equality operator
new Token("Assign", @"="); // Assignment operator
new Token("Plus", @"\+"); // Plus operator
new Token("LParen", @"\("); // Left parenthesis
new Token("Semicolon", @";"); // Semicolonnew Token("Whitespace", @"\s+").Ignore(); // Ignore spaces
new Token("LineComment", @"//.*").Ignore(); // Skip // comments
new Token("BlockComment", @"/\*.*?\*/").Ignore(); // Skip /* ... */ commentsSometimes, you want the compiler to recognize tokens like whitespace or comments but exclude them from the output token stream. CDTk makes this easy with the .Ignore() method:
var tokens = new TokenSet
{
new Token("Whitespace", @"\s+").Ignore(),
new Token("LineComment", @"//.*").Ignore(),
new Token("BlockComment", @"/\*.*?\*/").Ignore()
};Ignored tokens:
- Simplify parsing by removing unnecessary matches.
- Keep the focus on meaningful tokens in the token stream.
Token order matters in a TokenSet, especially when regex patterns overlap. Define more specific tokens before generic ones to avoid unexpected matches.
// Incorrect Order
new Token("Identifier", @"\w+"); // Matches everything, including "if"
new Token("Keyword", @"if"); // Will never match because "Identifier" captures it
// Correct Order
new Token("Keyword", @"if");
new Token("Identifier", @"\w+"); // Matches non-keywordsAlways test overlapping token patterns and prioritize the most specific matches first.
Support identifiers and symbols across multiple languages or alphabets:
new Token("UnicodeIdentifier", @"[\p{L}_][\p{L}\p{N}_]*"); // Letters and numbersDefine tokens that adapt to context:
new Token("TemplateStart", @"<(?=\w)"); // Match '<' only before word characters
new Token("LessThan", @"<"); // Match standalone '<'Regex patterns are greedy by default and match the largest possible substring. Use non-greedy qualifiers ? when necessary:
new Token("String", @""".*?"""); // Matches until the closest closing quoteEnsure specific patterns (e.g., keywords) appear above general ones (e.g., identifiers).
Test all token patterns in edge cases to identify conflicts or unexpected matches.
-
Use Descriptive Names: Name tokens after their purpose, not abbreviations.
new Token("LeftParenthesis", @"\("); // Clear new Token("LP", @"\("); // Ambiguous
-
Test Regular Expressions: Validate patterns using tools like Regex101 or Regexr.
-
Avoid Overlap Conflicts: Ensure that your tokens prioritize specific over general patterns.
-
Handle Edge Cases: Address scenarios like Unicode, large numbers, or scientific notation.
Ready to move on? Here’s what to explore next: