Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
77feb29
Replace Prism action lexing with a Ruby scanner
katsyoshi Sep 10, 2026
523f479
Expand action scanner to general Ruby source
katsyoshi Sep 10, 2026
27eaabe
Add local Rails lexer health snapshot
katsyoshi Sep 10, 2026
b30fd94
Improve Ruby source scanning for Rails
katsyoshi Sep 10, 2026
869f9f1
Handle keyword argument labels in Ruby lexer
katsyoshi Sep 10, 2026
9e1d5b5
Recognize percent literals after calls
katsyoshi Sep 10, 2026
280041d
Support interpolated Ruby symbols
katsyoshi Sep 10, 2026
3711dc4
Distinguish shifts from heredocs
katsyoshi Sep 10, 2026
ce3715a
Handle operator methods and call-site heredocs
katsyoshi Sep 10, 2026
42bda35
Support Ruby operator globals and backreferences
katsyoshi Sep 10, 2026
aa24988
Allow non-ASCII percent literal delimiters
katsyoshi Sep 10, 2026
701eab9
Disambiguate percent operators from literals
katsyoshi Sep 10, 2026
1db3a62
Disambiguate compressed shifts from heredocs
katsyoshi Sep 10, 2026
a5f181e
Disambiguate percent operators from percent literals
katsyoshi Sep 10, 2026
cde4f4a
Stop scanning Ruby source at __END__
katsyoshi Sep 10, 2026
29611e9
Avoid treating shifts after variables as heredocs
katsyoshi Sep 10, 2026
2e75791
Require a delimiter boundary for heredocs
katsyoshi Sep 10, 2026
5cd547d
Add Ruby source lexer literal support
katsyoshi Sep 12, 2026
ddadf42
Improve Ruby heredoc lexing
katsyoshi Sep 12, 2026
c0cba0e
Improve lexer handling for Rails syntax
katsyoshi Sep 12, 2026
0441a61
Classify Ruby splat and block arguments
katsyoshi Sep 12, 2026
4dacb8f
Improve Ruby constant and operator lexing
katsyoshi Sep 12, 2026
274bd80
Recognize qualified operator methods
katsyoshi Sep 12, 2026
7634890
Handle argumentless Ruby command blocks
katsyoshi Sep 12, 2026
b6563aa
Handle Ruby brace blocks
katsyoshi Sep 12, 2026
6fdf731
Lex Ruby setter method names
katsyoshi Sep 12, 2026
c7965ec
Lex keyword methods after receivers
katsyoshi Sep 12, 2026
1115676
Disambiguate endless method definitions
katsyoshi Sep 12, 2026
746df88
Avoid synthetic blocks after shifts
katsyoshi Sep 12, 2026
001f59f
Test block context after shifts
katsyoshi Sep 12, 2026
e89db08
Prefer longest Ruby operators
katsyoshi Sep 12, 2026
8cef693
Lex Ruby argument forwarding
katsyoshi Sep 12, 2026
7c1c488
Distinguish Ruby indexing brackets
katsyoshi Sep 12, 2026
06f305b
Handle variable-shaped Ruby symbols
katsyoshi Sep 12, 2026
7b6b0b5
Handle empty Ruby subclasses
katsyoshi Sep 12, 2026
aee0180
Keep predicate calls in expression context
katsyoshi Sep 12, 2026
1063544
Lex Ruby operator call parentheses
katsyoshi Sep 12, 2026
52fb02a
Lex forwarded Ruby arguments after commas
katsyoshi Sep 12, 2026
a55292b
Lex explicit super calls
katsyoshi Sep 12, 2026
829bf07
Fix qualified class path actions
katsyoshi Sep 12, 2026
22adc0d
Normalize Ruby argument lists
katsyoshi Sep 12, 2026
631a3c7
Lex do after parenthesized calls
katsyoshi Sep 12, 2026
073afc1
Ignore newlines after logical operators
katsyoshi Sep 12, 2026
44771ed
Lex symbols after predicate identifiers
katsyoshi Sep 12, 2026
e7735c9
Lex brace blocks after parenthesized calls
katsyoshi Sep 12, 2026
da4a375
Lex lambda bodies after argument lists
katsyoshi Sep 12, 2026
64102e1
Normalize splat argument lists
katsyoshi Sep 12, 2026
4f442c9
Lex escaped line continuations
katsyoshi Sep 12, 2026
cfe6cd5
Resolve block argument grammar conflict
katsyoshi Sep 12, 2026
efe46da
Lex receiver method brace blocks
katsyoshi Sep 12, 2026
7bca933
Accept block calls after argument lists
katsyoshi Sep 12, 2026
3a0a3a5
Lex ampersand operator symbols
katsyoshi Sep 12, 2026
11e55b1
Lex modifiers after control keywords
katsyoshi Sep 12, 2026
0fda7cf
Lex operator method symbols
katsyoshi Sep 12, 2026
85c3533
Lex bitwise assignment operators
katsyoshi Sep 12, 2026
dd73bc3
Avoid synthetic blocks after keyword values
katsyoshi Sep 12, 2026
73c735f
Lex setter symbols
katsyoshi Sep 12, 2026
bbe897e
Preserve newlines before Ruby terminators
katsyoshi Sep 12, 2026
1026049
Preserve heredoc suffix tokens
katsyoshi Sep 12, 2026
5793476
Ignore newlines after operator assignments
katsyoshi Sep 12, 2026
cefa56c
Preserve newlines after heredocs
katsyoshi Sep 12, 2026
4e9b68f
Preserve newlines in synthetic blocks
katsyoshi Sep 12, 2026
b540e4d
Preserve newlines in brace blocks
katsyoshi Sep 12, 2026
4ae0693
Lex class variables
katsyoshi Sep 12, 2026
45841f4
Continue method chains after newlines
katsyoshi Sep 12, 2026
5108fb1
Lex spaced ternary colons
katsyoshi Sep 12, 2026
3e8b36e
Preserve newlines in lambda blocks
katsyoshi Sep 12, 2026
9f3affc
Lex punctuation global variables
katsyoshi Sep 12, 2026
920b1e5
Distinguish modulo from percent literals
katsyoshi Sep 12, 2026
fd079d3
Lex percent regexp flags
katsyoshi Sep 12, 2026
a33a524
Lex parenthesized yield arguments
katsyoshi Sep 12, 2026
9c178f1
Reset conditional do context at newlines
katsyoshi Sep 12, 2026
f8a710b
Recognize modifiers after block endings
katsyoshi Sep 12, 2026
118798f
Lex quoted hash labels
katsyoshi Sep 12, 2026
e5184d7
Lex unary operator symbols
katsyoshi Sep 12, 2026
b67095c
Lex slash operator method definitions
katsyoshi Sep 12, 2026
3df56b1
Preserve receiver calls and interpolated strings
katsyoshi Sep 12, 2026
2307d71
Classify spaced empty brackets as arrays
katsyoshi Sep 12, 2026
599168d
Preserve do block calls in Ruby AST
katsyoshi Sep 12, 2026
397db84
Recognize spaced array command arguments
katsyoshi Sep 12, 2026
13e9201
Preserve interpolation in percent strings and heredocs
katsyoshi Sep 12, 2026
c47dd7c
Handle interpolated regular expression literals
katsyoshi Sep 12, 2026
798507b
Accept interpolated symbol literals
katsyoshi Sep 12, 2026
00e3525
Finish lexer interpolation edge cases
katsyoshi Sep 12, 2026
d95f6f1
Build interpolated dynamic symbols
katsyoshi Sep 12, 2026
b4bb278
Continue lines after bitwise ampersand
katsyoshi Sep 12, 2026
5d5229c
Handle bitwise operator line continuations
katsyoshi Sep 12, 2026
f05ad4c
Continue lines after splat arguments
katsyoshi Sep 12, 2026
530bc57
Continue lines after range operators
katsyoshi Sep 12, 2026
72aafdd
Continue lines after keyword logic operators
katsyoshi Sep 12, 2026
7d848f9
Preserve line continuations for operator tokens
katsyoshi Sep 12, 2026
42b9602
Continue lines after not operator
katsyoshi Sep 12, 2026
4aee3ca
Continue lines after lambda and scope operators
katsyoshi Sep 12, 2026
5f09ba3
Refactor Ruby lexer lexical context
katsyoshi Sep 12, 2026
2fc4d85
Refine command block context after labels
katsyoshi Sep 12, 2026
33a7860
Improve Ruby block AST actions and lexical context
katsyoshi Sep 12, 2026
b93cee9
Track command start in Ruby lexical context
katsyoshi Sep 13, 2026
29eee8f
Record Ruby lexer state transitions
katsyoshi Sep 13, 2026
f119590
Track hash delimiters in Ruby lexer context
katsyoshi Sep 13, 2026
c2cd1af
Balance Ruby conditional lexer context
katsyoshi Sep 13, 2026
2ef89d1
Track command argument delimiters in lexer context
katsyoshi Sep 13, 2026
90c1e3a
Track Ruby block nesting in lexer context
katsyoshi Sep 13, 2026
a613bdb
Track command brace blocks in lexer context
katsyoshi Sep 13, 2026
8266878
Track Ruby method name lexical state
katsyoshi Sep 13, 2026
d96eb79
Track Ruby label lexical states
katsyoshi Sep 13, 2026
7fe286e
Expose generated parser state traces
katsyoshi Sep 13, 2026
0a52751
Preserve UTF-8 character literals in Ruby lexer
katsyoshi Sep 13, 2026
f271949
Use regular do tokens for argumentless blocks
katsyoshi Sep 13, 2026
ffa54b7
Use real brace tokens for argumentless blocks
katsyoshi Sep 13, 2026
3dae457
Classify brace blocks by call context
katsyoshi Sep 13, 2026
7226978
Track Ruby method and lambda scopes
katsyoshi Sep 13, 2026
1b734b2
Preserve command brace token compatibility
katsyoshi Sep 13, 2026
b9afffe
Preserve command brace block AST
katsyoshi Sep 13, 2026
fa93e4a
Recognize command argument percent literals
katsyoshi Sep 13, 2026
27ea152
Classify beginless range tokens
katsyoshi Sep 13, 2026
e0b0d79
Keep double dollar globals intact
katsyoshi Sep 13, 2026
29bc465
Classify nested call brace blocks
katsyoshi Sep 13, 2026
f7f4633
Preserve assignment brace block chains
katsyoshi Sep 13, 2026
f80cea8
Connect parser events to lexical context
katsyoshi Sep 13, 2026
32ac838
Expose parser lookahead state
katsyoshi Sep 13, 2026
6392a5d
Track parser delimiter and block stacks
katsyoshi Sep 13, 2026
8dfe7c3
Track parser conditional stack
katsyoshi Sep 13, 2026
9b05cf8
Track parser lexical scopes
katsyoshi Sep 13, 2026
04ee7d7
Classify call braces inside arguments
katsyoshi Sep 13, 2026
877ae99
Classify method body array literals
katsyoshi Sep 13, 2026
4692ce0
Classify ternary branch brace blocks
katsyoshi Sep 13, 2026
228a854
Keep load path global intact
katsyoshi Sep 13, 2026
72ff469
Classify keyword-shaped method names in definitions
katsyoshi Sep 13, 2026
0d6242b
Handle multiple heredocs on one line
katsyoshi Sep 13, 2026
732541f
Recognize ampersand operator method names
katsyoshi Sep 13, 2026
efee505
Avoid synthetic ampersands after block arguments
katsyoshi Sep 13, 2026
a15943d
Preserve newlines after nested blocks
katsyoshi Sep 13, 2026
e5b0c68
Preserve block terminator newlines in calls
katsyoshi Sep 13, 2026
90f3fef
Preserve newlines after operator symbols
katsyoshi Sep 13, 2026
bc794f0
Fix contextual Ruby lexer tokens
katsyoshi Sep 13, 2026
af7a709
Restore alias and symbol lexer boundaries
katsyoshi Sep 13, 2026
64307b5
Handle keyword-shaped receiver setters
katsyoshi Sep 13, 2026
a9becbd
Classify lambda command blocks correctly
katsyoshi Sep 13, 2026
b64fd69
Remove synthetic block argument tokens
katsyoshi Sep 13, 2026
40a0e75
Track block delimiter ownership
katsyoshi Sep 13, 2026
730b7b7
Scan regexp interpolation boundaries
katsyoshi Sep 13, 2026
6b6b5da
Lex bracket operators in aliases
katsyoshi Sep 13, 2026
be5b9be
Ignore newline before block parameters
katsyoshi Sep 13, 2026
6ed16bf
Preserve alias newline lexer state
katsyoshi Sep 13, 2026
46922d7
Lex setter definitions without arguments
katsyoshi Sep 13, 2026
86ac8ff
Classify splat method body hashes
katsyoshi Sep 13, 2026
6feb8ad
Preserve bracket operators in aliases
katsyoshi Sep 13, 2026
09e536e
Restore lambda block context
katsyoshi Sep 13, 2026
2002677
Preserve heredoc suffix context
katsyoshi Sep 13, 2026
0cc8a57
Preserve blocks around singleton classes
katsyoshi Sep 13, 2026
9765c2b
Continue method chains through comments
katsyoshi Sep 13, 2026
3cb4bec
Track conditional block endings
katsyoshi Sep 13, 2026
7d3ac25
Preserve method scopes inside blocks
katsyoshi Sep 13, 2026
9c3d8b7
Classify negative command blocks
katsyoshi Sep 13, 2026
9bebe2a
Lex operator symbols without literals
katsyoshi Sep 13, 2026
acf7692
Finish Rails Ruby lexer compatibility
katsyoshi Sep 13, 2026
5dfe43d
Cover remaining Rails trick syntax
katsyoshi Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ values are Floats, and `tIDENTIFIER` values are Symbols or Strings. Operators an
keywords may carry `nil`. Parentheses use Ruby's context-dependent token names
(for example `tLPAREN` followed by `")"`), not a source string tokenizer.

