Skip to content

Commit

Permalink
Add #negative? to all expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
jaynetics committed Jan 7, 2024
1 parent 5c60c67 commit be5ce5b
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 38 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- all expressions now respond to `#negative?` / `#negated?`
- previously only sets, props, and posix classes did
- implemented `#negative?` / `#negated?` for more applicable expressions
- `\B`, `\D`, `\H`, `\S`, `\W`, `(?!...)`, `(?<!...)`

## [2.8.3] - 2023-12-04 - Janosch Müller

### Fixed
Expand Down
1 change: 1 addition & 0 deletions lib/regexp_parser/expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
require 'regexp_parser/expression/methods/human_name'
require 'regexp_parser/expression/methods/match'
require 'regexp_parser/expression/methods/match_length'
require 'regexp_parser/expression/methods/negative'
require 'regexp_parser/expression/methods/options'
require 'regexp_parser/expression/methods/parts'
require 'regexp_parser/expression/methods/printing'
Expand Down
5 changes: 1 addition & 4 deletions lib/regexp_parser/expression/classes/character_set.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
module Regexp::Expression
class CharacterSet < Regexp::Expression::Subexpression
attr_accessor :closed, :negative

alias :negative? :negative
alias :negated? :negative
alias :closed? :closed
alias :closed? :closed

def initialize(token, options = {})
self.negative = false
Expand Down
4 changes: 0 additions & 4 deletions lib/regexp_parser/expression/classes/posix_class.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
module Regexp::Expression
class PosixClass < Regexp::Expression::Base
def negative?
type == :nonposixclass
end

def name
text[/\w+/]
end
Expand Down
4 changes: 0 additions & 4 deletions lib/regexp_parser/expression/classes/unicode_property.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
module Regexp::Expression
module UnicodeProperty
class Base < Regexp::Expression::Base
def negative?
type == :nonproperty
end

def name
text[/\A\\[pP]\{([^}]+)\}\z/, 1]
end
Expand Down
20 changes: 20 additions & 0 deletions lib/regexp_parser/expression/methods/negative.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Regexp::Expression
module Shared
def negative?
false
end

# not an alias so as to respect overrides of #negative?
def negated?
negative?
end
end

Anchor::NonWordBoundary.class_eval { def negative?; true end }
Assertion::NegativeLookahead.class_eval { def negative?; true end }
Assertion::NegativeLookbehind.class_eval { def negative?; true end }
CharacterSet.class_eval { def negative?; negative end }
CharacterType::Base.class_eval { def negative?; token.start_with?('non') end }
PosixClass.class_eval { def negative?; type == :nonposixclass end }
UnicodeProperty::Base.class_eval { def negative?; type == :nonproperty end }
end
30 changes: 30 additions & 0 deletions spec/expression/methods/negative_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'spec_helper'

RSpec.describe('Expression::Base#negative?') do
include_examples 'parse', //, [] => [:root, negative?: false]
include_examples 'parse', /a/, [0] => [:literal, negative?: false]

include_examples 'parse', /\b/, [0] => [:word_boundary, negative?: false]
include_examples 'parse', /\B/, [0] => [:nonword_boundary, negative?: true]

include_examples 'parse', /(?=)/, [0] => [:lookahead, negative?: false]
include_examples 'parse', /(?!)/, [0] => [:nlookahead, negative?: true]

include_examples 'parse', /(?<=)/, [0] => [:lookbehind, negative?: false]
include_examples 'parse', /(?<!)/, [0] => [:nlookbehind, negative?: true]

include_examples 'parse', /[a]/, [0] => [:character, negative?: false]
include_examples 'parse', /[^a]/, [0] => [:character, negative?: true]

include_examples 'parse', /\d/, [0] => [:digit, negative?: false]
include_examples 'parse', /\D/, [0] => [:nondigit, negative?: true]

include_examples 'parse', /[[:word:]]/, [0, 0] => [:word, negative?: false]
include_examples 'parse', /[[:^word:]]/, [0, 0] => [:word, negative?: true]

include_examples 'parse', /\p{word}/, [0] => [:word, negative?: false]
include_examples 'parse', /\p{^word}/, [0] => [:word, negative?: true]

