Skip to content

Commit

Permalink
Add Paco::Combinators.index
Browse files Browse the repository at this point in the history
  • Loading branch information
skryukov committed Dec 18, 2021
1 parent 1dac424 commit 30941c0
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 15 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ rescue Paco::ParseError => e
end
```

- `Paco::Combinators.index` method. ([@skryukov])

Call `Paco::Combinators.index` to get `Paco::Index` representing the current offset into the parse without consuming the input.
`Paco::Index` has a 0-based character offset attribute `:pos` and 1-based `:line` and `:column` attributes.

```ruby
index.parse("Paco") #=> #<struct Paco::Index pos=0, line=1, column=1>
```


### Changed

- `Paco::Combinators.seq_map` merged into `Paco::Combinators.seq`. ([@skryukov])
Expand Down
7 changes: 1 addition & 6 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
require "bundler/setup"
require "paco"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
include Paco

require "irb"
IRB.start(__FILE__)
13 changes: 13 additions & 0 deletions docs/paco.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,19 @@ example = lazy { failed("always fails") }
example.parse("Paco") #=> Paco::ParseError
```

### Paco::Combinators.index

Returns parser that returns `Paco::Index` representing the current offset into the parse without consuming the input.
`Paco::Index` has a 0-based character offset attribute `:pos` and 1-based `:line` and `:column` attributes.

```ruby
include Paco

example = seq(one_of("123\n ").many.join, index, remainder)

example.parse("1\n2\n3\n\n Paco") #=> ["1\n2\n3\n\n ", #<struct Paco::Index pos=8, line=5, column=2>, "Paco"]
```

## Paco::Combinators: Text related methods

### Paco::Combinators.string(matcher)
Expand Down
7 changes: 7 additions & 0 deletions lib/paco/combinators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ def optional(parser)
alt(parser, succeed(nil))
end

# Returns parser that returns `Paco::Index` representing
# the current offset into the parse without consuming the input.
# @return [Paco::Parser]
def index
Parser.new { |ctx| ctx.index }
end

# Helper used for memoization
def memoize(&block)
Memoizer.memoize(block.source_location, &block)
Expand Down
12 changes: 4 additions & 8 deletions lib/paco/context.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "paco/callstack"
require "paco/index"

module Paco
class Context
Expand All @@ -25,15 +26,10 @@ def eof?
pos >= input.length
end

# @param [Integer] from
# @return [Paco::Index]
def index(from = nil)
from ||= pos
lines = input[0..from].lines

{
line: lines.length,
column: lines[-1]&.length || 0,
pos: from
}
Index.calculate(input: input, pos: from || pos)
end

# @param [Paco::Parser] parser
Expand Down
15 changes: 15 additions & 0 deletions lib/paco/index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Paco
Index = Struct.new(:pos, :line, :column) do
# @param [String] input
# @param [Integer] pos
def self.calculate(input:, pos:)
raise ArgumentError, "`pos` must be a non-negative integer" if pos < 0
raise ArgumentError, "`pos` is grater then input length" if pos > input.length

lines = input[0..pos].lines
line = lines.empty? ? 1 : lines.length
column = lines.last&.length || 1
new(pos, line, column)
end
end
end
2 changes: 1 addition & 1 deletion lib/paco/parse_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def message
index = ctx.index(pos)
<<~MSG
\nParsing error
line #{index[:line]}, column #{index[:column]}:
line #{index.line}, column #{index.column}:
unexpected #{ctx.eof? ? "end of file" : ctx.input[pos].inspect}
expecting #{expected}
MSG
Expand Down
6 changes: 6 additions & 0 deletions spec/paco/combinators_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,10 @@
expect { example.parse("Paco") }.to raise_error(Paco::ParseError)
end
end

describe "#index" do
it "returns index" do
expect(index.parse("")).to eq(Paco::Index.new(0, 1, 1))
end
end
end
26 changes: 26 additions & 0 deletions spec/paco/index_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require "spec_helper"

RSpec.describe Paco::Index do
subject(:index) { described_class }

let(:input) { "1\n2\n\n3\nPaco" }

it "returns index", :aggregate_failures do
expect(index.calculate(input: input, pos: 0)).to eq(Paco::Index.new(0, 1, 1))
expect(index.calculate(input: input, pos: 1)).to eq(Paco::Index.new(1, 1, 2))
expect(index.calculate(input: input, pos: 2)).to eq(Paco::Index.new(2, 2, 1))
expect(index.calculate(input: input, pos: 10)).to eq(Paco::Index.new(10, 5, 4))
end

it "returns start position when empty string" do
expect(index.calculate(input: "", pos: 0)).to eq(Paco::Index.new(0, 1, 1))
end

it "raises an error when pos < 0" do
expect { index.calculate(input: input, pos: -1) }.to raise_error(ArgumentError)
end

it "raises an error when pos > input length" do
expect { index.calculate(input: input, pos: 1000) }.to raise_error(ArgumentError)
end
end

0 comments on commit 30941c0

Please sign in to comment.