For source input, use `Arpaka.parse_source`:

```ruby
tree = Arpaka.parse_source("a = 1\na + 2", filename: "example.rb")
```

The source lexer is byte-oriented and context-sensitive. It supports the same
frontend slice as the token API, including numbers, variables, calls, arrays,
hashes, control flow, definitions, strings, regular expressions, percent
literals and static heredocs. Lexer failures include filename, line and byte
column. Ruby interpolation and syntax whose value cannot be represented by the
current AST raises an explicit lexer error.

The supported slice includes numeric and nil/boolean literals, binary `+ - * /`,
unary `+ -`, single-expression parentheses, simple local assignments, and
statements separated by semicolons or newlines. Every successful parse returns
Expand All @@ -141,10 +154,19 @@ an `AST::Program`. Its frozen `statements` array contains immutable Data nodes:
An unassigned bare identifier is a `BareCall`; assignment registers a local
before reading its right-hand side, including in `a = a`.

This is not yet a complete Ruby frontend. Strings, explicit calls, control flow,
methods, and multi-statement parentheses are unsupported. Nodes have no source
locations. Input must already reflect the lexical decisions Ruby's parser and
lexer normally make together; this API does not parse Ruby source text.
Explicit receiver calls without blocks produce
`ReceiverCall(receiver, operator, name, arguments)`. They preserve chained calls,
ordered positional arguments, the `.` / `::` / `&.` operator, and the implicit
`call` method in `handler.(message)`. Receiverless calls use `Call(name, arguments)`.

