Final project of the compiler class in Chung Ang Uni in Spring 2026
USAGE: syntax_analyzer [OPTIONS] <token_file>
POSITIONAL ARGUMENTS: <token_file> Source file to tokenize and parse.
OPTIONS:
-r <file>Path to the CFG grammar file.--use-regexEnable regex-based tokenizer. If use alone, it uses the built-in regexes--regex-path <file>Path to regex token definitions. It only matters if the regex-based tokenizer is enabled-h,--helpShow the usage message.
DESCRIPTION:
- loads a CFG grammar
- tokenizes the input source
- generates SLR parsing tables
- parses the token stream
- builds a parse tree
GRAMMAR FORMAT:
CODE -> VDECL CODE
CODE -> ''
REGEX TOKEN FORMAT:
TOKEN_NAME:regex
EXAMPLES:
syntax_analyzer -r grammar.txt source.code
syntax_analyzer -r grammar.txt \
--use-regex \
--regex-path lexer.regex \
source.code
AUTHOR: Made with shift/reduce conflicts and emotional damage.
- Rust 1.75+
- Cargo
Check your installation:
rustc --version
cargo --versionTo compile the project in release mode:
cargo build --releaseThe optimized executable will be generated in:
target/release/syntax_analyzerExample:
./target/release/syntax_analyzer -hOur non-ambiguous CFG can be found here. We made 2 changes:
EXPR:
Original grammar:
EXPR -> EXPR addsub EXPR | EXPR multidiv EXPR
This grammar is ambiguous because an expression such asnum addsub num multidiv numadmits multiple parse trees.
To enforce standard arithmetic precedence, the grammar was rewritten using HIGHOP, LOWOP and OPERAND nonterminals. Multiplication and division are reduced before addition and subtraction, eliminating the ambiguity.COND:
Original grammar:
COND -> COND comp COND
This grammar is ambiguous because an expression such asboolstr comp boolstr comp boolstradmits multiple parse trees.
We chose left associativity:(boolstr comp boolstr) comp boolstr
This was achieved by introducing RCOND and using left recursion:
COND -> COND comp RCONDandCOND -> RCOND
The rule parser reads a CFG grammar description and converts it into an internal representation usable by the SLR parser generator.
Features:
- Support for epsilon productions (
'') - Automatic terminal/non-terminal discovery
- Production validation
- Human-friendly syntax diagnostics with line and column information
- Colored error messages
- Embedded default grammar support
Example:
CODE -> VDECL CODE
CODE -> ''
VDECL -> vtype id semiThose rules must be in the format <Token> -> <Token list>
with space before and after ->
Two tokenization modes are available:
Reads a whitespace-separated token stream and attaches metadata to every token:
- line number
- column number
- original lexeme
Example:
vtype id lparen rparenExample:
Features:
- Custom regex rule files
- Built-in default lexer
- Longest-prefix matching
- Token metadata generation
- Precise lexer diagnostics with source highlighting
Example rule:
id:[a-zA-Z_][a-zA-Z0-9_]*
num:[0-9]+Those rules must be in the format :<Token>:<Regex> with no space in between
This allow to parse and transform file like :
int hello() {
return 42;
}Into a token list like :
vtype id lparen rparen lbrace return num semi rbraceThe canonical LR(0) collection is represented as a DFA.
Each state contains a set of LR(0) items and transitions
correspond to GOTO operations on grammar symbols.
Build Steps:
- Compute FIRST and FOLLOW sets.
- Build canonical LR(0) item collection.
- Generate SHIFT actions from DFA transitions.
- Generate GOTO entries from DFA transitions.
- Generate REDUCE actions using FOLLOW sets.
- Generate ACCEPT action for the augmented
Any shift/reduce or reduce/reduce conflict causes construction to fail.
The built SLR Table with our unambiguous CFG can be seen here
A parser is created with the SLRTable previously created, and the productions (aka rules).
The stack is set to empty.
Parsing steps:
- Initialize the stack with state 0.
- Repeatedly consult the ACTION table.
- Execute Shift, Reduce or Accept.
During SHIFT operations, leaf nodes are created for terminal symbols.
During REDUCE operations, a new nonterminal node is created and the reduced symbols become its children.
After ACCEPT, the remaining node becomes the root of the parse tree. - After each reduction, consult the GOTO table.
- Continue until the input is accepted.
On success, the root of the parse tree is returned.
On failure, a syntax error describing the unexpected token is returned.
Every tests can be found in the folder tests.
go to tests/parsing_base_cfg. You can execute the script ./test_parsing.sh [binary_path] (you need chmod 755).
go to tests/cfg_errors. You can execute the script ./test_cfg.sh [binary_path] (you need chmod 755).
go to tests/other_cfg. You can try an ambiguous cfg, and the base cfg from js-machine website. Inseide js_machine folder:
../../../syntax_analyzer -r js_machine_cfg.cfg test_inputand
../../../syntax_analyzer -r js_machine_cfg.cfg test_input_regex.txt --use-regex --regex-path lexer.regexgo to tests/regex. You can try regex:
../../syntax_analyzer test_fdecl_while.c --use-regex
go to tests/regex_error. The files in this folder should raise an error.
Generative AI tools were used for:
- Understanding the js-machine website.
- Understanding the construction of FIRST and FOLLOW sets.
- Understanding canonical LR(0) item collections.
- Understanding SLR parsing tables and parser actions.
- Reformulating and improving technical documentation.
- Understanding of Rust libraries
- Correction of rust lexical mistakes
- Helping write tests scripts