Capture DSL
Parslet capture DSL for variable binding
How the Code Works
This section explains how the parsing rules map to the format semantics.
Capture Mechanism
rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }The `capture(:name)` method captures matched text for later reference: This captures the heredoc marker (like `CAPTURE` or `FOOBAR`) in the context.
Dynamic Matching
rule(:captured_marker) {
dynamic { |source, context|
str(context.captures[:marker])
}
}The `dynamic` block accesses captured values: `context.captures[:marker]` returns the previously captured string.
Scope Isolation
rule(:document) { scope { doc_start >> text >> doc_end } }The `scope` block creates isolated capture contexts: Each document has its own capture namespace, allowing nested heredocs.
Document Structure
rule(:text) {
(document.as(:doc) | text_line.as(:line)).repeat(1)
}Heredoc-style documents with nested content: Documents can contain other documents (nested heredocs).
End Marker Matching
rule(:doc_end) { captured_marker }The end marker must match the start marker: This ensures `<FOOBAR` ... `FOOBAR` pairs are correctly matched.
End Marker Matching
rule(:doc_end) { captured_marker }The end marker must match the start marker: This ensures `<FOOBAR` ... `FOOBAR` pairs are correctly matched.
Source Code
# frozen_string_literal: true
# This example demonstrates how pieces of input can be captured and matched
# against later on. Without this, you cannot match here-documents and other
# self-dependent grammars.
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require "parsanol/parslet"
require "parsanol/convenience"
require "pp"
class CapturingParser < Parsanol::Parser
root :document
# Introduce a scope for each document. This ensures that documents can be
# nested.
rule(:document) { scope { doc_start >> text >> doc_end } }
# Start of a document is a heredoc marker. This is captured in :marker
rule(:doc_start) { str("<") >> marker >> newline }
rule(:marker) { match["A-Z"].repeat(1).capture(:marker) }
# The content of a document can be either lines of text or another
# document, introduced by <HERE, where HERE is the doc marker.
rule(:text) { (document.as(:doc) | text_line.as(:line)).repeat(1) }
rule(:text_line) do
captured_marker.absent? >> any >>
(newline.absent? >> any).repeat >> newline
end
# The end of the document is marked by the marker that was at the beginning
# of the document, by itself on a line.
rule(:doc_end) { captured_marker }
rule(:captured_marker) do
dynamic do |_source, context|
str(context.captures[:marker])
end
end
rule(:newline) { match["\n"] }
end
parser = CapturingParser.new
pp parser.parse_with_debug %(<CAPTURE
Text1
<FOOBAR
Text3
Text4
FOOBAR
Text2
CAPTURE)