Double quoted strings and backtick strings may contain simple interpolation and
produce `InterpolatedString(parts)`, where each part is a string or an AST node.

This is not yet a complete Ruby frontend. Percent literal interpolation, blocks,
method argument scopes, qualified constant paths, and other constructs are still incomplete; a successful
parse does not guarantee that all source semantics appear in the AST. Nodes have
no source locations. Token input must already reflect the lexical decisions
Ruby's parser and lexer normally make together; use `parse_source` for source text.

`Arpaka::ParseError` exposes `token` and `state`. `Arpaka::UnsupportedSyntax`
exposes the original `rule` and upstream `line`. Both inherit from
Expand Down
1 change: 0 additions & 1 deletion arpaka.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "lrama", ">= 0.8.0"
spec.add_dependency "prism", "~> 1.0"
end
6 changes: 5 additions & 1 deletion lib/lrama/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ def self.compile(source, filename: "(grammar)", class_name: "Parser", mode: :par
code = generate(source, filename: filename, class_name: class_name,
mode: mode, allow_error_rules: allow_error_rules)
box = ::Ruby::Box.new
box.eval(code)
begin
box.eval(code)
rescue SyntaxError => error
raise Error, "Invalid generated Ruby for #{filename}: #{error.message}"
end
box.const_get(class_name, false)
end
end
Expand Down
59 changes: 26 additions & 33 deletions lib/lrama/ruby/action_code.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
# frozen_string_literal: true

require "prism"
require_relative "action_scanner"

module Lrama
module Ruby
# Loaded only in the generator's box. Ruby strings, comments, regular
# expressions and instance variables must not become grammar references.
module ActionReferences
def ruby_short_reference?(offset)
references
@ruby_short_references.include?(offset)
end

private

def _references
Prism.lex(s_value).value.filter_map do |token, _state|
next unless [:GLOBAL_VARIABLE, :NUMBERED_REFERENCE].include?(token.type)

value = token.value
unless /\A\$(?:\$|[1-9][0-9]*|[a-zA-Z_][a-zA-Z0-9_]*)\z/.match?(value)
raise Backend::Error, "Unsupported semantic reference: #{value}"
end

attributes = if /\A\$[0-9]+\z/.match?(value)
{ number: value.delete_prefix("$").to_i, index: value.delete_prefix("$").to_i }
result = ActionScanner.new(s_value, filename: location.grammar_file.path,
line: location.first_line, column: location.first_column).scan
@ruby_short_references = result.references.select(&:short_interpolation).map(&:first_column)
result.references.map do |reference|
attributes = if reference.number
{ number: reference.number, index: reference.number }
else
{ name: value.delete_prefix("$") }
{ name: reference.name }
end
Grammar::Reference.new(type: :dollar, **attributes,
first_column: token.location.start_offset, last_column: token.location.end_offset)
end.sort_by(&:first_column)
first_column: reference.first_column, last_column: reference.last_column)
end
rescue ActionScanner::Error => error
raise Backend::Error, error.message
end
end