include_examples 'parse', //, [] => [:root, negated?: false]
include_examples 'parse', /[^a]/, [0] => [:character, negated?: true]
end
4 changes: 2 additions & 2 deletions spec/parser/posix_classes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
RSpec.describe('PosixClass parsing') do
include_examples 'parse', /[[:word:]]/,
[0] => [CharacterSet, count: 1],
[0, 0] => [:posixclass, :word, PosixClass, name: 'word', text: '[:word:]', negative?: false]
[0, 0] => [:posixclass, :word, PosixClass, name: 'word', text: '[:word:]']
include_examples 'parse', /[[:^word:]]/,
[0] => [CharacterSet, count: 1],
[0, 0] => [:nonposixclass, :word, PosixClass, name: 'word', text: '[:^word:]', negative?: true]
[0, 0] => [:nonposixclass, :word, PosixClass, name: 'word', text: '[:^word:]']

# cases treated as regular subsets by Ruby, not as (invalid) posix classes
include_examples 'parse', '[[:ab]c:]',
Expand Down
40 changes: 20 additions & 20 deletions spec/parser/properties_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@

RSpec.describe('Property parsing') do
# test various notations supported by Ruby
include_examples 'parse', '\p{sd}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\p{SD}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\p{Soft Dotted}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\p{Soft-Dotted}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\p{sOfT_dOtTeD}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\p{sd}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\p{SD}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\p{Soft Dotted}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\p{Soft-Dotted}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\p{sOfT_dOtTeD}', 0 => [:property, :soft_dotted]

# test ^-negation
include_examples 'parse', '\p{^sd}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\p{^SD}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\p{^Soft Dotted}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\p{^Soft-Dotted}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\p{^sOfT_dOtTeD}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\p{^sd}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\p{^SD}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\p{^Soft Dotted}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\p{^Soft-Dotted}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\p{^sOfT_dOtTeD}', 0 => [:nonproperty, :soft_dotted]

# test P-negation
include_examples 'parse', '\P{sd}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\P{SD}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\P{Soft Dotted}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\P{Soft-Dotted}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\P{sOfT_dOtTeD}', 0 => [:nonproperty, :soft_dotted, negative?: true]
include_examples 'parse', '\P{sd}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\P{SD}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\P{Soft Dotted}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\P{Soft-Dotted}', 0 => [:nonproperty, :soft_dotted]
include_examples 'parse', '\P{sOfT_dOtTeD}', 0 => [:nonproperty, :soft_dotted]

# double negation is positive again
include_examples 'parse', '\P{^sd}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\P{^SD}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\P{^Soft Dotted}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\P{^Soft-Dotted}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\P{^sOfT_dOtTeD}', 0 => [:property, :soft_dotted, negative?: false]
include_examples 'parse', '\P{^sd}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\P{^SD}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\P{^Soft Dotted}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\P{^Soft-Dotted}', 0 => [:property, :soft_dotted]
include_examples 'parse', '\P{^sOfT_dOtTeD}', 0 => [:property, :soft_dotted]

# test #shortcut
include_examples 'parse', '\p{soft_dotted}', 0 => [:property, :soft_dotted, shortcut: 'sd']
Expand Down
2 changes: 1 addition & 1 deletion spec/parser/set/intersections_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
[0, 0, 0] => [CharacterSet::IntersectedSequence, count: 1],
[0, 0, 0, 0] => [CharacterSet::Range, count: 2],
[0, 0, 1] => [CharacterSet::IntersectedSequence, count: 1],
[0, 0, 1, 0] => [CharacterSet, count: 1, negative?: true]
[0, 0, 1, 0] => [CharacterSet, count: 1]

include_examples 'parse', /[a&&a-z]/,
[0] => [CharacterSet, count: 1],
Expand Down
6 changes: 3 additions & 3 deletions spec/parser/sets_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@
[0, 1, 1, 0] => [:literal, :literal, Literal, text: 'c', set_level: 3]

include_examples 'parse', '[a[^b[c]]]',
[0] => [:set, :character, CharacterSet, text: '[', count: 2, set_level: 0, negative?: false],
[0] => [:set, :character, CharacterSet, text: '[', count: 2, set_level: 0],
[0, 0] => [:literal, :literal, Literal, text: 'a', set_level: 1],
[0, 1] => [:set, :character, CharacterSet, text: '[', count: 2, set_level: 1, negative?: true],
[0, 1] => [:set, :character, CharacterSet, text: '[', count: 2, set_level: 1],
[0, 1, 0] => [:literal, :literal, Literal, text: 'b', set_level: 2],
[0, 1, 1] => [:set, :character, CharacterSet, text: '[', count: 1, set_level: 2, negative?: false],
[0, 1, 1] => [:set, :character, CharacterSet, text: '[', count: 1, set_level: 2],
[0, 1, 1, 0] => [:literal, :literal, Literal, text: 'c', set_level: 3]

include_examples 'parse', '[aaa]',
Expand Down

0 comments on commit be5ce5b

Please sign in to comment.