diff --git a/.rdoc_options b/.rdoc_options new file mode 100644 index 00000000..81831200 --- /dev/null +++ b/.rdoc_options @@ -0,0 +1,2 @@ +page_dir: doc +warn_missing_rdoc_ref: true diff --git a/Rakefile b/Rakefile index 05a7e013..a69c7594 100644 --- a/Rakefile +++ b/Rakefile @@ -7,13 +7,8 @@ require 'rdoc/task' spec = Gem::Specification.load("racc.gemspec") RDoc::Task.new(:docs) do |rd| - rd.main = "README.rdoc" - rd.rdoc_files.include(spec.files.find_all { |file_name| - file_name =~ /^(bin|lib|ext)/ || file_name !~ /\// - }) - - title = "#{spec.name}-#{spec.version} Documentation" - rd.options << "-t #{title}" + rd.title = "#{spec.name}-#{spec.version} Documentation" + rd.main = "index.md" rd.rdoc_dir = "_site" end diff --git a/doc/advanced-topics.md b/doc/advanced-topics.md new file mode 100644 index 00000000..2229765e --- /dev/null +++ b/doc/advanced-topics.md @@ -0,0 +1,867 @@ +# Advanced Topics + +This document covers advanced Racc features and techniques for experienced users. + +## Table of Contents + +- [Operator Precedence](#operator-precedence) +- [Error Recovery](#error-recovery) +- [Embedded Actions](#embedded-actions) +- [Token Symbol Conversion](#token-symbol-conversion) +- [Performance Optimization](#performance-optimization) +- [Grammar Design Patterns](#grammar-design-patterns) +- [Integration with Lexers](#integration-with-lexers) +- [Runtime Options](#runtime-options) + +## Operator Precedence + +Operator precedence is a powerful mechanism for resolving shift/reduce conflicts in expression grammars. + +### The Problem: Expression Ambiguity + +Consider this grammar: + +```ruby +rule + exp: exp '+' exp + | exp '*' exp + | NUMBER +end +``` + +For input `2 + 3 * 4`, this grammar is ambiguous: +- Could parse as `(2 + 3) * 4` = 20 +- Could parse as `2 + (3 * 4)` = 14 + +Racc reports this as a shift/reduce conflict. + +### The Solution: Precedence Declarations + +```ruby +class Calculator + prechigh + left '*' '/' + left '+' '-' + preclow +rule + exp: exp '+' exp + | exp '*' exp + | exp '/' exp + | exp '-' exp + | NUMBER +end +``` + +This declares: +- Multiplication and division have higher precedence +- Addition and subtraction have lower precedence +- Both levels are left-associative + +### Precedence Rules + +#### Declaration Syntax + +```ruby +prechigh + nonassoc '++' '--' # Highest precedence + left '*' '/' '%' + left '+' '-' + right '=' # Lowest precedence +preclow +``` + +Or reversed: + +```ruby +preclow + right '=' + left '+' '-' + left '*' '/' '%' + nonassoc '++' '--' +prechigh +``` + +#### Associativity Types + +Left Associative (`left`): + +```ruby +a - b - c => (a - b) - c +``` + +Most arithmetic operators are left-associative. + +Right Associative (`right`): + +```ruby +a = b = c => a = (b = c) +``` + +Assignment operators are typically right-associative. + +Non-Associative (`nonassoc`): + +```ruby +a < b < c => ERROR +``` + +Comparison operators are often non-associative. + +### How Precedence Works + +When a shift/reduce conflict occurs: + +1. Racc determines the rule's precedence (from the rightmost terminal) +2. Racc compares this with the lookahead token's precedence +3. Higher precedence wins +4. If equal, associativity decides: + - `left`: reduce + - `right`: shift + - `nonassoc`: error + +### Rule Precedence Override + +Sometimes you need to override the default precedence for a specific rule: + +```ruby +prechigh + nonassoc UMINUS # Unary minus + left '*' '/' + left '+' '-' +preclow + +rule + exp: exp '+' exp + | exp '-' exp + | exp '*' exp + | exp '/' exp + | '-' exp = UMINUS # Use UMINUS precedence, not '-' + | NUMBER +end +``` + +The `= UMINUS` syntax (equivalent to yacc's `%prec`) assigns UMINUS precedence to the unary minus rule, making it higher than multiplication. + +### Complete Example + +```ruby +class Calculator + prechigh + nonassoc UMINUS + right '' # Exponentiation + left '*' '/' '%' + left '+' '-' + nonassoc '<' '>' '<=' '>=' + nonassoc '==' '!=' + right '=' + preclow + +rule + exp: exp '=' exp + | exp '+' exp + | exp '-' exp + | exp '*' exp + | exp '/' exp + | exp '%' exp + | exp '' exp + | exp '==' exp + | exp '!=' exp + | exp '<' exp + | exp '>' exp + | exp '<=' exp + | exp '>=' exp + | '-' exp = UMINUS + | '(' exp ')' + | NUMBER +end +``` + +## Error Recovery + +Racc supports automatic error recovery using the special `error` token, similar to yacc. + +### Basic Error Recovery + +```ruby +rule + statements: statement + | statements statement + | statements error statement + { + puts "Error recovered" + yyerrok # Exit error recovery mode + } +end +``` + +### How Error Recovery Works + +1. Parser detects a syntax error +2. Calls `on_error` method +3. If `on_error` returns normally, enters error recovery mode +4. Pops states from stack until a state with an `error` transition is found +5. Discards tokens until parsing can continue +6. Resumes normal parsing after successfully reducing an `error` rule + +### Error Recovery Strategies + +#### Strategy 1: Statement-Level Recovery + +Recover at statement boundaries: + +```ruby +rule + program: statements + + statements: statement + | statements statement + | statements error ';' + { + # Synchronize at semicolon + yyerrok + } + + statement: IDENT '=' exp ';' + | IF exp THEN statements END + | WHILE exp DO statements END +end +``` + +#### Strategy 2: Expression-Level Recovery + +Recover within expressions: + +```ruby +rule + expression: term + | expression '+' term + | expression error term + { + puts "Recovered from expression error" + yyerrok + } +end +``` + +#### Strategy 3: Block-Level Recovery + +Recover at block boundaries: + +```ruby +rule + block: '{' statements '}' + | '{' error '}' + { + puts "Skipped invalid block" + yyerrok + } +end +``` + +### Error Recovery Methods + +#### `yyerror` - Enter Error Recovery + +Call from within an action to manually enter error recovery: + +```ruby +statement: IDENT '=' expression + { + if reserved_word?(val[0]) + puts "Cannot assign to reserved word" + yyerror + end + result = [:assign, val[0], val[2]] + } +``` + +#### `yyerrok` - Exit Error Recovery + +Call to exit error recovery mode and resume normal parsing: + +```ruby +error_recovery: error ';' + { + yyerrok + } +``` + +#### `on_error` - Custom Error Handling + +Override for custom error messages: + +```ruby +def on_error(token_id, value, value_stack) + token_name = token_to_str(token_id) + @errors << "Syntax error at #{token_name}: #{value}" + # Return normally to enter error recovery mode +end +``` + +### Error Reporting Example + +```ruby +class MyParser + def initialize + @errors = [] + end + + def parse(input) + @errors.clear + result = do_parse + if @errors.empty? + result + else + puts "Parse completed with errors:" + @errors.each { |e| puts " #{e}" } + nil + end + end + + def on_error(token_id, value, value_stack) + line = @lexer.line_number + @errors << "Line #{line}: unexpected #{token_to_str(token_id)}" + end +end +``` + +### Best Practices + +1. Place error rules strategically at synchronization points (statement ends, block boundaries) +2. Always call `yyerrok` when you've recovered +3. Collect errors rather than aborting on first error +4. Provide helpful messages in `on_error` +5. Test error paths as thoroughly as success paths + +## Embedded Actions + +Embedded actions allow you to execute code at any point during a rule match, not just at the end. + +### Basic Syntax + +```ruby +target: A B { puts "Seen A and B" } C D { result = "complete" } +``` + +### Embedded Action Values + +Embedded actions produce values accessible via `val`: + +```ruby +target: A { result = 1 } B { p val[1] } # Prints 1 +# ^----- val[0] ^------ val[1] val[2] +``` + +Note: `val[1]` is the embedded action's value, not B's value! + +### Use Cases + +#### 1. Setting Context + +```ruby +function_def: TYPE IDENT { @return_type = val[0] } '(' params ')' body + { + result = [:function, val[1], val[3], val[5], @return_type] + } +``` + +#### 2. Opening Scopes + +```ruby +block: '{' { enter_scope } statements '}' { exit_scope } +``` + +#### 3. Mid-Rule Validation + +```ruby +assignment: IDENT '=' { check_identifier(val[0]) } expression +``` + +#### 4. Semantic Predicates + +```ruby +qualified_name: IDENT { check_imported(val[0]) } '.' IDENT +``` + +### Implementation Details + +Embedded actions are equivalent to empty rule non-terminals: + +```ruby +# This: +target: A { result = 1 } B + +# Is equivalent to: +target: A @1 B +@1: /* empty */ { result = 1 } +``` + +### Limitations + +Embedded actions add states to the parser, which can: +- Increase parser size +- Create additional conflicts +- Affect performance + +Use them judiciously for clarity, but prefer end-of-rule actions when possible. + +## Token Symbol Conversion + +By default, Racc uses symbols for unquoted tokens and strings for quoted tokens. You can customize this. + +### Default Behavior + +```ruby +# Grammar: +rule + statement: IDENT '=' NUMBER +end + +# Lexer must produce: +[:IDENT, "x"] # Unquoted -> symbol +['=', '='] # Quoted -> string +[:NUMBER, 42] # Unquoted -> symbol +``` + +### Custom Token Symbols + +Use the `convert` block to change token representations: + +```ruby +convert + PLUS 'PlusClass' + MINUS 'MinusClass' + IF 'IfKeyword' +end +``` + +Now the lexer should produce: + +```ruby +[PlusClass, '+'] +[MinusClass, '-'] +[IfKeyword, 'if'] +``` + +### String Tokens + +To use strings as token symbols, quote carefully: + +```ruby +convert + PLUS '"plus"' # Token symbol is "plus" + MINUS '"minus\n"' # Token symbol is "minus\n" + IDENT "\"id_#{val}\"" # Token symbol is "id_#{val}" +end +``` + +### Why Use Custom Tokens? + +1. Integrating existing lexers that use different token representations +2. Type-safe tokens using classes instead of symbols +3. Rich token objects with metadata + +### Example: Token Classes + +```ruby +# Grammar: +convert + NUMBER 'NumberToken' + IDENT 'IdentToken' +end + +# Lexer: +class NumberToken + attr_reader :value + def initialize(value) + @value = value + end +end + +class IdentToken + attr_reader :name + def initialize(name) + @name = name + end +end + +def tokenize(input) + tokens = [] + # ... + tokens << [NumberToken, NumberToken.new(42)] + tokens << [IdentToken, IdentToken.new("x")] + tokens +end +``` + +### Restrictions + +Cannot use `false` or `nil` as token symbols - they cause parse errors. + +## Performance Optimization + +### Optimization Strategies + +#### 1. Use the C Extension + +Ensure `cparse.so` is available: + +```ruby +require 'racc/parser' # Automatically loads C extension if available +``` + +Speed improvement: 2-5x faster than pure Ruby. + +#### 2. Avoid `-E` Embedded Mode in Production + +```bash +# Development/distribution: +racc -E grammar.y # Slower, standalone + +# Production (if runtime available): +racc grammar.y # Faster, uses C extension +``` + +#### 3. Optimize Actions + +```ruby +# Slow - creates unnecessary arrays: +expression: term '+' term + { + temp = [] + temp << val[0] + temp << val[2] + result = temp[0] + temp[1] + } + +# Fast - direct computation: +expression: term '+' term + { + result = val[0] + val[2] + } +``` + +#### 4. Use `options omit_action_call` + +```ruby +options omit_action_call + +rule + # Empty actions don't generate method calls + statement: expression +end +``` + +#### 5. Optimize the Lexer + +The lexer is often the bottleneck: + +```ruby +# Slow - repeated string matching: +def tokenize(input) + tokens = [] + while !input.empty? + if input =~ /\A\d+/ + tokens << [:NUMBER, $&.to_i] + input = $' + elsif input =~ /\A[a-z]+/ + # ... + end + end + tokens +end + +# Fast - use StringScanner: +require 'strscan' + +def tokenize(input) + ss = StringScanner.new(input) + tokens = [] + until ss.eos? + case + when ss.scan(/\d+/) + tokens << [:NUMBER, ss.matched.to_i] + when ss.scan(/[a-z]+/) + tokens << [:IDENT, ss.matched] + when ss.scan(/\s+/) + # skip + end + end + tokens +end +``` + +#### 6. Minimize Parser Size + +- Fewer rules = smaller state machine +- Simpler grammar = faster parsing +- Avoid excessive embedded actions + +### Profiling + +Profile to find bottlenecks: + +```ruby +require 'ruby-prof' + +RubyProf.start +parser.parse(large_input) +result = RubyProf.stop + +printer = RubyProf::GraphPrinter.new(result) +printer.print(STDOUT, {}) +``` + +Usually shows lexer is the bottleneck, not the parser. + +## Grammar Design Patterns + +### Pattern 1: Lists + +Comma-separated list: + +```ruby +list: element + | list ',' element +``` + +List with optional trailing comma: + +```ruby +list: elements + | elements ',' + +elements: element + | elements ',' element +``` + +Zero-or-more items: + +```ruby +list: /* empty */ + | list element +``` + +### Pattern 2: Optional Elements + +```ruby +optional_else: /* empty */ + | ELSE statements +``` + +Or with better naming: + +```ruby +else_clause: /* empty */ { result = nil } + | ELSE statements { result = val[1] } +``` + +### Pattern 3: Repetition + +One or more: + +```ruby +statements: statement + | statements statement +``` + +Zero or more: + +```ruby +statements: /* empty */ + | statements statement +``` + +### Pattern 4: Grouping with Parentheses + +```ruby +primary: NUMBER + | IDENT + | '(' expression ')' +``` + +### Pattern 5: Block Structures + +```ruby +block: BEGIN statements END + | BEGIN END + +statements: statement + | statements statement +``` + +### Pattern 6: Operator Chains + +```ruby +# Comparison chains: a < b < c +comparisons: expression relop expression + | comparisons relop expression + +relop: '<' | '>' | '<=' | '>=' +``` + +## Integration with Lexers + +### Racc + Rexical + +Rexical is a lexer generator for Ruby that works well with Racc. + +Rexical specification (calc.rex): + +```ruby +class Calculator +macro + BLANK /\s+/ + DIGIT /\d+/ + +rule + {BLANK} # ignore + {DIGIT} { [:NUMBER, text.to_i] } + /\+/ { [:PLUS, text] } + /\*/ { [:MULT, text] } +end +``` + +Racc grammar (calc.y): + +```ruby +class Calculator +rule + exp: exp '+' exp { result = val[0] + val[2] } + | exp '*' exp { result = val[0] * val[2] } + | NUMBER +end + +---- header + require_relative 'calc.rex.rb' + +---- inner + def parse(input) + scan_str(input) + end +``` + +Generate both: + +```bash +rex calc.rex -o calc.rex.rb +racc calc.y -o calc.y.rb +``` + +### Racc + StringScanner + +For custom lexers without Rexical: + +```ruby +require 'strscan' + +class MyParser + def parse(input) + @ss = StringScanner.new(input) + do_parse + end + + def next_token + return [false, '$'] if @ss.eos? + + case + when @ss.scan(/\s+/) + next_token # Skip whitespace, get next token + when @ss.scan(/\d+/) + [:NUMBER, @ss.matched.to_i] + when @ss.scan(/[+\-*\/]/) + [@ss.matched, @ss.matched] + when @ss.scan(/\w+/) + [:IDENT, @ss.matched] + else + raise "Unexpected character: #{@ss.getch}" + end + end +end +``` + +## Runtime Options + +### Parser Generation Options + +Set in grammar file: + +```ruby +options omit_action_call result_var +``` + +Available options: + +- `omit_action_call` / `no_omit_action_call`: Optimize empty actions +- `result_var` / `no_result_var`: Use `result` variable in actions + +### Runtime Debugging + +Enable at runtime: + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +parser.instance_variable_set(:@racc_debug_out, STDERR) +``` + +### Custom Parser State + +Track custom state in instance variables: + +```ruby +class MyParser + def initialize + @symbol_table = {} + @scope_depth = 0 + end + + # Use @symbol_table and @scope_depth in actions +end +``` + +## Advanced Error Handling + +### Multiple Error Collection + +```ruby +class MyParser + def initialize + @errors = [] + end + + def parse(input) + @errors.clear + result = do_parse + raise ParseError, @errors.join("\n") unless @errors.empty? + result + end + + def on_error(token_id, value, value_stack) + @errors << "Error at #{token_to_str(token_id)}: #{value}" + # Don't raise - let error recovery continue + end +end +``` + +### Context-Aware Errors + +```ruby +def on_error(token_id, value, value_stack) + context = value_stack.last(3).map(&:inspect).join(" ") + raise ParseError, "Expected #{expected_tokens.join(' or ')} " \ + "after #{context}, got #{token_to_str(token_id)}" +end +``` + +## Summary + +Advanced Racc features enable: + +1. Precedence - Clean, unambiguous expression grammars +2. Error Recovery - Robust parsers that report multiple errors +3. Embedded Actions - Fine-grained semantic control +4. Token Conversion - Integration with existing lexers +5. Optimization - Fast parsing with proper techniques + +Master these techniques to build production-quality parsers for complex languages. diff --git a/doc/command-reference.md b/doc/command-reference.md new file mode 100644 index 00000000..1a12a000 --- /dev/null +++ b/doc/command-reference.md @@ -0,0 +1,508 @@ +# Racc Command Reference + +Complete reference for the `racc` command-line tool. + +## Synopsis + +```bash +racc [options] grammarfile +``` + +## Basic Usage + +Generate a parser from a grammar file: + +```bash +racc mygrammar.y +``` + +This creates `mygrammar.tab.rb` by default. + +## Options + +### Output Control + +#### `-o FILENAME`, `--output-file=FILENAME` + +Specify the output filename: + +```bash +racc calc.y -o calculator.rb +``` + +Default: `.tab.rb` + +#### `-O FILENAME`, `--log-file=FILENAME` + +Specify the log file name (used with `-v`): + +```bash +racc calc.y -v -O calc.log +``` + +Default: `.output` + +### Code Generation Options + +#### `-E`, `--embedded` + +Generate a standalone parser with embedded runtime: + +```bash +racc mygrammar.y -E -o standalone_parser.rb +``` + +Use case: When distributing parsers to environments without the Racc runtime installed. + +Note: The generated file will be larger, and the C extension (`cparse.so`) cannot be used, resulting in slower parsing. + +#### `-F`, `--frozen` + +Add `frozen_string_literal: true` magic comment: + +```bash +racc mygrammar.y -F +``` + +Generates: + +```ruby +# frozen_string_literal: true +class MyParser < Racc::Parser + # ... +end +``` + +#### `-e RUBYPATH`, `--executable=RUBYPATH` + +Generate an executable parser with shebang: + +```bash +racc calc.y -e /usr/bin/ruby +``` + +To use the current Ruby interpreter: + +```bash +racc calc.y -e ruby +``` + +Generates: + +```ruby +#!/usr/bin/ruby +# Parser code... +``` + +Don't forget to make it executable: + +```bash +chmod +x calc.tab.rb +``` + +### Line Number Conversion + +#### `-l`, `--no-line-convert` + +Disable line number conversion: + +```bash +racc mygrammar.y -l +``` + +Background: By default, Racc converts line numbers in error messages to refer to the grammar file rather than the generated parser. This option disables that conversion. + +Use case: When debugging issues in Ruby 1.4.3 or earlier, which had bugs with constant references. + +#### `-c`, `--line-convert-all` + +Convert line numbers for `header` and `footer` blocks in addition to actions and `inner`: + +```bash +racc mygrammar.y -c +``` + +Warning: Don't use this if your header and footer blocks are concatenated from multiple sources. + +### Action Generation + +#### `-a`, `--no-omit-actions` + +Generate method definitions and calls for all actions, even empty ones: + +```bash +racc mygrammar.y -a +``` + +Default: Racc omits method calls for empty actions to improve performance. + +### Debugging and Information + +#### `-v`, `--verbose` + +Output detailed parsing information to a log file: + +```bash +racc calc.y -v +``` + +Generates `calc.output` with: +- State transition table +- Shift/reduce conflicts +- Reduce/reduce conflicts +- Detailed state information + +Example output: + +``` +State 0 + + 0: $start -> . target $end + + NUMBER shift, goto 1 + target goto 2 + +State 1 + + 3: target -> NUMBER . + + $end reduce using rule 3 (target -> NUMBER) +``` + +#### `-g`, `--debug` + +Generate parser with debugging code: + +```bash +racc calc.y -g -o calc.rb +``` + +To use debug output: + +```ruby +parser = Calculator.new +parser.instance_variable_set(:@yydebug, true) +result = parser.parse(input) +``` + +Note: Using `-g` alone doesn't enable debugging; you must also set `@yydebug = true` at runtime. + +Debug output shows: +- Token shifts +- Rule reductions +- State transitions +- Value stack contents + +#### `-S`, `--output-status` + +Output progress information during generation: + +```bash +racc mygrammar.y -S +``` + +Shows real-time progress: + +``` +Parsing grammar file... +Calculating states... +Resolving conflicts... +Generating parser... +Done. +``` + +### Validation + +#### `-C`, `--check-only` + +Check grammar syntax without generating a parser: + +```bash +racc mygrammar.y -C +``` + +Use case: Quick validation during grammar development. + +### Information + +#### `--version` + +Display Racc version: + +```bash +racc --version +``` + +#### `--copyright` + +Display copyright information: + +```bash +racc --copyright +``` + +#### `--help` + +Display brief option summary: + +```bash +racc --help +``` + +## Common Workflows + +### Development Workflow + +During grammar development, use verbose mode to understand conflicts: + +```bash +racc -v calc.y +cat calc.output # Review state transitions +``` + +Enable debugging for runtime troubleshooting: + +```bash +racc -g calc.y -o calc.rb +``` + +### Distribution Workflow + +For distribution to environments with Racc runtime (Ruby 1.8+): + +```bash +racc mygrammar.y -o myparser.rb +``` + +For standalone distribution (no runtime required): + +```bash +racc -E mygrammar.y -o myparser.rb +``` + +### Production Workflow + +Generate optimized, frozen parser: + +```bash +racc -F mygrammar.y -o myparser.rb +``` + +## Examples + +### Example 1: Basic Parser Generation + +```bash +racc calculator.y +# Generates: calculator.tab.rb +``` + +### Example 2: Custom Output Name + +```bash +racc calculator.y -o calc_parser.rb +``` + +### Example 3: Debugging Shift/Reduce Conflicts + +```bash +racc calculator.y -v +less calculator.output # Review conflicts +``` + +### Example 4: Standalone Parser + +```bash +racc calculator.y -E -o standalone_calc.rb +# No racc runtime required to use standalone_calc.rb +``` + +### Example 5: Executable Parser + +```bash +racc calculator.y -e ruby -o calc +chmod +x calc +./calc input.txt +``` + +### Example 6: Full Debug Setup + +```bash +racc -g -v calculator.y -o calc.rb +``` + +Then in your code: + +```ruby +require './calc' + +parser = Calculator.new +parser.instance_variable_set(:@yydebug, true) + +# This will print detailed debug output +result = parser.parse("2 + 3 * 4") +``` + +### Example 7: Grammar Validation Only + +```bash +racc -C mygrammar.y +# Checks syntax, doesn't generate parser +``` + +### Example 8: Frozen String Literal Parser + +```bash +racc -F calculator.y -o calc.rb +``` + +## Understanding Conflicts + +When Racc reports conflicts: + +``` +5 shift/reduce conflicts +``` + +Use `-v` to generate a detailed report: + +```bash +racc -v grammar.y +``` + +Then examine `grammar.output`: + +``` +State 42 + + 15: exp -> exp . '+' exp + 15: exp -> exp . '-' exp + 16: exp -> exp '+' exp . + + '*' shift, goto 8 + '+' shift, goto 9 + '-' shift, goto 10 + $end reduce using rule 16 (exp -> exp '+' exp) + + '*': shift/reduce conflict (shift 8, reduce 16) +``` + +This helps identify where to add precedence declarations. + +## Error Messages + +### Common Errors + +"syntax error" +- Grammar file has syntax errors +- Check the line number indicated +- Common causes: unmatched braces, missing colons + +"token X is declared but not used" +- Token declared but never appears in grammar +- Warning only, not fatal + +"token X is used but not declared" +- Token used in grammar but not declared with `token` directive +- Warning only, not fatal + +"X shift/reduce conflicts" +- Grammar has ambiguities +- Use `-v` to investigate +- Consider adding precedence declarations or using `expect` + +"X reduce/reduce conflicts" +- Serious grammar ambiguity +- Grammar may need restructuring +- Cannot be suppressed with `expect` + +## Performance Considerations + +### Generation Performance + +- Larger grammars take longer to process +- Use `-S` to monitor progress on large grammars + +### Runtime Performance + +- Fastest: Normal mode (uses `cparse.so` if available) +- Slower: `-E` embedded mode (pure Ruby, no C extension) +- Slowest: Debug mode with `@yydebug = true` + +## Environment Variables + +Racc does not use any special environment variables. Ruby's standard environment variables apply: + +- `RUBYLIB` - Additional load paths +- `RUBYOPT` - Default Ruby options + +## Exit Status + +- 0: Success +- 1: Error (syntax error, file not found, etc.) + +## Files + +### Input File + +- Grammar file: `.y` extension by convention (not required) + +### Output Files + +- Parser file: `.tab.rb` by default, or specified with `-o` +- Log file: `.output` with `-v`, or specified with `-O` + +## Notes + +### Ruby Version Compatibility + +- Ruby 1.6: Supported, use `-E` for standalone parsers +- Ruby 1.7: Supported +- Ruby 1.8+: Recommended, includes runtime by default +- Ruby 1.9+: Fully supported +- Ruby 2.x+: Fully supported +- Ruby 3.x+: Fully supported with `-F` for frozen strings + +### File Naming Conventions + +While not required, these conventions are recommended: + +- Grammar files: `.y` extension (e.g., `calc.y`) +- Generated parsers: `_parser.rb` suffix (e.g., `calc_parser.rb`) +- Log files: `.output` extension (e.g., `calc.output`) + +### Integration with Build Systems + +#### Makefile + +```makefile +%.rb: %.y + racc -o $@ $< + +calc_parser.rb: calc.y + racc -o calc_parser.rb calc.y +``` + +#### Rake + +```ruby +desc "Generate parser" +task :parser do + sh "racc calc.y -o calc_parser.rb" +end +``` + +#### Bundler/Gem + +In your gemspec: + +```ruby +Gem::Specification.new do |s| + s.extensions = ['ext/extconf.rb'] + # Add parser generation to build process +end +``` + +--- + +For detailed grammar syntax, see [Grammar Reference](grammar-reference.md). diff --git a/doc/debugging.md b/doc/debugging.md new file mode 100644 index 00000000..d08dab8c --- /dev/null +++ b/doc/debugging.md @@ -0,0 +1,608 @@ +# Debugging Racc Parsers + +This guide covers common problems you'll encounter when working with Racc and how to solve them. + +## Table of Contents + +- [Grammar File Parse Errors](#grammar-file-parse-errors) +- [Shift/Reduce and Reduce/Reduce Conflicts](#shiftreduce-and-reducereduce-conflicts) +- [Parser Generated Successfully but Doesn't Work](#parser-generated-successfully-but-doesnt-work) +- [Token-Related Issues](#token-related-issues) +- [Debugging Tools and Techniques](#debugging-tools-and-techniques) +- [Common Mistakes](#common-mistakes) + +## Grammar File Parse Errors + +### Symptom + +Racc reports a syntax error when processing your grammar file: + +``` +parse error on value "end" (tEND) +``` + +### Diagnosis + +1. Check the line number in the error message +2. Look for common syntax errors around that line: + - Unmatched braces `{ }` in actions + - Missing colons `:` after rule names + - Unclosed strings or comments + - Missing `end` keyword + +### Solution + +#### Unmatched Braces + +```ruby +# Wrong: +expression: term '+' term + { + result = val[0] + val[2] + # Missing closing brace! + +# Correct: +expression: term '+' term + { + result = val[0] + val[2] + } +``` + +#### Missing Colon + +```ruby +# Wrong: +expression term '+' term + +# Correct: +expression: term '+' term +``` + +#### Block-End Confusion + +If the error occurs at a closing brace or `end`, you've likely added an extra opening delimiter somewhere earlier. Count your braces! + +### Prevention + +- Use an editor with bracket matching +- Properly indent your grammar file +- Comment complex actions to track scope + +## Shift/Reduce and Reduce/Reduce Conflicts + +### Symptom + +Racc reports conflicts after processing your grammar: + +``` +5 shift/reduce conflicts +1 reduce/reduce conflict +``` + +### Understanding Conflicts + +Shift/Reduce Conflict: +The parser can't decide whether to: +- Shift: Read another token +- Reduce: Apply a rule + +Reduce/Reduce Conflict: +The parser can't decide which of two rules to apply. + +### Diagnosis + +Generate a detailed state machine report: + +```bash +racc -v mygrammar.y +``` + +This creates `mygrammar.output` with detailed conflict information. + +Example output: + +``` +State 23 + + 15: exp -> exp . '+' exp + 15: exp -> exp '+' exp . + 16: exp -> exp . '*' exp + + '*' shift, goto 24 + '+' shift, goto 25 + $end reduce using rule 15 (exp -> exp '+' exp) + + '*': shift/reduce conflict (shift 24, reduce 15) +``` + +This shows that when the parser sees `*` after `exp '+' exp`, it doesn't know whether to shift (read the `*`) or reduce (complete the addition). + +### Solution + +#### 1. Add Operator Precedence + +Most arithmetic conflicts are resolved by declaring precedence: + +```ruby +class Calculator + prechigh + nonassoc UMINUS + left '*' '/' + left '+' '-' + preclow +rule + exp: exp '+' exp + | exp '-' exp + | exp '*' exp + | exp '/' exp + | '-' exp = UMINUS + | NUMBER +end +``` + +This tells Racc: +- Multiplication has higher precedence than addition +- Addition is left-associative + +#### 2. Restructure the Grammar + +Sometimes conflicts indicate a structural problem: + +```ruby +# Ambiguous - causes conflicts: +statement: IF expr THEN statement + | IF expr THEN statement ELSE statement + +# Better - no conflict: +statement: IF expr THEN statement else_part + +else_part: /* empty */ + | ELSE statement +``` + +#### 3. Use `expect` for Acceptable Conflicts + +If you understand the conflict and it's harmless (like the classic dangling-else problem): + +```ruby +class MyParser + expect 1 # Expect exactly 1 shift/reduce conflict +rule + # ... +end +``` + +This suppresses warnings as long as there's exactly 1 conflict. + +Important: +- This doesn't fix conflicts, it just suppresses warnings +- Only use when you understand why the conflict exists +- Doesn't work for reduce/reduce conflicts + +### Learning More + +Conflicts are a complex topic. For in-depth understanding, consult: + +- "[Compilers: Principles, Techniques, and Tools](https://suif.stanford.edu/dragonbook/)" (Dragon Book) +- "[Ruby を 256 倍使うための本 無道編](https://i.loveruby.net/ja/support/ruby256mudo.html)" by Minero Aoki (Japanese) +- The `.output` file generated by `racc -v` + +## Parser Generated Successfully but Doesn't Work + +### Symptom + +- Parser generates without errors +- But produces wrong results or raises exceptions at runtime + +### Debugging with Debug Mode + +Generate a parser with debugging code: + +```bash +racc -g mygrammar.y -o myparser.rb +``` + +Enable debugging at runtime: + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +result = parser.parse(input) +``` + +Debug output shows: + +``` +read :NUMBER (42) +shift :NUMBER +goto exp +reduce exp -> NUMBER +read :PLUS +shift :PLUS +read :NUMBER (3) +shift :NUMBER +goto exp +reduce exp -> exp '+' exp +accept 14 +``` + +This shows exactly what the parser is doing: +- Which tokens are read +- When shifts and reductions occur +- What rules are being applied +- The current state + +### Redirect Debug Output + +To save debug output to a file: + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +parser.instance_variable_set(:@racc_debug_out, File.open('debug.log', 'w')) +result = parser.parse(input) +``` + +### Common Issues Revealed by Debugging + +#### Wrong Token Values + +``` +read :NUMBER ("42") # Should be integer! +``` + +Fix your tokenizer: + +```ruby +# Wrong: +tokens << [:NUMBER, text] + +# Correct: +tokens << [:NUMBER, text.to_i] +``` + +#### Wrong Rule Reductions + +If the parser reduces the wrong rule, your grammar may be ambiguous or your token sequence incorrect. + +#### Value Calculation Errors + +Add debug output to your actions: + +```ruby +expression: term '+' term + { + puts "Adding: #{val[0].inspect} + #{val[2].inspect}" + result = val[0] + val[2] + } +``` + +## Token-Related Issues + +### Issue 1: Parser Hangs or Doesn't Recognize End of Input + +Symptom: Parser never completes or raises unexpected errors. + +Cause: Forgot to send end-of-input marker. + +Solution: + +Always return `[false, anything]` or `nil` when tokens are exhausted: + +```ruby +def next_token + return [false, '$'] if @position >= @tokens.length + token = @tokens[@position] + @position += 1 + token +end +``` + +Important notes: +- As of Racc 0.10.2+, `next_token` is guaranteed not to be called after returning `[false, anything]` +- Can return `nil` instead of `[false, anything]` (modern Racc versions) + +### Issue 2: Wrong Token Format + +Symptom: Parser raises exceptions or produces wrong results. + +Cause: Tokens not in `[symbol, value]` format. + +Wrong: + +```ruby +# Returns a hash: +def next_token + {type: :NUMBER, value: 42} +end + +# Returns just the value: +def next_token + 42 +end +``` + +Correct: + +```ruby +def next_token + [:NUMBER, 42] +end +``` + +### Issue 3: Token Symbol Mismatch + +Symptom: Parser doesn't recognize tokens. + +Cause: Token symbols from lexer don't match grammar. + +Check your grammar: + +```ruby +rule + expression: NUMBER # Expects :NUMBER symbol +``` + +Check your lexer: + +```ruby +# Must match: +tokens << [:NUMBER, 42] # Correct + +# Won't match: +tokens << [:DIGIT, 42] # Wrong symbol! +tokens << ["NUMBER", 42] # String, not symbol! +``` + +## Debugging Tools and Techniques + +### Technique 1: State Machine Visualization + +Generate detailed state information: + +```bash +racc -v grammar.y +less grammar.output +``` + +Study the state machine to understand parser behavior. + +### Technique 2: Incremental Development + +Build your grammar incrementally: + +1. Start with a minimal grammar +2. Test thoroughly +3. Add one feature at a time +4. Test after each addition + +Example progression: + +```ruby +# Step 1: Just numbers +rule + expression: NUMBER +end + +# Step 2: Add addition +rule + expression: expression '+' expression + | NUMBER +end + +# Step 3: Add other operators +rule + expression: expression '+' expression + | expression '*' expression + | NUMBER +end +``` + +### Technique 3: Unit Test Your Grammar + +Write tests for each grammar rule: + +```ruby +describe MyParser do + it "parses numbers" do + parser = MyParser.new + expect(parser.parse("42")).to eq(42) + end + + it "parses addition" do + parser = MyParser.new + expect(parser.parse("2+3")).to eq(5) + end + + it "respects precedence" do + parser = MyParser.new + expect(parser.parse("2+3*4")).to eq(14) + end +end +``` + +### Technique 4: Print Intermediate Values + +Add debug output to actions: + +```ruby +expression: term '+' term + { + puts "Left: #{val[0].inspect}, Right: #{val[2].inspect}" + result = val[0] + val[2] + puts "Result: #{result.inspect}" + } +``` + +### Technique 5: Validate Token Stream + +Before parsing, inspect your token stream: + +```ruby +tokens = lexer.tokenize(input) +pp tokens # Pretty-print tokens +parser.parse_tokens(tokens) +``` + +### Technique 6: Check Grammar Syntax Only + +Quick validation without generating parser: + +```bash +racc -C grammar.y +``` + +## Common Mistakes + +### Mistake 1: Modifying `_values` + +Never do this: + +```ruby +expression: term + { + _values.pop # DANGER! + result = val[0] + } +``` + +The `_values` stack is internal to Racc. Modifying it corrupts the parser. + +### Mistake 2: Off-by-One in `val` Array + +Remember: `val` is zero-indexed! + +```ruby +expression: term '+' term + { + # Wrong (yacc-style, 1-indexed): + result = $1 + $3 + + # Correct (Ruby-style, 0-indexed): + result = val[0] + val[2] + } +``` + +### Mistake 3: Not Handling Whitespace + +Symptom: Parser fails on input with spaces. + +Cause: Lexer doesn't handle whitespace. + +Solution: + +```ruby +# In lexer: +when @ss.scan(/\s+/) + # Ignore whitespace - no token +``` + +### Mistake 4: Using Reserved Names + +Avoid these prefixes: +- Constants: `Racc_*` +- Methods: `racc_*`, `_racc_*` + +### Mistake 5: Forgetting End-of-Input + +Already covered in [Token-Related Issues](#token-related-issues), but worth repeating: Always send `[false, '$']` when tokens run out! + +### Mistake 6: Wrong Action Return Value + +When using `options no_result_var`: + +```ruby +options no_result_var + +rule + expression: term '+' term + { + x = val[0] + val[2] + # Wrong: No explicit return, x is discarded + } + + # Correct - last expression is returned: + expression: term '+' term + { + val[0] + val[2] + } +``` + +## Debugging Checklist + +When your parser misbehaves, work through this checklist: + +- [ ] Grammar file syntax is valid (`racc -C`) +- [ ] Understand all conflicts (`racc -v`, read `.output` file) +- [ ] Tokens are in `[symbol, value]` format +- [ ] End-of-input marker `[false, '$']` is sent +- [ ] Token symbols match between lexer and grammar +- [ ] Token values have correct types (e.g., integers not strings) +- [ ] Whitespace is handled by lexer +- [ ] Actions use `val[0]`, `val[1]`, not `$1`, `$2` +- [ ] Not modifying `_values` stack +- [ ] Debug mode enabled (`racc -g`, `@yydebug = true`) +- [ ] Debug output reviewed and understood + +## Getting Help + +If you're still stuck: + +1. Simplify: Reduce your grammar to the smallest case that fails +2. Isolate: Test lexer and parser separately +3. Compare: Look at working examples in the Racc repository +4. Read: The `.output` file often contains the answer +5. Share: Ask for help with a minimal reproducible example + +## Advanced Debugging + +### Custom Error Messages + +Provide better error messages: + +```ruby +def on_error(token_id, value, value_stack) + token_name = token_to_str(token_id) + line = @lexer.line_number + column = @lexer.column_number + + raise ParseError, "Syntax error at line #{line}, column #{column}: " \ + "unexpected #{token_name} (#{value.inspect})" +end +``` + +### Error Recovery + +Implement error recovery with the `error` token: + +```ruby +rule + statements: statement + | statements statement + | statements error statement + { + puts "Recovered from error" + yyerrok + } +end +``` + +### Performance Debugging + +If your parser is slow: + +1. Ensure you're not using `-g` debug mode in production +2. Use the C extension (not `-E` embedded mode) +3. Profile your lexer - it's often the bottleneck +4. Consider using `strscan` for tokenization + +## Summary + +Most Racc debugging falls into these categories: + +1. Grammar syntax errors - Check for typos, matched braces +2. Conflicts - Use `-v` to understand, add precedence or restructure +3. Token issues - Verify format `[symbol, value]` and end-of-input +4. Runtime errors - Enable debug mode with `-g` and `@yydebug = true` + +The debug output from `racc -g` is your best friend. It shows exactly what the parser is doing and will reveal most problems quickly. diff --git a/doc/en/Overview-of-racc.md b/doc/en/Overview-of-racc.md deleted file mode 100644 index 8e9d7089..00000000 --- a/doc/en/Overview-of-racc.md +++ /dev/null @@ -1,209 +0,0 @@ -This post follows from [Overview of rexical part 2](https://github.com/tenderlove/rexical/wiki/Overview-of-rexical-part-2). Taken from Jeff Nyman's [blog post](http://webcache.googleusercontent.com/search?q=cache:http://testerstories.com/2012/06/a-tester-learns-rex-and-racc-part-3/&num=1&strip=1&vwsrc=0). - -# Combining rexical and racc - -Assuming you’ve been following along, you’ve broken your input into a stream of tokens. Now you need some way to recognize higher-level patterns. This is where Racc comes in: Racc lets you describe what you want to do with those tokens. That’s what I’ll be covering here: how the parser works with the lexer. - -You already know that you define a lexer specification with a rex file. This generates a lexer class. In a very similar fashion, you define a grammar with a racc file. This generates a parser class. If you want your own parser, you have to write grammar file. In the last post we started creating a new simple language to handle calculations of numbers. The language will consist of `ADD (+)`, `SUBTRACT (-)`, `MULTIPLY (*)`, and `DIVISION (/)`. Those operators are given as the symbol identifier as well as the actual symbol that maps to that identifier. The language also supports the notion of `DIGIT`. A `DIGIT` token must come before and after an operator. So there’s our language. Now we want to try to evaluate the code of this new language. - -You have to create a grammar file. The first part of this file, as with the Rex specification file, is a class block. In your project directory, create a file called `test_language.y`. This will be our grammar file. As with the start of our Rex file, we’ll use our class: -``` ruby -class TestLanguage - -end -``` -Then you will have a grammar block. In Rex, the grammar block is a way to specify the patterns for specific symbols. In Racc, the grammar block describes grammar that can be understood by the parser. As with Rex specification files, we’ll eventually add a rule clause to signify our grammar block. In fact, this clause will mark the start of what are called the “production rules.” To fill in these rules, you have to understand the Racc grammar. So let’s just look at the basics before we try to fill in our nascent grammar file. - -A grammar rule has the form: - -``` -some : THING ; -``` -The symbols produced by a lexer are called terminals or tokens. The things assembled from them are called non-terminals. So in this schematic, ‘some’ represents a non-terminal while ‘THING’ is a terminal. So ‘THING’ must have been produced by the lexer and ‘some’ can only be created by assembling it from terminals. Here’s an example: - -``` -value : DIGIT ; -``` -This means a construct called a value (a non-terminal) can be built up from a DIGIT (a terminal). Here’s another example: - -``` -expression : value '+' value ; -``` -Here an expression can be built up from two values (which we know are DIGITS) and a plus sign. You can specify alternatives as well: - -``` -expression : value '+' value | value '-' value ; -``` -Some people like to space all this out for better readability: - -``` -expression - : - value '+' value - | value '-' value -; -``` -There’s a lot of terminology coming your way here so, for now, just understand that terminals are all the basic symbols or tokens of which a given language is composed. Non-terminals are the syntactic variables in the grammar which represent a set of strings that the grammar is composed of. The general style used is to have all terminals in uppercase and all non-terminals in lowercase. - -As with Rex grammar, you can write actions inside curly braces. These actions are, once again, Ruby code. Since Racc needs tokens upon which to operate, Racc expects to call a method that yields tokens, where each token is a two element array with the first element being the type of token (matching the token declaration) and the second element the actual value of the token, which will usually be the actual text. This is a lot easier to show than talk about, although in reality it’s not so different from what you’ve seen with Rex files. So here’s an example: -``` -expression : value '+' value { puts "Performing addition." } -``` - -But I could also do this: -``` -expression : value '+' value { result = val[0] + val[2] } -``` - -If you ever read documentation on yacc, you’ll see mention of variables like `$$` and `$1` and so on. The equivalent of the above in yacc would be: -``` -expression : value '+' value { $$ = $1 + $3; } -``` - -Here yacc’s `$$` is racc’s `result`, while yacc’s `$0`, `$1` would be the array `val`. Since Ruby is zero based, that’s why `$1` is `val[0]`. Okay, so let’s put this stuff to use. Change your grammar file so it looks like this: -``` ruby -class TestLanguage - rule - expression : DIGIT - | DIGIT ADD DIGIT { return val[0] + val[2] } -end -``` -What I’m basically saying here is that something I want to call an “expression” can be recognized if it’s a `DIGIT` or a `DIGIT ADD DIGIT`. From the lexer file, we know that `DIGIT` is going to correspond to an array like this: `[:DIGIT, text.to_i]`. We know that `ADD` is going to correspond to an array like this `[:ADD, text]`. Further, we know that `DIGIT` means `\d+` (i.e., any group of numbers) and `ADD` means `\+` (i.e., the literal plus sign). I’m also saying that when `DIGIT` is encountered nothing should happen. There is no action provided for that. But if `DIGIT ADD DIGIT` is encountered, then I want a return value provided, which is the addition of the first `DIGIT` with the second `DIGIT`. - -In order to compile this file, you can use the following command: -``` -racc test_language.y -o parser.rb -``` - -To make that process more streamlined, let’s add a task to our Rakefile: -``` ruby -desc "Generate Parser" -task :parser do - `racc test_language.y -o parser.rb` -end -``` - -Now you can just type `rake parser` to generate the parser just as you can type `rake lexer` to generate the lexer. In fact, you might always want to force both things to happen, in which case you can add this task: -``` ruby -desc "Generate Lexer and Parser" -task :generate => [:lexer, :parser] -``` - -So how do we test this? Let’s create a file in our spec directory called `language_parser_spec.rb` and add the following to it: -``` ruby -require './parser.rb' - -class TestLanguageParser - describe 'Testing the Parser' do - before do - @evaluator = TestLanguage.new - end - - it 'tests for a digit' do - @result = @evaluator.parse("2") - @result.should == 2 - end - end -end -``` - -Structurally you can see that this is similar to the test format we established in our `language_lexer_spec.rb` file. Running this test will tell you that there is no parse method. That’s true. Just as we added a tokenize method to our rex specification, we have to add a parse method to our grammar file. Here’s how you can do that: -``` ruby -class TestLanguage -rule - expression : DIGIT - | DIGIT ADD DIGIT { return val[0] + val[2] } -end - ----- inner - def parse(input) - scan_str(input) - end -``` - -Once again, we use an inner section just as we did with our rex specification. Notice a few major differences here, though. First, you must preface the section with four hyphens (-). Also, notice that the inner section is **OUTSIDE** of the class block. This is different from the rex specification where the inner section was inside the class block. Make sure to regenerate your files and then run the test again. Now you will be told that the `scan_str` method is undefined. This method is defined in the lexer and so you have to add another section to your grammar file: -``` ruby -class TestLanguage -rule - expression : DIGIT - | DIGIT ADD DIGIT { return val[0] + val[2] } -end - ----- header - require_relative 'lexer' - ----- inner - def parse(input) - scan_str(input) - end -``` - -If you regenerate and run your test, the parser test should pass. However, we have not made it do a calculation yet. So add the following test: -``` ruby -... - it 'tests for addition' do - @result = @evaluator.parse("2+2") - @result.should == 4 - end -... -``` - -If that’s working for you, congratulations! You now have the start of a working language. Granted, it’s just a calculator example but let’s not leave it just doing addition. You can fill out the rest of the grammar file as such: - -``` ruby -class TestLanguage -rule - expression : DIGIT - | DIGIT ADD DIGIT { return val[0] + val[2] } - | DIGIT SUBTRACT DIGIT { return val[0] - val[2] } - | DIGIT MULTIPLY DIGIT { return val[0] * val[2] } - | DIGIT DIVIDE DIGIT { return val[0] / val[2] } -end - ----- header - require_relative 'lexer' - ----- inner - def parse(input) - scan_str(input) - end -``` - -I’ll leave it as an exercise to fill in the existing tests to prove that the grammar for all calculation types is being read and processed correctly. - -This simple example still leaves a lot open. For example, while `2+2` will work `2 + 2` (with spaces) will not. This is because your lexer did not specify any white space and thus the parser does not know how to recognize it. Also, what about more complicated expressions? Can I do `2+2+2`? Try it and you’ll find that it does not. And even if I could support that, how would I handle operator precedence? For example would `2+3*2` give me `8`, as it should, or would it instead give me `10`? - -Keep in mind that in order to solve problems like this, you have to account for how your grammar can be constructed. For example, to allow for spaces, you just have to indicate that (1) blank spaces are allowed and (2) that no action occurs when a blank space is matched. In fact, you’ve already seen this in prior examples here. Just add the following to your rex specification file (`test_language.rex`): -``` ruby -class TestLanguage -macro - BLANK [\ \t]+ - DIGIT \d+ - ADD \+ - SUBTRACT \- - MULTIPLY \* - DIVIDE \/ - -rule - {BLANK} # no action - {DIGIT} { [:DIGIT, text.to_i] } - {ADD} { [:ADD, text] } - {SUBTRACT} { [:SUBTRACT, text] } - {MULTIPLY} { [:MULTIPLY, text] } - {DIVIDE} { [:DIVIDE, text] } - -inner - def tokenize(code) - scan_setup(code) - tokens = [] - while token = next_token - tokens << token - end - tokens - end -end -``` - -That simple bit of extra information allows your parser to handle `2 + 2` as well as it does `2+2`. The other problems are a little more involved. And with that, perhaps you can see now that even with a simple calculator, it takes some work to get it to do what you want in terms of a language. And yet all programming languages that you work with are ultimately constructed of grammar specified just in the way we have been doing it here. - -I’ve learned a lot by trying to figure out how these tools work. One of my goals is to see if I can build an alternative to Gherkin, the language that is used by Cucumber to create what are called feature files. I think this will be a challenging exercise. Beyond that, I can see some good exercises here for constructing test grammars for test description languages. Playing around with concepts like these could, perhaps, lead to some interesting notions about how we communicate about tests. diff --git a/doc/en/grammar.en.rdoc b/doc/en/grammar.en.rdoc deleted file mode 100644 index 704a5ea6..00000000 --- a/doc/en/grammar.en.rdoc +++ /dev/null @@ -1,218 +0,0 @@ -= Racc Grammar File Reference - -== Global Structure - -== Class Block and User Code Block - -There are two top-level blocks: the 'class' block, and the 'user code' -block. The 'user code' block MUST be after the 'class' block. - -== Comment - -Comments can be added about everywhere. Two comment styles are -supported: Ruby style (`# ...`) and C style (`/* ... */`). - -== Class Block - -The class block is formed like this: - - class CLASS_NAME - [precedence table] - [token declarations] - [expected number of S/R conflict] - [options] - [semantic value conversion] - [start rule] - rule - GRAMMARS - -CLASS_NAME is a name of parser class. -This is the name of generating parser class. - -If CLASS_NAME includes '::', Racc outputs module clause. -For example, writing "class M::C" causes creating the code below: - - module M - class C - : - : - end - end - -== Grammar Block - -The grammar block describes the grammar -to be understood by parser. Syntax is: - - (token): (token) (token) (token).... (action) - - (token): (token) (token) (token).... (action) - | (token) (token) (token).... (action) - | (token) (token) (token).... (action) - -(action) is an action which is executed when its (token)s are found. -(action) is a ruby code block, which is surrounded by braces: - - { print val[0] - puts val[1] } - -Note that you cannot use '%' string, here document, '%r' regexp in action. - -Actions can be omitted. -When it is omitted, '' (empty string) is used. - -A return value of action is a value of left side value ($$). -It is value of result, or returned value by "return" statement. - -Here is an example of whole grammar block. - - rule - goal: definition rules source { result = val } - - definition: /* none */ { result = [] } - | definition startdesig { result[0] = val[1] } - | definition - precrule # this line continue from upper line - { - result[1] = val[1] - } - - startdesig: START TOKEN - -You can use following special local variables in action. - - * result ($$) - -The value of left-hand side (lhs). A default value is val[0]. - - * val ($1,$2,$3...) - -An array of value of right-hand side (rhs). - - * _values (...$-2,$-1,$0) - -A stack of values. -DO NOT MODIFY this stack unless you know what you are doing. - -== Operator Precedence - -This function is equal to '%prec' in yacc. -To designate this block: - - prechigh - nonassoc '++' - left '*' '/' - left '+' '-' - right '=' - preclow - -`right' is yacc's %right, `left' is yacc's %left. - -`=' + (symbol) means yacc's %prec: - - prechigh - nonassoc UMINUS - left '*' '/' - left '+' '-' - preclow - - rule - exp: exp '*' exp - | exp '-' exp - | '-' exp =UMINUS # equals to "%prec UMINUS" - : - : - -== expect - -Racc supports Bison's "expect" directive to declare the expected -number of shift/reduce conflicts. - - class MyParser - expect 3 - rule - : - : - -Then warnings are issued only when the effective number of conflicts differs. - -== Declaring Tokens - -Declaring tokens avoids many bugs. - -Racc outputs warnings for declared tokens that do not exist, or existing tokens not declared. -The syntax is: - - token TOKEN_NAME AND_IS_THIS - ALSO_THIS_IS AGAIN_AND_AGAIN THIS_IS_LAST - -== Options - -You can write options for racc command in your racc file. - - options OPTION OPTION ... - -Options are: - - * omit_action_call - -omit empty action call or not. - - * result_var - -use/does not use local variable "result" - -You can use 'no_' prefix to invert its meanings. - -== Converting Token Symbol - -Token symbols are, as default, - - * naked token strings in racc file (TOK, XFILE, this_is_token, ...) - --> symbol (:TOK, :XFILE, :this_is_token, ...) - * quoted strings (':', '.', '(', ...) - --> same string (':', '.', '(', ...) - -You can change this default using a "convert" block. -Here is an example: - - convert - PLUS 'PlusClass' # We use PlusClass for symbol of `PLUS' - MIN 'MinusClass' # We use MinusClass for symbol of `MIN' - end - -We can use almost all ruby value can be used by token symbol, -except 'false' and 'nil'. These are causes unexpected parse error. - -If you want to use String as token symbol, special care is required. -For example: - - convert - class '"cls"' # in code, "cls" - PLUS '"plus\n"' # in code, "plus\n" - MIN "\"minus#{val}\"" # in code, \"minus#{val}\" - end - -== Start Rule - -'%start' in yacc. This changes the start symbol. - - start real_target - -== User Code Block - -A "User Code Block" is a piece of Ruby source code copied in the output. -There are three user code blocks, "header" "inner" and "footer". - -User code blocks are introduced by four '-' at the beginning of a line, -followed by a single-word name: - - ---- header - ruby statement - ruby statement - ruby statement - - ---- inner - ruby statement - : - : diff --git a/doc/en/grammar2.en.rdoc b/doc/en/grammar2.en.rdoc deleted file mode 100644 index 28eea595..00000000 --- a/doc/en/grammar2.en.rdoc +++ /dev/null @@ -1,219 +0,0 @@ -= Racc Grammar File Reference - -== Global Structure - -== Class Block and User Code Block - -There are two blocks on the toplevel. One is the 'class' block, the other is the 'user code' -block. The 'user code' block MUST be placed after the 'class' block. - -== Comments - -You can insert comments about all places. Two styles of comments can be used, Ruby style '#.....' and C style '/\*......*\/'. - -== Class Block - -The class block is formed like this: - - class CLASS_NAME - [precedence table] - [token declarations] - [expected number of S/R conflicts] - [options] - [semantic value conversion] - [start rule] - rule - GRAMMARS - -CLASS_NAME is a name of the parser class. This is the name of the generating parser -class. - -If CLASS_NAME includes '::', Racc outputs the module clause. For example, writing -"class M::C" causes the code below to be created: - - module M - class C - : - : - end - end - -== Grammar Block - -The grammar block describes grammar which is able to be understood by the parser. -Syntax is: - - (token): (token) (token) (token).... (action) - - (token): (token) (token) (token).... (action) - | (token) (token) (token).... (action) - | (token) (token) (token).... (action) - -(action) is an action which is executed when its (token)s are found. -(action) is a ruby code block, which is surrounded by braces: - - { print val[0] - puts val[1] } - -Note that you cannot use '%' string, here document, '%r' regexp in action. - -Actions can be omitted. When it is omitted, '' (empty string) is used. - -A return value of action is a value of the left side value ($$). It is the value of the -result, or the returned value by `return` statement. - -Here is an example of the whole grammar block. - - rule - goal: definition rules source { result = val } - - definition: /* none */ { result = [] } - | definition startdesig { result[0] = val[1] } - | definition - precrule # this line continues from upper line - { - result[1] = val[1] - } - - startdesig: START TOKEN - -You can use the following special local variables in action: - -* result ($$) - -The value of the left-hand side (lhs). A default value is val[0]. - -* val ($1,$2,$3...) - -An array of value of the right-hand side (rhs). - -* _values (...$-2,$-1,$0) - -A stack of values. DO NOT MODIFY this stack unless you know what you are doing. - -== Operator Precedence - -This function is equal to '%prec' in yacc. -To designate this block: - - prechigh - nonassoc '++' - left '*' '/' - left '+' '-' - right '=' - preclow - -`right` is yacc's %right, `left` is yacc's %left. - -`=` + (symbol) means yacc's %prec: - - prechigh - nonassoc UMINUS - left '*' '/' - left '+' '-' - preclow - - rule - exp: exp '*' exp - | exp '-' exp - | '-' exp =UMINUS # equals to "%prec UMINUS" - : - : - -== expect - -Racc has bison's "expect" directive. - - # Example - - class MyParser - expect 3 - rule - : - : - -This directive declares "expected" number of shift/reduce conflicts. If -"expected" number is equal to real number of conflicts, Racc does not print -conflict warning message. - -== Declaring Tokens - -By declaring tokens, you can avoid many meaningless bugs. If declared token -does not exist or existing token is not declared, Racc output warnings. -Declaration syntax is: - - token TOKEN_NAME AND_IS_THIS - ALSO_THIS_IS AGAIN_AND_AGAIN THIS_IS_LAST - -== Options - -You can write options for Racc command in your Racc file. - - options OPTION OPTION ... - -Options are: - -* omit_action_call - -omits empty action call or not. - -* result_var - -uses local variable "result" or not. - -You can use 'no_' prefix to invert their meanings. - -== Converting Token Symbol - -Token symbols are, as default, - - * naked token string in Racc file (TOK, XFILE, this_is_token, ...) - --> symbol (:TOK, :XFILE, :this_is_token, ...) - * quoted string (':', '.', '(', ...) - --> same string (':', '.', '(', ...) - -You can change this default by "convert" block. -Here is an example: - - convert - PLUS 'PlusClass' # We use PlusClass for symbol of `PLUS' - MIN 'MinusClass' # We use MinusClass for symbol of `MIN' - end - -We can use almost all ruby value can be used by token symbol, -except 'false' and 'nil'. These cause unexpected parse error. - -If you want to use String as token symbol, special care is required. -For example: - - convert - class '"cls"' # in code, "cls" - PLUS '"plus\n"' # in code, "plus\n" - MIN "\"minus#{val}\"" # in code, \"minus#{val}\" - end - -== Start Rule - -'%start' in yacc. This changes start rule. - - start real_target - -== User Code Block - -"User Code Block" is a Ruby source code which is copied to output. There are -three user code blocks, "header" "inner" and "footer". - -Format of user code is like this: - - ---- header - ruby statement - ruby statement - ruby statement - - ---- inner - ruby statement - : - : - -If four '-' exist on the line head, Racc treats it as the beginning of the -user code block. The name of the user code block must be one word. diff --git a/doc/en/racc.en.rhtml b/doc/en/racc.en.rhtml deleted file mode 100644 index 0274afee..00000000 --- a/doc/en/racc.en.rhtml +++ /dev/null @@ -1,37 +0,0 @@ -% require 'makefile' -% version = Makefile.get_parameter('Makefile', 'version') -

Racc

- - - - - - - -
Version<%= version %>
TypeParser Generator
FormatRuby script + Ruby extension
Requirementruby (>=1.6)
LicenseLGPL
-

--- Download (.tar.gz) --- Old Versions --- Online Manual --- -

- -

-Racc (Ruby yACC) is an LALR(1) parser generator for Ruby. -Version 1.4.x is stable release. -

-

-Parsers generated by Racc requires "Racc Runtime Module". -Ruby 1.8.x comes with this runtime. -If you want to run your parsers with ruby 1.6.x, -use "racc -E" command. For details, see online manual. -

- -

Git

-

-You can use the latest version of Racc using git. -To clone out a working copy, type: -

-
-$ git clone https://github.com/ruby/racc.git
-
diff --git a/doc/getting-started.md b/doc/getting-started.md new file mode 100644 index 00000000..5c1d399f --- /dev/null +++ b/doc/getting-started.md @@ -0,0 +1,395 @@ +# Getting Started with Racc + +This guide will walk you through creating your first parser with Racc, from basic concepts to a working calculator. + +## Prerequisites + +- Basic understanding of Ruby +- Ruby 1.6 or later installed +- Racc installed (included with Ruby 1.8+, or install via `gem install racc`) + +## Understanding the Basics + +### What is a Parser? + +A parser analyzes input according to grammar rules and extracts structure and meaning. Racc generates parser classes from grammar specifications, similar to how yacc generates C parsers. + +### The Parsing Process + +Parsing typically happens in two stages: + +1. Lexical Analysis (Tokenization): Breaking input into tokens +2. Syntax Analysis (Parsing): Recognizing patterns in token sequences + +### Tokens: The Building Blocks + +A token is a pair: `[symbol, value]` + +- Symbol: The token type (e.g., `:NUMBER`, `:PLUS`, `:IDENT`) +- Value: The actual data (e.g., `42`, `"+"`, `"variable_name"`) + +Convention: +- Terminal symbols (tokens): UPPERCASE +- Non-terminal symbols (grammar constructs): lowercase + +## Your First Grammar: A Simple Calculator + +Let's build a calculator that handles basic arithmetic: addition, subtraction, multiplication, and division. + +### Step 1: Create the Lexer + +First, we need a lexer to convert input strings into tokens. Create `test_language.rex`: + +```ruby +class TestLanguage +macro + BLANK [\ \t]+ + NUMBER \d+ + ADD \+ + SUBTRACT \- + MULTIPLY \* + DIVIDE \/ + +rule + {BLANK} # no action (ignore whitespace) + {NUMBER} { [:NUMBER, text.to_i] } + {ADD} { [:ADD, text] } + {SUBTRACT} { [:SUBTRACT, text] } + {MULTIPLY} { [:MULTIPLY, text] } + {DIVIDE} { [:DIVIDE, text] } + +inner + def tokenize(code) + scan_setup(code) + tokens = [] + while token = next_token + tokens << token + end + tokens + end +end +``` + +Generate the lexer: + +```bash +rex test_language.rex -o lexer.rb +``` + +### Step 2: Create the Grammar File + +Create `test_language.y`: + +```ruby +class TestLanguage +rule + expression : NUMBER + | NUMBER ADD NUMBER { result = val[0] + val[2] } + | NUMBER SUBTRACT NUMBER { result = val[0] - val[2] } + | NUMBER MULTIPLY NUMBER { result = val[0] * val[2] } + | NUMBER DIVIDE NUMBER { result = val[0] / val[2] } +end + +---- header + require_relative 'lexer' + +---- inner + def parse(input) + scan_str(input) + end +``` + +### Step 3: Generate the Parser + +```bash +racc test_language.y -o parser.rb +``` + +### Step 4: Test Your Parser + +Create a test file `test_parser.rb`: + +```ruby +require './parser' + +parser = TestLanguage.new + +# Test basic operations +puts parser.parse("2 + 2") # => 4 +puts parser.parse("10 - 3") # => 7 +puts parser.parse("4 * 5") # => 20 +puts parser.parse("15 / 3") # => 5 +``` + +## Understanding the Grammar Syntax + +### Rule Structure + +```ruby +rule + target: SYMBOL another_symbol + | SYMBOL different_symbol + | SYMBOL +end +``` + +- target: Non-terminal symbol (left-hand side) +- |: Alternative productions (OR) +- SYMBOL: Terminal or non-terminal symbols +- { ... }: Actions (Ruby code) + +### Actions + +Actions are Ruby code blocks that execute when a rule matches: + +```ruby +expression: NUMBER '+' NUMBER { result = val[0] + val[2] } +``` + +Special Variables in Actions: + +- `result`: The return value (similar to `$$` in yacc). Default is `val[0]` +- `val`: Array of right-hand side values (similar to `$1, $2, $3...` in yacc) +- `_values`: The value stack (do not modify unless you know what you're doing) + +Example: + +```ruby +expression: term '+' term { result = val[0] + val[2] } +# val[0] val[1] val[2] +``` + +### Empty Productions + +Use empty productions for optional elements: + +```ruby +optional_else: ELSE statements + | # empty - else clause is optional +``` + +## Building a Better Calculator + +The simple calculator above can only handle single operations. Let's improve it to handle complex expressions with proper precedence. + +### Enhanced Grammar with Precedence + +Create `calculator.y`: + +```ruby +class Calculator + prechigh + nonassoc UMINUS + left '*' '/' + left '+' '-' + preclow + +rule + target: expression + + expression: expression '+' expression { result = val[0] + val[2] } + | expression '-' expression { result = val[0] - val[2] } + | expression '*' expression { result = val[0] * val[2] } + | expression '/' expression { result = val[0] / val[2] } + | '(' expression ')' { result = val[1] } + | '-' expression =UMINUS { result = -val[1] } + | NUMBER +end + +---- header + require_relative 'calc_lexer' + +---- inner + def parse(input) + scan_str(input) + end +``` + +This grammar now supports: +- Multiple operations in one expression +- Parentheses for grouping +- Unary minus +- Proper operator precedence + +## User Code Blocks + +Racc grammar files support three user code blocks: + +### header + +Code placed before the class definition (for require statements): + +```ruby +---- header + require 'strscan' + require_relative 'lexer' +``` + +### inner + +Code placed inside the class definition (for methods): + +```ruby +---- inner + def parse(input) + @tokens = tokenize(input) + do_parse + end + + def next_token + @tokens.shift + end +``` + +### footer + +Code placed after the class definition (for helper classes): + +```ruby +---- footer + class Token + attr_accessor :type, :value + end +``` + +## Implementing Token Feeding + +Racc supports two methods for feeding tokens to the parser: + +### Method 1: Using `do_parse` and `next_token` + +```ruby +---- inner + def parse(input) + @tokens = [ + [:NUMBER, 1], + [:ADD, '+'], + [:NUMBER, 2], + [false, '$'] # End of input marker + ] + do_parse + end + + def next_token + @tokens.shift + end +``` + +The `next_token` method must: +- Return `[symbol, value]` for each token +- Return `[false, anything]` or `nil` when input is exhausted + +### Method 2: Using `yyparse` with yield + +```ruby +def parse(input, scanner) + yyparse(scanner, :scan_tokens) +end + +# In scanner object: +def scan_tokens + until end_of_file + # Process and yield each token + yield [:NUMBER, 42] + yield [:ADD, '+'] + end + yield [false, '$'] # End marker +end +``` + +## Writing the Scanner + +While you can write scanners manually in Ruby, the `strscan` library (included with Ruby 1.8+) provides efficient scanning: + +```ruby +require 'strscan' + +class MyScanner + def initialize(input) + @ss = StringScanner.new(input) + end + + def tokenize + tokens = [] + until @ss.eos? + case + when @ss.scan(/\s+/) + # Skip whitespace + when text = @ss.scan(/\d+/) + tokens << [:NUMBER, text.to_i] + when text = @ss.scan(/[+\-*\/]/) + tokens << [text, text] + end + end + tokens << [false, '$'] + tokens + end +end +``` + +## Next Steps + +Now that you understand the basics: + +1. Read the [Grammar Reference](grammar-reference.md) for complete syntax details +2. Learn about [operator precedence](advanced-topics.md#operator-precedence) +3. Explore [error recovery](advanced-topics.md#error-recovery) mechanisms +4. Study the [Parser Class Reference](parser-class-reference.md) for available methods +5. Review [Debugging](debugging.md) techniques for troubleshooting + +## Common Pitfalls + +1. Forgetting the end-of-input marker: Always return `[false, anything]` when tokens are exhausted +2. Incorrect token format: Tokens must always be `[symbol, value]` pairs +3. Modifying the value stack: Don't modify `_values` unless you understand the internals +4. Off-by-one errors: Remember `val` is zero-indexed (`val[0]`, not `val[1]` for the first element) +5. Not handling whitespace: Lexers must explicitly handle or ignore whitespace + +## Example: Complete Working Parser + +Here's a complete, working example that ties everything together: + +```ruby +# file: simple_calc.y +class SimpleCalc +rule + target: exp + + exp: exp '+' exp { result = val[0] + val[2] } + | exp '-' exp { result = val[0] - val[2] } + | NUMBER +end + +---- inner + def parse(str) + @q = [] + until str.empty? + case str + when /\A\s+/ + # Skip whitespace + when /\A\d+/ + @q.push [:NUMBER, $&.to_i] + when /\A.{1}/ + @q.push [$&, $&] + end + str = $' + end + @q.push [false, '$'] + do_parse + end + + def next_token + @q.shift + end +``` + +Generate and use: + +```bash +racc simple_calc.y -o simple_calc.rb +ruby -r ./simple_calc -e "puts SimpleCalc.new.parse('1 + 2 + 3')" # => 6 +``` + +--- + +Continue to [Grammar Reference](grammar-reference.md) for detailed grammar syntax documentation. diff --git a/doc/grammar-reference.md b/doc/grammar-reference.md new file mode 100644 index 00000000..97b7fe9a --- /dev/null +++ b/doc/grammar-reference.md @@ -0,0 +1,668 @@ +# Racc Grammar File Reference + +This document provides a complete reference for Racc grammar file syntax. + +## Version Compatibility Notes + +Changes from previous versions: + +- v1.2.5: When concatenating user code, embedded code is now concatenated before external files +- v1.1.5: Meaning of reserved word `token` changed +- v0.14: Semicolons at the end of rules are now optional; `token`, `prechigh` are no longer reserved words +- v0.12: `prepare` renamed to `header`, `driver` renamed to `footer` (old names work until 2.0) +- v0.10: Removed `end` corresponding to `class` +- v0.9: Changed from period-based syntax to brace-based syntax `{ }` + +## File Structure + +A Racc grammar file consists of two top-level sections: + +1. Class Block: Grammar and parser class definition +2. User Code Block: Custom Ruby code to embed in output + +The user code block MUST come after the class block. + +``` +┌─────────────────────────────────┐ +│ Class Block │ +│ - class definition │ +│ - operator precedence │ +│ - token declarations │ +│ - options │ +│ - grammar rules │ +└─────────────────────────────────┘ +┌─────────────────────────────────┐ +│ User Code Block │ +│ - header │ +│ - inner │ +│ - footer │ +└─────────────────────────────────┘ +``` + +## Comments + +Two comment styles are supported: + +```ruby +# Ruby-style comment (to end of line) + +/* C-style comment + can span multiple lines */ +``` + +Comments can be placed almost anywhere in the file, with a few exceptions (e.g., inside strings). + +## Class Block + +### Basic Structure + +```ruby +class ClassName [< SuperClass] + [precedence table] + [token declarations] + [expect] + [options] + [semantic value conversion] + [start rule] +rule + grammar rules +end +``` + +### Class Name + +The class name becomes the name of the generated parser class: + +```ruby +class MyParser +``` + +For namespaced classes, use `::`: + +```ruby +class MyModule::MyParser +``` + +This generates: + +```ruby +module MyModule + class MyParser < Racc::Parser + # ... + end +end +``` + +### Superclass Specification + +You can optionally specify a superclass: + +```ruby +class MyParser < CustomParserBase +``` + +Warning: Specifying a superclass can significantly affect parser behavior. Only use this if you have a specific need. This feature is reserved for future extensions. + +## Grammar Rules + +### Basic Syntax + +Grammar rules define the structure your parser will recognize: + +```ruby +rule + target: symbol symbol symbol action + | symbol symbol action + | symbol action + + another: SYMBOL + | SYMBOL SYMBOL +end +``` + +Components: +- Left-hand side (target): Non-terminal symbol being defined +- Colon (`:`): Separates left-hand side from right-hand side +- Right-hand side: Sequence of symbols (terminals and non-terminals) +- Pipe (`|`): Alternative productions (OR) +- Action: Ruby code block in `{ }` braces + +### Example + +```ruby +rule + goal: definition rules source { result = val } + + definition: /* none */ { result = [] } + | definition startdesig { result[0] = val[1] } + | definition precrule { result[1] = val[1] } + + startdesig: START TOKEN +end +``` + +### Empty Productions + +Empty (epsilon) productions allow optional elements: + +```ruby +optional_then: THEN + | # empty production - THEN is optional +``` + +## Actions + +Actions are Ruby code blocks executed when a rule is reduced. + +### Syntax + +```ruby +expression: term '+' term { result = val[0] + val[2] } +``` + +### Special Variables + +Inside actions, you have access to these special variables: + +#### `result` (equivalent to yacc's `$$`) + +The value of the left-hand side. Default value is `val[0]`. + +```ruby +expression: NUMBER { result = val[0] * 2 } +``` + +#### `val` (equivalent to yacc's `$1, $2, $3, ...`) + +An array containing the values of right-hand side symbols (zero-indexed): + +```ruby +expression: term '+' term { result = val[0] + val[2] } +# val[0] val[1] val[2] +``` + +#### `_values` (equivalent to yacc's `..., $-2, $-1, $0`) + +The value stack used internally by Racc. DO NOT MODIFY unless you fully understand the parser internals. + +### Return Values + +The value of the left-hand side can be set in two ways: + +Default behavior (with `result` variable): + +```ruby +expression: term '+' term + { + result = val[0] + val[2] + } +``` + +With `no_result_var` option: + +```ruby +options no_result_var + +rule + expression: term '+' term { val[0] + val[2] } # last expression is the value +``` + +### Omitting Actions + +Actions can be omitted. The default action is `{ result = val[0] }`: + +```ruby +expression: term # equivalent to: term { result = val[0] } +``` + +### Embedded Actions + +Actions can be embedded within the right-hand side of a rule: + +```ruby +target: A B { puts 'seen A B' } C D { result = val[3] } +``` + +Embedded actions execute at that point in the parse and return a value accessible via `val`: + +```ruby +target: A { result = 1 } B { p val[1] } # prints 1 (not B's value!) +``` + +Semantically, embedded actions are equivalent to empty rule non-terminals: + +```ruby +# These are equivalent: +target: A { result = 1 } B +# Same as: +target: A nonterm B +nonterm: /* empty */ { result = 1 } +``` + +### Action Restrictions + +Some Ruby syntax is not supported in actions: + +- Here documents (`< 7 + puts calc.parse("(1 + 2) * 3") # => 9 + end +``` diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 00000000..295e1426 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,190 @@ +# Racc - Ruby LALR(1) Parser Generator + +## Overview + +Racc (Ruby yACC) is an LALR(1) parser generator for Ruby, similar to yacc for C. It generates parser classes from grammar files (.y files), allowing you to process structured text and implement domain-specific languages. + +### Key Features + +- LALR(1) parsing algorithm (same as yacc/bison) +- Generates pure Ruby parser classes +- Operator precedence support +- Error recovery mechanisms +- Embedded runtime (optional standalone parsers) +- Debug mode for troubleshooting + +## Quick Start + +### Installation + +Racc runtime is included with Ruby 1.8.0 and later. For standalone installation: + +```bash +gem install racc +``` + +### Basic Usage + +1. Create a grammar file (e.g., `calc.y`): + +```ruby +class Calculator +rule + expression: NUMBER + | expression '+' NUMBER { result = val[0] + val[2] } + | expression '-' NUMBER { result = val[0] - val[2] } +end + +---- inner + def parse(input) + @tokens = input + do_parse + end + + def next_token + @tokens.shift + end +``` + +2. Generate the parser: + +```bash +racc calc.y -o calc.rb +``` + +3. Use your parser: + +```ruby +require './calc' + +parser = Calculator.new +result = parser.parse([ + [:NUMBER, 5], + ['+', '+'], + [:NUMBER, 3] +]) +puts result # => 8 +``` + +## Documentation + +This documentation is organized into the following sections: + +- [Getting Started](getting-started.md) - Step-by-step tutorial for creating your first parser +- [Grammar Reference](grammar-reference.md) - Complete grammar file syntax reference +- [Command Reference](command-reference.md) - racc command-line options and usage +- [Parser Class Reference](parser-class-reference.md) - Racc::Parser class methods and APIs +- [Debugging](debugging.md) - Troubleshooting and debugging techniques +- [Advanced Topics](advanced-topics.md) - Operator precedence, error recovery, and advanced features + +## What is Racc? + +Racc is a tool for processing grammar. A string is just a sequence of characters and has no inherent meaning to computers. However, humans can find meaning in these character sequences. Racc helps computers partially understand this meaning through automation. + +### What Racc Does + +Racc automates the processing of the "structure" contained in strings. For example, consider Ruby's if statement: + +``` +if condition [then] + statements + ... +[elsif condition [then] + statements + ...] +[else + statements + ...] +end +``` + +In an if statement, the word "if" must come first, and elsif clauses must come before else clauses. These structural relationships are what Racc processes. + +### What Racc Cannot Do + +What Racc cannot process automatically is the "semantic meaning" - for example, understanding that a condition expression in an if statement is actually a condition. You must write code to handle the semantic meaning through "actions" in your grammar. + +## How Racc Works + +### The Two-Stage Process + +Parser processing typically involves two stages: + +1. Lexical Analysis (Scanning): Breaking the character stream into a token stream +2. Syntax Analysis (Parsing): Recognizing higher-level patterns in the token stream + +For example, this input: + +```ruby +if flag then # item found. + puts 'ok' +end +``` + +Becomes this token stream: + +``` +IF IDENT THEN IDENT STRING END +``` + +### Terminals and Non-terminals + +Grammar symbols are divided into two types: + +- Terminal symbols: Tokens produced by the lexer (IF, IDENT, NUMBER, etc.) +- Non-terminal symbols: Higher-level constructs built from terminals (expression, statement, etc.) + +The parser builds non-terminal symbols from terminals according to your grammar rules, ultimately reducing the entire input to a single start symbol if the input is valid. + +## Runtime Requirements + +Parsers generated by Racc require the Racc runtime module to operate: + +- Ruby 1.8.0+: Runtime is included, no action needed +- Ruby 1.6.x: Use `racc -E` to generate standalone parsers with embedded runtime + +The runtime includes: +- `racc/parser.rb` - Core parser functionality (required) +- `cparse.so` - Optional C extension for performance (not required, but recommended) + +## Distribution + +When distributing parsers created with Racc: + +### For Ruby 1.8.0+ +Simply distribute your generated .rb file. Users with Ruby 1.8+ already have the runtime. + +### For Ruby 1.6.x or Standalone Distribution +Use the `-E` option to embed the runtime: + +```bash +racc -E mygrammar.y -o myparser.rb +``` + +This creates a single file with no external dependencies (except Ruby itself). Note that the C extension cannot be used with this option, so parsing will be slower. + +## Repository + +GitHub: https://github.com/ruby/racc + +To clone the latest version: + +```bash +git clone https://github.com/ruby/racc.git +``` + +## Additional Resources + +- For writing scanners, consider using the `strscan` library (included with Ruby 1.8+) +- For a tutorial combining Racc with Rexical (a lexer generator), see the Getting Started guide + +## Getting Help + +If you're new to parser generators: +- Start with the [Getting Started](getting-started.md) guide +- Work through the examples in the repository +- Consult "Compilers: Principles, Techniques, and Tools" (the "Dragon Book") for theory + +If you're familiar with yacc: +- Jump to [Grammar Reference](grammar-reference.md) to see syntax differences +- Review [Parser Class Reference](parser-class-reference.md) for API differences diff --git a/doc/ja/command.ja.html b/doc/ja/command.ja.html deleted file mode 100644 index d28a209f..00000000 --- a/doc/ja/command.ja.html +++ /dev/null @@ -1,101 +0,0 @@ -

Raccコマンドリファレンス

-

-racc [-ofilename] [--output-file=filename] - [-erubypath] [--executable=rubypath] - [-v] [--verbose] - [-Ofilename] [--log-file=filename] - [-g] [--debug] - [-E] [--embedded] - [-F] [--frozen] - [-l] [--no-line-convert] - [-c] [--line-convert-all] - [-a] [--no-omit-actions] - [-C] [--check-only] - [-S] [--output-status] - [--version] [--copyright] [--help] grammarfile -

- -
-
filename
-
-Raccの文法ファイルを指定します。拡張子には特に制限はありません。 -
-
-ooutfile, --output-file=outfile
-
-作成するクラスをかきこむファイル名を指定します。デフォルトは.tab.rbです。 -
-
-Ofilename, --log-file=filename
-
--v オプションをつけた時に生成するログファイルの名前を -filename に変更します。 -デフォルトは filename.output です。 -
-
-erubypath, --executable=rubypath
-
-実行可能ファイルを生成します。rubypathは Ruby 本体のパスです。 -rubypathを単に 'ruby' にした時には Racc が動作している -Ruby のパスを使用します。 -
-
-v, --verbose
-
-ファイル "filename".output に詳細な解析情報を出力します。 -
-
-g, --debug
-
-出力するコードにデバッグ用コードを加えます。-g をつけて生成したパーサで -@yydebug を true にセットすると、デバッグ用のコードが出力されます。
--g をつけるだけでは何もおこりませんので注意してください。 -
-
-E, --embedded
-
-ランタイムルーチンをすべて含んだコードを生成します。 -つまり、このオプションをつけて生成したコードは Ruby さえあれば動きます。 -
-
-F, --frozen
-
-Add frozen_string_literals: true. -
-
-C, --check-only
-
-(文法ファイルの) 文法のチェックだけをして終了します。 -
-
-S, --output-status
-
-進行状況を逐一報告します。 -
-
-l, --no-line-convert
-
-

-Ruby では例外が発生した時のファイル名や行番号を表示してくれますが、 -Racc の生成したパーサは、デフォルトではこの場合のファイル名・行番号を -文法ファイルでのものに置きかえます。このフラグはその機能をオフにします。 -

-

-ruby 1.4.3 以前のバージョンではバグのために定数の参照に失敗する -場合があるので、定数参照に関してなにかおかしいことがおこったらこのフラグを -試してみてください。 -

-
-
-c, --line-convert-all
-
-アクションと inner に加え header footer の行番号も変換します。 -header と footer がつながっているような場合には使わないでください。 -
-a, --no-omit-actions
- -
-全てのアクションに対応するメソッド定義と呼び出しを行います。 -例えアクションが省略されていても空のメソッドを生成します。 -
-
--version
-
-Racc のバージョンを出力して終了します。 -
-
--copyright
-
-著作権表示を出力して終了します。 -
--help
- -
-オプションの簡単な説明を出力して終了します。 -
-
diff --git a/doc/ja/debug.ja.rdoc b/doc/ja/debug.ja.rdoc deleted file mode 100644 index 90f70f68..00000000 --- a/doc/ja/debug.ja.rdoc +++ /dev/null @@ -1,36 +0,0 @@ -= パーサのデバッグ - -ここでは、Racc を使っていくうえで遭遇しそうな問題について書きます。 - -== 文法ファイルがパースエラーになる - -エラーメッセージに出ている行番号のあたりを見て間違いを -探してください。ブロックを閉じる行でエラーになる場合は、 -どこかで開き括弧などを増やしてしまっている可能性が高いです。 - -== なんたら conflict って言われた - -一番ありがちで一番面倒な問題は衝突 (conflict) でしょう。 -文法中に衝突があると、racc はコンパイル後に -「5 shift/reduce conflict」のようなメッセージを表示します。 --v をつけると出力される .output ファイルからはさらに詳しい情報が得られます。 -それをどう使うか、とかそういうことに関しては、それなりの本を読んでください。 -とてもここに書けるような単純な話ではありません。 -当然ながら『Ruby を 256 倍使うための本 無道編』(青木峰郎著)がお勧めです。 - -== パーサは問題なく生成できたけど予想どおりに動かない - -racc に -g オプションをつけてパーサを出力すると、デバッグ用のコードが -付加されます。ここで、パーサクラスのインスタンス変数 @yydebug を true に -しておいてから do_parse/yyparse を呼ぶと、デバッグ用メッセージが出力 -されます。パーサが動作する様子が直接見えますので、完全に現在の状態を -把握できます。これを見てどこがおかしいのかわかったらあとは直すだけ。 - -== next_token に関して - -いまだ自分でも忘れることが多いのが -「送るトークンが尽きたら [false,なにか] を送る」ということです。 -ちなみに Racc 0.10.2 以降では一度 [false,なにか] を受け取ったら -それ以上 next_token は呼ばないことが保証されています。 - -追記: 最近は [false,なにか] ではなく nil でもよいことになった。 diff --git a/doc/ja/grammar.ja.rdoc b/doc/ja/grammar.ja.rdoc deleted file mode 100644 index fd414a05..00000000 --- a/doc/ja/grammar.ja.rdoc +++ /dev/null @@ -1,348 +0,0 @@ -= 規則ファイル文法リファレンス - -== 文法に関する前バージョンとの非互換 - - * (1.2.5) ユーザーコードを連結する時、外部ファイルよりも - 埋めこんであるコードを先に連結します。 - * (1.1.6) 新しいディレクティブ options が追加されました。 - * (1.1.5) 予約語 token の意味が変更になりました。 - * (0.14) ルールの最後のセミコロンが省略可能になりました。 - また、token prechigh などが予約語でなくなりました。 - * (10.2) prepare が header に driver が footer になりました。 - 今はそのままでも使えますが、2.0 からは対応しません。 - * (0.10) class に対応する end がなくなりました。 - * (0.9) ダサダサのピリオド方式をやめて { と } で囲むようにしました。 - -== 全体の構造 - -トップレベルは、規則部とユーザーコード部に分けられます。 -ユーザーコード部はクラス定義の後に来なければいけません。 - -=== コメント - -文法ファイルには、一部例外を除いて、ほとんどどこにでもコメントを -書くことができます。コメントは、Rubyの #.....(行末) スタイルと、 -Cの /*......*/ スタイルを使うことができます。 - -=== 規則部 - -規則部は以下のような形をしています。 --- -class クラス名 [< スーパークラス] - [演算子順位] - [トークン宣言] - [オプション] - [expect] - [トークンシンボル値おきかえ] - [スタート規則] -rule - 文法記述 --- -"クラス名"はここで定義するパーサクラスの名前です。 -これはそのままRubyのクラス名になります。 - -また M::C のように「::」を使った名前を使うと、クラス定義を -モジュール M の中にネストさせます。つまり class M::C ならば --- -module M - class C < Racc::Parser - いろいろ - end -end --- -のように出力します。 - -さらに、Ruby と同じ構文でスーパークラスを指定できます。 -ただしこの指定をするとパーサの動作に重大な影響を与えるので、 -特に必要がない限り指定してはいけません。これは将来の拡張の -ために用意したもので、現在指定する必然性はあまりありません。 - -=== 文法の記述 - -racc で生成するパーサが理解できる文法を記述します。 -文法は、予約語 rule と end の間に、以下のような書式で書きます。 --- -トークン: トークンの並び アクション - -トークン: トークンの並び アクション - | トークンの並び アクション - | トークンの並び アクション - (必要なだけ同じようにつづける) --- -アクションは { } で囲みます。アクションでは Ruby の文はほとんど -使えますが、一部だけは非対応です。対応していないものは以下のとおり。 - - * ヒアドキュメント - * =begin ... =end 型コメント - * スペースで始まる正規表現 - * ごくまれに % の演算。普通に演算子のまわりにスペースを入れていれば問題なし - -このあたりに関しては完全な対応はまず無理です。あきらめてください。 - -左辺の値($$)は、オプションによって返し方がかわります。まずデフォルトでは -ローカル変数 result (そのデフォルト値は val[0])が 左辺値を表し、アクション -ブロックを抜けた時の result の値が左辺値になります。または明示的に return -で返した場合もこの値になります。一方、options で no_result_var を指定した -場合、左辺値はアクションブロックの最後の文の値になります (Ruby のメソッドと -同じ)。 - -どちらの場合でもアクションは省略でき、省略した場合の左辺値は常に val[0] です。 - -以下に文法記述の全体の例をしめします。 --- -rule - goal: def ruls source - { - result = val - } - - def : /* none */ - { - result = [] - } - | def startdesig - { - result[0] = val[1] - } - | def - precrule # これは上の行の続き - { - result[1] = val[1] - } -(略) --- -アクション内では特別な意味をもった変数がいくつか使えます。 -そのような変数を以下に示します。括弧の中は yacc での表記です。 - - * result ($$) - -左辺の値。初期値は val[0] です。 - - * val ($1,$2,$3…) - -右辺の記号の値の配列。Ruby の配列なので当然インデックスはゼロから始まります。 -この配列は毎回作られるので自由に変更したり捨てたりして構いません。 - - * _values (...,$-2,$-1,$0) - -値スタック。Racc コアが使っているオブジェクトがそのまま渡されます。 -この変数の意味がわかる人以外は絶対に変更してはいけません。 - -またアクションの特別な形式に、埋めこみアクションというものがあります。 -これはトークン列の途中の好きなところに記述することができます。 -以下に埋めこみアクションの例を示します。 --- -target: A B { puts 'test test' } C D { normal action } --- -このように記述すると A B を検出した時点で puts が実行されます。 -また、埋めこみアクションはそれ自体が値を持ちます。つまり、以下の例において --- -target: A { result = 1 } B { p val[1] } --- -最後にある p val[1] は埋めこみアクションの値 1 を表示します。 -B の値ではありません。 - -意味的には、埋めこみアクションは空の規則を持つ非終端記号を追加することと -全く同じ働きをします。つまり、上の例は次のコードと完全に同じ意味です。 --- -target : A nonterm B { p val[1] } -nonterm : /* 空の規則 */ { result = 1 } --- - -=== 演算子優先順位 - -あるトークン上でシフト・還元衝突がおこったとき、そのトークンに -演算子優先順位が設定してあると衝突を解消できる場合があります。 -そのようなものとして特に有名なのは数式の演算子と if...else 構文です。 - -優先順位で解決できる文法は、うまく文法をくみかえてやれば -優先順位なしでも同じ効果を得ることができます。しかしたいていの -場合は優先順位を設定して解決するほうが文法を簡単にできます。 - -シフト・還元衝突がおこったとき、Racc はまずその規則に順位が設定 -されているか調べます。規則の順位は、その規則で一番うしろにある -終端トークンの優先順位です。たとえば --- -target: TERM_A nonterm_a TERM_B nonterm_b --- -のような規則の順位はTERM_Bの優先順位になります。もしTERM_Bに -優先順位が設定されていなかったら、優先順位で衝突を解決することは -できないと判断し、「Shift/Reduce conflict」を報告します。 - -演算子の優先順位はつぎのように書いて定義します。 --- -prechigh - nonassoc PLUSPLUS - left MULTI DIVIDE - left PLUS MINUS - right '=' -preclow --- -prechigh に近い行にあるほど優先順位の高いトークンです。上下をまるごと -さかさまにして preclow...prechigh の順番に書くこともできます。left -などは必ず行の最初になければいけません。 - -left right nonassoc はそれぞれ「結合性」を表します。結合性によって、 -同じ順位の演算子の規則が衝突した場合にシフト還元のどちらをとるかが -決まります。たとえば --- -a - b - c --- -が --- -(a - b) - c --- -になるのが左結合 (left) です。四則演算は普通これです。 -一方 --- -a - (b - c) --- -になるのが右結合 (right) です。代入のクオートは普通 right です。 -またこのように演算子が重なるのはエラーである場合、非結合 (nonassoc) です。 -C 言語の ++ や単項のマイナスなどがこれにあたります。 - -ところで、説明したとおり通常は還元する規則の最後のトークンが順位を -決めるのですが、ある規則に限ってそのトークンとは違う順位にしたいことも -あります。例えば符号反転のマイナスは引き算のマイナスより順位を高く -しないといけません。このような場合 yacc では %prec を使います。 -racc ではイコール記号を使って同じことをできます。 --- -prechigh - nonassoc UMINUS - left '*' '/' - left '+' '-' -preclow -(略) -exp: exp '*' exp - | exp '-' exp - | '-' exp = UMINUS # ここだけ順位を上げる --- -このように記述すると、'-' exp の規則の順位が UMINUS の順位になります。 -こうすることで符号反転の '-' は '*' よりも順位が高くなるので、 -意図どおりになります。 - -=== トークン宣言 - -トークン(終端記号)のつづりを間違えるというのはよくあることですが、 -発見するのはなかなか難しいものです。1.1.5 からはトークンを明示的に -宣言することで、宣言にないトークン / 宣言にだけあるトークンに対して -警告が出るようになりました。yacc の %token と似ていますが最大の違いは -racc では必須ではなく、しかもエラーにならず警告だけ、という点です。 - -トークン宣言は以下のように書きます。 --- -token A B C D - E F G H --- -トークンのリストを複数行にわたって書けることに注目してください。 -racc では一般に「予約語」は行の先頭に来た時だけ予約語とみなされるので -prechigh などもシンボルとして使えます。ただし深淵な理由から end だけは -どうやっても予約語になってしまいます。 - -=== オプション - -racc のコマンドラインオプションの一部をファイル中にデフォルト値 -として記述することができます。 --- -options オプション オプション … --- -現在ここで使えるのは - - * omit_action_call - -空のアクション呼び出しを省略する - - * result_var - -変数 result を使う - -です。 -それぞれ no_ を頭につけることで意味を反転できます。 - -=== expect - -実用になるパーサはたいてい無害な shift/reduce conflict を含みます。 -しかし文法ファイルを書いた本人はそれを知っているからいいですが、 -ユーザが文法ファイルを処理した時に「conflict」と表示されたら -不安に思うでしょう。そのような場合、以下のように書いておくと -shift/reduce conflict のメッセージを抑制できます。 --- -expect 3 --- -この場合 shift/reduce conflict はぴったり三つでなければいけません。 -三つでない場合はやはり表示が出ます (ゼロでも出ます)。 -また reduce/reduce conflict の表示は抑制できません。 - -=== トークンシンボル値の変更 - -トークンシンボルを表す値は、デフォルトでは - - * 文法中、引用符でかこまれていないもの (RULEとかXENDとか) - →その名前の文字列を intern して得られるシンボル (1.4 では Fixnum) - * 引用符でかこまれているもの(':'とか'.'とか) - →その文字列そのまま - -となっていますが、たとえば他の形式のスキャナがすでに存在する場合などは、 -これにあわせなければならず、このままでは不便です。このような場合には、 -convert 節を加えることで、トークンシンボルを表す値を変えることができます。 -以下がその例です。 --- -convert - PLUS 'PlusClass' #→ PlusClass - MIN 'MinusClass' #→ MinusClass -end --- -デフォルトではトークンシンボル PLUS に対してはトークンシンボル値は -:PLUS ですが、上のような記述がある場合は PlusClass になります。 -変換後の値は false・nil 以外ならなんでも使えます。 - -変換後の値として文字列を使うときは、次のように引用符を重ねる必要があります。 --- -convert - PLUS '"plus"' #→ "plus" -end --- -また、「'」を使っても生成された Ruby のコード上では「"」になるので -注意してください。バックスラッシュによるクオートは有効ですが、バック -スラッシュは消えずにそのまま残ります。 --- -PLUS '"plus\n"' #→ "plus\n" -MIN "\"minus#{val}\"" #→ \"minus#{val}\" --- - -=== スタート規則 - -パーサをつくるためには、どの規則が「最初の」規則か、ということを Racc におしえて -やらなければいけません。それを明示的に書くのがスタート規則です。スタート規則は -次のように書きます。 --- -start real_target --- -start は行の最初にこなければいけません。このように書くと、ファイルで -一番最初に出てくる real_target の規則をスタート規則として使います。 -省略した場合は、ファイルの最初の規則がスタート規則になります。普通は -最初の規則を一番上にかくほうが書きやすく、わかりやすくなりますから、 -この記法はあまりつかう必要はないでしょう。 - -=== ユーザーコード部 - -ユーザーコードは、パーサクラスが書きこまれるファイルに、 -アクションの他にもコードを含めたい時に使います。このようなものは -書きこまれる場所に応じて三つ存在し、パーサクラスの定義の前が -header、クラスの定義中(の冒頭)が inner、定義の後が footer です。 -ユーザコードとして書いたものは全く手を加えずにそのまま連結されます。 - -ユーザーコード部の書式は以下の通りです。 --- ----- 識別子 - ruby の文 - ruby の文 - ruby の文 - ----- 識別子 - ruby の文 - : --- -行の先頭から四つ以上連続した「-」(マイナス)があるとユーザーコードと -みなされます。識別子は一つの単語で、そのあとには「=」以外なら何を -書いてもかまいません。 diff --git a/doc/ja/index.ja.html b/doc/ja/index.ja.html deleted file mode 100644 index 3c30ac44..00000000 --- a/doc/ja/index.ja.html +++ /dev/null @@ -1,10 +0,0 @@ -

Racc ユーザマニュアル

-

バージョン 1.4 対応

- diff --git a/doc/ja/parser.ja.rdoc b/doc/ja/parser.ja.rdoc deleted file mode 100644 index 395047bf..00000000 --- a/doc/ja/parser.ja.rdoc +++ /dev/null @@ -1,125 +0,0 @@ -= class Racc::Parser -Racc の生成するパーサはすべて Racc::Parser クラスを継承します。 -Racc::Parser クラスにはパース中に使用するメソッドがいくつかあり、 -そのようなメソッドをオーバーロードすると、パーサを初期化したり -することができます。 - -== Super Class - -Object - -== Constants - -プリフィクス "Racc_" がついた定数はパーサの予約定数です。 -そのような定数は使わないでください。動作不可能になります。 -== Instance Methods -ここに載っているもののほか、プリフィクス "racc_" および "_racc_" が -ついたメソッドはパーサの予約名です。そのようなメソッドは使わないで -ください。 - -: do_parse -> Object - パースを開始します。 - また、トークンが必要になった時は #next_token を呼び出します。 - - -- - # Example - ---- inner - def parse - @q = [[1,1], - [2,2], - [3,3], - [false, '$']] - do_parse - end - - def next_token - @q.shift - end - -- - -: next_token -> [Symbol, Object] - [abstract method] - - パーサが次のトークンを読みこむ時に使います。 - [記号, その値] の形式の配列を返してください。 - 記号はデフォルトでは - - * 文法中、引用符でかこまれていないもの - → その名前の文字列のシンボル (例えば :ATOM ) - * 引用符でかこまれているもの
- → その文字列そのまま (例えば '=' ) - - で表します。これを変更する方法については、 - 文法リファレンスを参照してください。 - - また、もう送るシンボルがなくなったときには - [false, なにか] または nil を返してください。 - - このメソッドは抽象メソッドなので、#do_parse を使う場合は - 必ずパーサクラス中で再定義する必要があります。 - 定義しないままパースを始めると例外 NotImplementedError が - 発生します。 - -: yyparse( receiver, method_id ) - パースを開始します。このメソッドでは始めてトークンが - 必要になった時点で receiver に対して method_id メソッドを - 呼び出してトークンを得ます。 - - receiver の method_id メソッドはトークンを yield しなければ - なりません。形式は #next_token と同じで [記号, 値] です。 - つまり、receiver の method_id メソッドの概形は以下のように - なるはずです。 - -- - def method_id - until end_of_file - : - yield 記号, 値 - : - end - end - -- - 少し注意が必要なのは、method_id が呼び出されるのは始めて - トークンが必要になった時点であるということです。method_id - メソッドが呼び出されたときは既にパースが進行中なので、 - アクション中で使う変数を method_id の冒頭で初期化すると - まず失敗します。 - - トークンの終端を示す [false, なにか] を渡したらそれ以上は - yield しないでください。その場合には例外が発生します。 - - 最後に、method_id メソッドからは必ず yield してください。 - しない場合は何が起きるかわかりません。 - -: on_error( error_token_id, error_value, value_stack ) - パーサコアが文法エラーを検出すると呼び出します (yacc の yyerror)。 - エラーメッセージを出すなり、例外を発生するなりしてください。 - このメソッドから正常に戻った場合、パーサはエラー回復モード - に移行します。 - - error_token_id はパースエラーを起こした記号の内部表現 (整数) です。 - #token_to_str で文法ファイル上の文字列表現に直せます。 - - error_value はその値です。 - - value_stack はエラーの時点での値スタックです。 - value_stack を変更してはいけません。 - - on_error のデフォルトの実装は例外 ParseError を発生します。 - -: token_to_str( t ) -> String - Racc トークンの内部表現 (整数) - を文法ファイル上の記号表現の文字列に変換します。 - - t が整数でない場合は TypeError を発生します。 - t が範囲外の整数だった場合は nil を返します。 - -: yyerror - エラー回復モードに入ります。このとき #on_error は呼ばれません。 - アクション以外からは呼び出さないでください。 - -: yyerrok - エラー回復モードから復帰します。 - アクション以外からは呼び出さないでください。 - -: yyaccept - すぐに値スタックの先頭の値を返して #do_parse、#yyparse を抜けます。 diff --git a/doc/ja/racc.ja.rhtml b/doc/ja/racc.ja.rhtml deleted file mode 100644 index 3ab978e7..00000000 --- a/doc/ja/racc.ja.rhtml +++ /dev/null @@ -1,36 +0,0 @@ -% require 'makefile' -% version = Makefile.get_parameter('Makefile', 'version') -

Racc

- - - - - - -
最新版<%= version %>
種別parser generator
形式ruby script, ruby extension
必要環境ruby (>=1.6)
配布条件LGPL
- -

-Ruby 用の LALR(1) パーザジェネレータです。 -生成したパーサはそれなりに高速に動作します。 -

-

-Racc で生成したパーサは動作時にランタイムモジュールが必要です。 -Ruby 1.8 にはこのランタイムが最初から添付されているので -何も考えなくて大丈夫ですが、Ruby 1.6 以前を対象にするときは -racc -E でパーサを生成する必要があります。 -

-

-なお、Racc 1.4.x のランタイムと Ruby 1.8 添付の Racc ランタイムは、 -ソースコード上では微妙に違いがありますが、完全に互換性があります。 -

- -

状況

-

-もう基本的な部分は枯れているはずです。 -TODO はまだいくつかありますが、気持ちが他にいってるので -当分は大きく変更するつもりはありません。 -

diff --git a/doc/ja/usage.ja.html b/doc/ja/usage.ja.html deleted file mode 100644 index b1291a0c..00000000 --- a/doc/ja/usage.ja.html +++ /dev/null @@ -1,414 +0,0 @@ -

Racc の使い方

-

-Racc は文法規則から Ruby で書かれたパーサを生成するパーサジェネレータです。 -パーサ生成アルゴリズムには yacc などと同じ LALR(1) を使用しています。 -

-

-yacc を知っている人は記述法の違いだけわかれば使えると思います。 -yacc を知らない人は -拙著『Ruby を 256 倍使うための本 無道編』(青木峰郎著、ASCII) -などを一読していただくのがよいかと思います。 -他の UNIX コマンドなどとは異なり、 -いきなり使うだけで Racc を理解するのはかなり困難です。 -

- -

Racc とはなにか

-

-Racc は文法を処理するツールです。 -文字列はただの文字の列で、コンピュータにとっては意味を持ちません。 -しかし人間はその文字の列の中になにか意味を見出すことができます。 -コンピュータにもそのようなことを、部分的にでも、させられたら便利でしょう。 -Racc はその手伝いをしてくれます。完全な自動化ではありませんが、 -人間が全部やるよりも遥かに簡単になります。 -

-

-Racc が自動化してくれる部分とは、文字列の含む「構造」の処理です。 -たとえば Ruby の if 文を考えてみると、次のように定式化できます。 -

-
-if 条件式 [then]
-  文
-  :
-[elsif 条件式 [then]
-  文
-  :]
-[else
-  文
-  :]
-end
-
-

-if 文では if という単語が最初になくてはならず、 -elsif 節は else 節より前になくてはいけません。 -このような配置の関係 (構造) が、Racc が処理する対象です。 -

-

-一方、Racc で処理できないのはどういうことでしょうか。それは、たとえば -if の条件式にあたる部分が「なんであるか」ということです。つまり、条件 -式が if の条件だということです。これは、こっちで条件として扱うコードを -書いてやらないといけません。 -

-

-と言っても、わかりにくいでしょう。こういう抽象的なものは実際にいじって -みるのが一番です。 -

- -

実際の話

-

-実際に Racc をどのように使うかという話をします。Racc には独自のソース -コードみたいなものがあって、この中に処理したい「構造」を記述しておきま -す。このソースファイルを「文法ファイル」と呼ぶことにしましょう。この文 -法ファイルの名前が parse.y と仮定すると、コマンドラインから以下のよう -に打ちこめば、その構造を処理するためのクラスを含んだファイルが得られま -す。 -

-
-$ racc parse.y
-
-

-生成されるファイルはデフォルトでは "ファイル名.tab.rb" です。他の名前 -にしたいなら、-o オプションで変更できます。 -

-
-$ racc parse.y -o myparser.rb
-
-

-このようにして作ったクラス、またはそのような処理を担当するパート、 -のことはパーサ (parser) と呼ぶことになっています。解析するヤツ、 -というくらいに適当にとらえてください。 -

- -

文法ファイルを書く

-

-Racc は文法ファイルから Ruby のクラスを生成するツールだと言いました。 -そのクラスは全て Racc::Parser の下位クラスで、名前は文法ファイル中で -指定します。以下、ここに書くべきことが「なんなのか」を説明します。 -ここでは内容に重点を置くので、文法ファイル自体の文法の詳細は -文法リファレンスを見てください。 -

- -

文法

-

-まずは、全体の概形です。 -

-
-class MyParser
-rule
-
-  if_stmt: IF expr then stmt_list elsif else END
-
-  then   : THEN
-         |
-
-  elsif  :
-         | ELSIF stmt_list
-
-  else   :
-         | ELSE stmt_list
-
-  expr   : NUMBER
-         | IDENT
-         | STRING
-
-  stmt_list : ふにゃふにゃ
-
-end
-
-

-Ruby スクリプトのように class でパーサクラス名を指定し、rule ... end -の間にパーサに解析させたい文法を記述します。 -

-

-文法は、記号の並びでもって表します。rule ... end の間にあるコロンとバー -以外のもの、if_stmt IF expr then などが全て「記号」です。そしてコロン -が日本語で言う「〜は××だ」の「は」みたいなもんで、その左の記号が右の -記号の列と同じものを指す、というふうに定義します。また、バーは「または」 -を意味します。それと、単純にコロンの左の記号のことを左辺、右を右辺とも -言います。以下はこちらのほうを使って説明しましょう。 -

-

-少し注意が必要な点を述べます。まず、then の、バーのあとの定義 (規則) を -見てください。ここには何も書いていないので、これはその通り「無」であっ -てもいい、ということを表しています。つまり、then は記号 THEN 一個か、 -またはなにもなし(省略する)でよい、ということです。記号 then は実際の -Ruby のソースコードにある then とは切り離して考えましょう -(それは実は大文字の記号 THEN が表しています)。 -

-

-さて、そろそろ「記号」というものがなんなのか書きましょう。 -ただし順番に話をしないといけないので、まずは聞いていてください。 -この文章の最初に、パーサとは文字の列から構造を見出す部分だと言いました。 -しかし文字の列からいきなり構造を探すのは面倒なので、実際にはまず -文字の列を単語の列に分割します。その時点でスペースやコメントは捨てて -しまい、以降は純粋にプログラムの一部をなす部分だけを相手にします。 -たとえば文字列の入力が次のようだったとすると、 -

-
-if flag then   # item found.
-  puts 'ok'
-end
-
-

-単語の列は次のようになります。 -

-
-if flag then puts 'ok' end
-
-

-ここで、工夫が必要です。どうやら flag はローカル変数名だと思われますが、 -変数名というのは他にもいろいろあります。しかし名前が i だろうが a だろ -うが vvvvvvvvvvvv だろうが、「構造」は同じです。つまり同じ扱いをされる -べきです。変数 a を書ける場所なら b も書けなくてはいけません。だったら -一時的に同じ名前で読んでもいいじゃん。ということで、この単語の列を以下 -のように読みかえましょう。 -

-
-IF IDENT THEN IDENT STRING END
-
-

-これが「記号」の列です。パーサではこの記号列のほうを扱い、構造を見付け -ていきます。 -

-

-さらに記号について見ていきましょう。 -記号は二種類に分けられます。「左辺にある記号」と「ない記号」です。 -左辺にある記号は「非終端」記号と言います。ないほうは「終端」記号と -言います。最初の例では終端記号はすべて大文字、非終端記号は小文字で -書いてあるので、もう一度戻って例の文法を見てください。 -

-

-なぜこの区分が重要かと言うと、入力の記号列はすべて終端記号だからです。 -一方、非終端記号はパーサの中でだけ、終端記号の列から「作りだす」ことに -よって始めて存在します。例えば次の規則をもう一度見てください。 -

-
-  expr   : NUMBER
-         | IDENT
-         | STRING
-
-

-expr は NUMBER か IDENT か STRING だと言っています。逆に言うと、 -IDENT は expr に「なることができます」。文法上 expr が存在できる -場所に IDENT が来ると、それは expr になります。例えば if の条件式の -部分は expr ですから、ここに IDENT があると expr になります。その -ように文法的に「大きい」記号を作っていって、最終的に一個になると、 -その入力は文法を満たしていることになります。実際にさっきの入力で -試してみましょう。入力はこうでした。 -

-
-IF IDENT THEN IDENT STRING END
-
-

-まず、IDENT が expr になります。 -

-
-IF expr THEN IDENT STRING END
-
-

-次に THEN が then になります。 -

-
-IF expr then IDENT STRING END
-
-

-IDENT STRING がメソッドコールになります。この定義はさきほどの例には -ないですが、実は省略されているんだと考えてください。そしていろいろな -過程を経て、最終的には stmt_list (文のリスト)になります。 -

-
-IF expr then stmt_list END
-
-

-elsif と else は省略できる、つまり無から生成できます。 -

-
-IF expr then stmt_list elsif else END
-
-

-最後に if_stmt を作ります。 -

-
-if_stmt
-
-

-ということでひとつになりました。 -つまりこの入力は文法的に正しいということがわかりました。 -

- -

アクション

-

-ここまでで入力の文法が正しいかどうかを確認する方法はわかりましたが、 -これだけではなんにもなりません。最初に説明したように、ここまででは -構造が見えただけで、プログラムは「意味」を理解できません。そしてその -部分は Racc では自動処理できないので、人間が書く、とも言いました。 -それを書くのが以下に説明する「アクション」という部分です。 -

-

-前項で、記号の列がだんだんと大きな単位にまとめられていく過程を見ました。 -そのまとめる時に、同時になにかをやらせることができます。それが -アクションです。アクションは、文法ファイルで以下のように書きます。 -

-
-class MyParser
-rule
-
-  if_stmt: IF expr then stmt_list elsif else END
-             { puts 'if_stmt found' }
-
-  then   : THEN
-             { puts 'then found' }
-         |
-             { puts 'then is omitted' }
-
-  elsif  :
-             { puts 'elsif is omitted' }
-         | ELSIF stmt_list
-             { puts 'elsif found' }
-
-  else   :
-             { puts 'else omitted' }
-         | ELSE stmt_list
-             { puts 'else found' }
-
-  expr   : NUMBER
-             { puts 'expr found (NUMBER)' }
-         | IDENT
-             { puts 'expr found (IDENT)' }
-         | STRING
-             { puts 'expr found (STRING)' }
-
-  stmt_list : ふにゃふにゃ
-
-end
-
-

-見てのとおり、規則のあとに { と } で囲んで書きます。 -アクションにはだいたい好きなように Ruby スクリプトが書けます。 -

-

-(この節、未完) -

-
- -

-yacc での $$ は Racc ではローカル変数 result -で、$1,$2... は配列 valです。 -resultval[0] ($1) の値に初期化され、 -アクションを抜けたときの result の値が左辺値になります。 -Racc ではアクション中の return はアクションから抜けるだけで、 -パース自体は終わりません。アクション中からパースを終了するには、 -メソッド yyaccept を使ってください。 -

-

-演算子の優先順位、スタートルールなどの yacc の一般的な機能も用意されて -います。ただしこちらも少し文法が違います。 -

-

-yacc では生成されたコードに直接転写されるコードがありました。 -Racc でも同じように、ユーザ指定のコードが書けます。 -Racc ではクラスを生成するので、クラス定義の前/中/後の三個所があります。 -Racc ではそれを上から順番に header inner footer と呼んでいます。 -

- -

ユーザが用意すべきコード

-

-パースのエントリポイントとなるメソッドは二つあります。ひとつは -do_parseで、こちらはトークンを -Parser#next_token から得ます。もうひとつは -yyparse で、こちらはスキャナから yield され -ることによってトークンを得ます。ユーザ側ではこのどちらか(両方でもいい -けど)を起動する簡単なメソッドを inner に書いてください。これらメソッド -の引数など、詳しいことはリファレンスを見てください。 -

- -

-どちらのメソッドにも共通なのはトークンの形式です。必ずトークンシンボル -とその値の二要素を持つ配列を返すようにします。またスキャンが終了して、 -もう送るものがない場合は [false,なにか] を返し -てください。これは一回返せば十分です (逆に、yyparse を使 -う場合は二回以上 yield してはいけない)。 -

-

-パーサは別に文字列処理にだけ使われるものではありませんが、実際問題とし -て、パーサを作る場面ではたいてい文字列のスキャナとセットで使うことが多 -いでしょう。Ruby ならスキャナくらい楽勝で作れますが、高速なスキャナと -なると実は難しかったりします。そこで高速なスキャナを作成するためのライ -ブラリも作っています。詳しくは -「スキャナを作る」の項を見てください。 -

-

-Racc には error トークンを使ったエラー回復機能もあります。yacc の -yyerror() は Racc では -Racc::Parser#on_error -で、エラーが起きたトークンとその値、値スタック、の三つの引数をとります。 -on_error のデフォルトの実装は例外 -Racc::ParseError を発生します。 -

-

-ユーザがアクション中でパースエラーを発見した場合は、メソッド -yyerror -を呼べばパーサがエラー回復モードに入ります。 -ただしこのとき on_errorは呼ばれません。 -

- -

パーサを生成する

-

-これだけあればだいたい書けると思います。あとは、最初に示した方法で文法 -ファイルを処理し、Ruby スクリプトを得ます。 -

-

-うまくいけばいいのですが、大きいものだと最初からはうまくいかないでしょ -う。racc に -g オプションをつけてコンパイルし、@yydebug を true にする -とデバッグ用の出力が得られます。デバッグ出力はパーサの @racc_debug_out -に出力されます(デフォルトは stderr)。また、racc に -v オプションをつけ -ると、状態遷移表を読みやすい形で出力したファイル(*.output)が得られます。 -どちらもデバッグの参考になるでしょう。 -

- - -

作ったパーサを配布する

-

-Racc の生成したパーサは動作時にランタイムルーチンが必要です。 -具体的には parser.rb と cparse.so です。 -ただし cparse.so は単にパースを高速化するためのライブラリなので -必須ではありません。なくても動きます。 -

-

-まず Ruby 1.8.0 以降にはこのランタイムが標準添付されているので、 -Ruby 1.8 がある環境ならばランタイムについて考慮する必要はありません。 -Racc 1.4.x のランタイムと Ruby 1.8 に添付されているランタイムは -完全互換です。 -

-

-問題は Ruby 1.8 を仮定できない場合です。 -Racc をユーザみんなにインストールしてもらうのも一つの手ですが、 -これでは不親切です。そこでRacc では回避策を用意しました。 -

-

-racc に -E オプションをつけてコンパイルすると、 -パーサと racc/parser.rb を合体したファイルを出力できます。 -これならばファイルは一つだけなので簡単に扱えます。 -racc/parser.rb は擬似的に require したような扱いになるので、 -この形式のパーサが複数あったとしてもクラスやメソッドが衝突することもありません。 -ただし -E を使った場合は cparse.so が使えませんので、 -必然的にパーサの速度は落ちます。 -

- - -

おまけ: スキャナを書く

-

-パーサを使うときは、たいてい文字列をトークンに切りわけてくれるスキャナ -が必要になります。しかし実は Ruby は文字列の最初からトークンに切りわけ -ていくという作業があまり得意ではありません。 -正確に言うと、簡単にできるのですが、それなりのオーバーヘッドがかかります。 -

-

-そのオーバーヘッドを回避しつつ、 -手軽にスキャナを作れるように strscan というパッケージを作りました。 -Ruby 1.8 以降には標準添付されていますし、 -筆者のホームページには -単体パッケージがあります。 -

diff --git a/doc/parser-class-reference.md b/doc/parser-class-reference.md new file mode 100644 index 00000000..6af9bf68 --- /dev/null +++ b/doc/parser-class-reference.md @@ -0,0 +1,490 @@ +# Racc::Parser Class Reference + +Complete API reference for the `Racc::Parser` class and generated parser classes. + +## Overview + +All parsers generated by Racc inherit from the `Racc::Parser` class. This class provides methods for parsing, error handling, and debugging. You can override these methods to customize parser behavior. + +## Class Hierarchy + +``` +Object + └─ Racc::Parser + └─ YourGeneratedParser +``` + +## Reserved Names + +To avoid conflicts with internal Racc mechanisms: + +- Constants: Do not use constants with the `Racc_` prefix +- Methods: Do not use methods with the `racc_` or `_racc_` prefixes + +Using these reserved names will cause the parser to malfunction. + +## Instance Methods + +### Parsing Entry Points + +#### `do_parse` -> Object + +Starts the parsing process. Calls `next_token` to obtain tokens. + +Usage: + +```ruby +def parse + @tokens = [ + [:NUMBER, 1], + [:PLUS, '+'], + [:NUMBER, 2], + [false, '$'] + ] + do_parse +end + +def next_token + @tokens.shift +end +``` + +Returns: The result value (typically from the start rule's action) + +Requires: You must implement the `next_token` method + +See also: [`next_token`](#next_token--symbol-object) + +#### `yyparse(receiver, method_id)` -> Object + +Starts parsing. Calls `receiver.send(method_id)` to obtain tokens via `yield`. + +Parameters: +- `receiver`: Object that will supply tokens +- `method_id`: Method name (symbol or string) to call on receiver + +Usage: + +```ruby +def parse(scanner) + yyparse(scanner, :scan) +end + +# In scanner: +def scan + until end_of_input + # ... + yield [:NUMBER, 42] + yield [:PLUS, '+'] + # ... + end + yield [false, '$'] # End marker - yield only once +end +``` + +Important notes: +- The receiver's method must `yield` tokens in the form `[symbol, value]` +- The method is called when the parser first needs a token (parsing is already in progress) +- Don't initialize action variables at the start of the method +- After yielding `[false, anything]`, don't yield again - an exception will occur +- The receiver method must always yield tokens, never return them + +### Token Handling + +#### `next_token` -> [Symbol, Object] + +[Abstract method - must be implemented by user] + +Called by the parser to obtain the next token. Must return a two-element array: `[symbol, value]`. + +Return value format: +- `[symbol, value]`: The next token +- `[false, anything]` or `nil`: End of input + +Token symbol format: + +By default: +- Unquoted symbols in grammar (ATOM, NUMBER, etc.) -> Ruby symbols (`:ATOM`, `:NUMBER`) +- Quoted strings in grammar (`'='`, `'+'`, etc.) -> Same string (`'='`, `'+'`) + +These defaults can be changed with the `convert` block in the grammar file. + +Example: + +```ruby +def next_token + return [false, '$'] if @position >= @tokens.length + token = @tokens[@position] + @position += 1 + token +end +``` + +Raises: `NotImplementedError` if not implemented when using `do_parse` + +### Error Handling + +#### `on_error(error_token_id, error_value, value_stack)` + +Called by the parser core when a syntax error is detected (equivalent to yacc's `yyerror`). + +Parameters: +- `error_token_id`: Integer - Internal representation of the error token +- `error_value`: The value of the error token +- `value_stack`: The value stack at the time of error (do not modify!) + +Default behavior: Raises `Racc::ParseError` + +Usage - Custom error messages: + +```ruby +def on_error(token_id, value, value_stack) + token_name = token_to_str(token_id) + raise ParseError, "Unexpected #{token_name}: #{value.inspect}" +end +``` + +Usage - Error recovery: + +```ruby +def on_error(token_id, value, value_stack) + @errors << "Error at #{value}" + # Return normally to enter error recovery mode +end +``` + +Notes: +- If this method returns normally, the parser enters error recovery mode +- The value stack must not be modified +- Use `token_to_str` to convert `token_id` to a readable string + +#### `token_to_str(token_id)` -> String + +Converts a token's internal representation (integer) to its string representation from the grammar file. + +Parameters: +- `token_id`: Integer - Internal token ID + +Returns: String - Token symbol as it appears in the grammar, or `nil` if out of range + +Raises: `TypeError` if `token_id` is not an integer + +Example: + +```ruby +def on_error(token_id, value, value_stack) + token_str = token_to_str(token_id) + puts "Parse error at token: #{token_str}" +end +``` + +#### `yyerror` + +Manually enters error recovery mode. Does not call `on_error`. + +Usage: + +```ruby +expression: IDENT + { + if val[0] == "error" + yyerror # Enter error recovery mode + end + result = val[0] + } +``` + +Important: Only call from within an action. Do not call from outside the parsing process. + +#### `yyerrok` + +Exits error recovery mode and returns to normal parsing. + +Usage: + +```ruby +error_recovery: error SEMICOLON + { + yyerrok # Resume normal parsing + result = :recovered + } +``` + +Important: Only call from within an action. + +### Flow Control + +#### `yyaccept` + +Immediately exits `do_parse` or `yyparse`, returning the value at the top of the value stack. + +Usage: + +```ruby +statement: BREAK + { + result = :break + yyaccept # Exit parsing immediately + } +``` + +Returns: The value currently at the top of the value stack + +Important: Only call from within an action. + +## Action Variables + +These special variables are available within action blocks in your grammar file. + +### `result` (yacc's `$$`) + +The value of the left-hand side (the result of the rule). + +Default value: `val[0]` + +Usage: + +```ruby +expression: term '+' term + { + result = val[0] + val[2] + } +``` + +Note: Not available if `options no_result_var` is specified in the grammar. + +### `val` (yacc's `$1, $2, $3, ...`) + +Array containing the values of right-hand side symbols (zero-indexed). + +Usage: + +```ruby +expression: term '+' term + { + # val[0] = value of first 'term' + # val[1] = value of '+' + # val[2] = value of second 'term' + result = val[0] + val[2] + } +``` + +Notes: +- Array is freshly created for each action, so you can freely modify or discard it +- Indexing starts at 0 (unlike yacc's $1, $2, which start at 1) + +### `_values` (yacc's `..., $-2, $-1, $0`) + +The internal value stack used by the Racc core. + +Warning: This is the actual object used by Racc. Do not modify unless you fully understand parser internals. Modifying this stack can cause parser corruption. + +Usage: Primarily for inspection/debugging by advanced users. + +## Instance Variables + +### `@yydebug` + +Controls debug output. Set to `true` to enable debugging (parser must be generated with `-g`). + +Usage: + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +result = parser.parse(input) +``` + +Output: Detailed parsing information to `@racc_debug_out` + +Requirements: Parser must be generated with `racc -g` + +### `@racc_debug_out` + +Output destination for debug messages. + +Default: `$stderr` + +Usage: + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +parser.instance_variable_set(:@racc_debug_out, File.open('debug.log', 'w')) +``` + +## Exceptions + +### `Racc::ParseError` + +Raised by the default `on_error` implementation when a syntax error occurs. + +Inheritance: `StandardError` + +Usage - Catching parse errors: + +```ruby +begin + result = parser.parse(input) +rescue Racc::ParseError => e + puts "Parse error: #{e.message}" +end +``` + +Usage - Raising custom errors: + +```ruby +def on_error(token_id, value, value_stack) + raise Racc::ParseError, "Unexpected token at line #{@line}" +end +``` + +## Complete Example + +Here's a complete example demonstrating the parser class API: + +```ruby +# Generated by: racc calculator.y -o calculator.rb + +class Calculator < Racc::Parser + # ... (Racc-generated code) ... + + # User code (from ---- inner block): + + def parse(input) + @tokens = tokenize(input) + @position = 0 + do_parse + end + + def next_token + return [false, '$'] if @position >= @tokens.length + token = @tokens[@position] + @position += 1 + token + end + + def tokenize(input) + # Tokenization logic + tokens = [] + # ... + tokens + end + + def on_error(token_id, value, value_stack) + token_name = token_to_str(token_id) + raise ParseError, "Syntax error at #{token_name}: #{value.inspect}" + end +end + +# Usage: +parser = Calculator.new +result = parser.parse("2 + 3 * 4") +puts result # => 14 +``` + +## Advanced Usage + +### Custom Parser Base Class + +You can specify a custom base class in the grammar file: + +```ruby +class MyParser < CustomBase +rule + # ... +end +``` + +Warning: This significantly affects parser behavior. Only use if you have a specific need. + +### Multiple Parsers in One File + +Each parser class is independent. You can have multiple parsers: + +```ruby +# grammar1.y +class Parser1 < Racc::Parser + # ... +end + +# grammar2.y +class Parser2 < Racc::Parser + # ... +end + +# Use both: +p1 = Parser1.new +p2 = Parser2.new +``` + +### Thread Safety + +Racc parsers maintain state in instance variables during parsing. Each thread should use its own parser instance: + +```ruby +# Thread-safe usage: +Thread.new do + parser = MyParser.new # New instance per thread + result = parser.parse(input) +end +``` + +### Reusing Parser Instances + +Parser instances can be reused for multiple parse operations: + +```ruby +parser = MyParser.new + +result1 = parser.parse(input1) +result2 = parser.parse(input2) # Safe - parser resets state +``` + +Note: Ensure your `parse` method properly resets any custom state. + +## Debugging Tips + +### Enable Debug Output + +```ruby +parser = MyParser.new +parser.instance_variable_set(:@yydebug, true) +result = parser.parse(input) +``` + +### Redirect Debug Output + +```ruby +parser.instance_variable_set(:@racc_debug_out, File.open('parse.log', 'w')) +``` + +### Custom Debugging + +```ruby +def on_error(token_id, value, value_stack) + puts "Error at token: #{token_to_str(token_id)}" + puts "Current value: #{value.inspect}" + puts "Stack depth: #{value_stack.length}" + raise ParseError +end +``` + +## Yacc Compatibility Reference + +For users familiar with yacc/bison: + +| yacc/bison | Racc | Notes | +|------------|------|-------| +| `yyparse()` | `do_parse` or `yyparse()` | Two methods available | +| `yylex()` | `next_token` | Returns `[symbol, value]` | +| `yyerror()` | `on_error()` | Takes 3 arguments | +| `YYACCEPT` | `yyaccept` | Method call | +| `YYERROR` | `yyerror` | Method call | +| `yyerrok` | `yyerrok` | Method call | +| `$$` | `result` | Local variable in action | +| `$1, $2, ...` | `val[0], val[1], ...` | Zero-indexed array | +| `$-1, $-2, ...` | `_values[-1], ...` | Don't modify! | +| `%token` | `token` | Optional in Racc | +| `%prec` | `= SYMBOL` | Different syntax | +| `%expect` | `expect` | Same behavior | diff --git a/racc.gemspec b/racc.gemspec index 710ddf5e..c214581d 100644 --- a/racc.gemspec +++ b/racc.gemspec @@ -34,16 +34,12 @@ DESC "lib/racc/parser.rb", "lib/racc/parserfilegenerator.rb", "lib/racc/sourcetext.rb", "lib/racc/state.rb", "lib/racc/statetransitiontable.rb", - "lib/racc/static.rb", - "doc/en/grammar.en.rdoc", "doc/en/grammar2.en.rdoc", - "doc/ja/command.ja.html", "doc/ja/debug.ja.rdoc", - "doc/ja/grammar.ja.rdoc", "doc/ja/index.ja.html", - "doc/ja/parser.ja.rdoc", "doc/ja/usage.ja.html", + "lib/racc/static.rb", "doc/index.md", "doc/getting-started.md", + "doc/command-reference.md", "doc/parser-class-reference.md", + "doc/grammar-reference.md", "doc/debugging.md", "doc/advanced-topics.md" ] s.require_paths = ["lib"] s.required_ruby_version = ">= 2.5" - s.rdoc_options = ["--main", "README.rdoc"] - s.extra_rdoc_files = ["README.ja.rdoc", "README.rdoc"] s.metadata["homepage_uri"] = s.homepage s.metadata["source_code_uri"] = s.homepage s.metadata["documentation_uri"] = "https://ruby.github.io/racc/"