Expand All @@ -34,25 +37,15 @@ def lex_c_code
return super unless @end_symbol == "}"

reset_first_position
depth = 0
# Prism recovers from the yacc references (which are not yet valid
# Ruby assignments) and still provides byte-accurate token locations.
Prism.lex(@scanner.rest).value.each do |token, _state|
case token.type
when :BRACE_LEFT, :LAMBDA_BEGIN, :EMBEXPR_BEGIN
depth += 1
when :BRACE_RIGHT, :EMBEXPR_END
if depth.zero?
code = @scanner.rest.byteslice(0, token.location.start_offset)
@scanner.pos += code.bytesize
@line += code.count("\n")
@head = @scanner.pos - code.bytesize + code.b.rindex("\n") + 1 if code.include?("\n")
return [:C_DECLARATION, Lexer::Token::UserCode.new(s_value: code, location: location)]
end
depth -= 1
end
end
raise Backend::Error, "Unterminated Ruby action at line #{line}"
result = ActionScanner.new(@scanner.rest, filename: @grammar_file.path,
line: line, column: column).scan(action: true)
code = @scanner.string.byteslice(@scanner.pos, result.end_offset)
@scanner.pos += code.bytesize
@line += code.count("\n")
@head = @scanner.pos - code.bytesize + code.b.rindex("\n") + 1 if code.include?("\n")
[:C_DECLARATION, Lexer::Token::UserCode.new(s_value: code, location: location)]
rescue ActionScanner::Error => error
raise Backend::Error, error.message
end
end
end
Expand Down
Loading