Best Gherkin-php code snippet using TokenMatcher
MatcherFactory.php
Source:MatcherFactory.php  
...58	 * @return Matcher59	 */60	public function comma() {61		if ( !isset( $this->cache[__METHOD__] ) ) {62			$this->cache[__METHOD__] = new TokenMatcher( Token::T_COMMA );63		}64		return $this->cache[__METHOD__];65	}66	/**67	 * Matcher for an arbitrary identifier68	 * @return Matcher69	 */70	public function ident() {71		if ( !isset( $this->cache[__METHOD__] ) ) {72			$this->cache[__METHOD__] = new TokenMatcher( Token::T_IDENT );73		}74		return $this->cache[__METHOD__];75	}76	/**77	 * Matcher for a string78	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#strings79	 * @warning If the string will be used as a URL, use self::urlstring() instead.80	 * @return Matcher81	 */82	public function string() {83		if ( !isset( $this->cache[__METHOD__] ) ) {84			$this->cache[__METHOD__] = new TokenMatcher( Token::T_STRING );85		}86		return $this->cache[__METHOD__];87	}88	/**89	 * Matcher for a string containing a URL90	 * @param string $type Type of resource referenced, e.g. "image" or "audio".91	 *  Not used here, but might be used by a subclass to validate the URL more strictly.92	 * @return Matcher93	 */94	public function urlstring( $type ) {95		return $this->string();96	}97	/**98	 * Matcher for a URL99	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#urls100	 * @param string $type Type of resource referenced, e.g. "image" or "audio".101	 *  Not used here, but might be used by a subclass to validate the URL more strictly.102	 * @return Matcher103	 */104	public function url( $type ) {105		if ( !isset( $this->cache[__METHOD__] ) ) {106			$this->cache[__METHOD__] = new UrlMatcher();107		}108		return $this->cache[__METHOD__];109	}110	/**111	 * CSS-wide value keywords112	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#common-keywords113	 * @return Matcher114	 */115	public function cssWideKeywords() {116		if ( !isset( $this->cache[__METHOD__] ) ) {117			$this->cache[__METHOD__] = new KeywordMatcher( [ 'initial', 'inherit', 'unset' ] );118		}119		return $this->cache[__METHOD__];120	}121	/**122	 * Add calc() support to a basic type matcher123	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#calc-notation124	 * @param Matcher $typeMatcher Matcher for the type125	 * @param string $type Type being matched126	 * @return Matcher127	 */128	public function calc( Matcher $typeMatcher, $type ) {129		if ( $type === 'integer' ) {130			$num = $this->rawInteger();131		} else {132			$num = $this->rawNumber();133		}134		$ows = $this->optionalWhitespace();135		$ws = $this->significantWhitespace();136		// Definitions are recursive. This will be used by reference and later137		// will be replaced.138		$calcValue = new NothingMatcher();139		if ( $type === 'integer' ) {140			// Division will always resolve to a number, making the expression141			// invalid, so don't allow it.142			$calcProduct = new Juxtaposition( [143				&$calcValue,144				Quantifier::star( new Juxtaposition( [ $ows, new DelimMatcher( '*' ), $ows, &$calcValue ] ) )145			] );146		} else {147			$calcProduct = new Juxtaposition( [148				&$calcValue,149				Quantifier::star( new Alternative( [150					new Juxtaposition( [ $ows, new DelimMatcher( '*' ), $ows, &$calcValue ] ),151					new Juxtaposition( [ $ows, new DelimMatcher( '/' ), $ows, $this->rawNumber() ] ),152				] ) ),153			] );154		}155		$calcSum = new Juxtaposition( [156			$ows,157			$calcProduct,158			Quantifier::star( new Juxtaposition( [159				$ws, new DelimMatcher( [ '+', '-' ] ), $ws, $calcProduct160			] ) ),161			$ows,162		] );163		$calcFunc = new FunctionMatcher( 'calc', $calcSum );164		if ( $num === $typeMatcher ) {165			$calcValue = new Alternative( [166				$typeMatcher,167				new BlockMatcher( Token::T_LEFT_PAREN, $calcSum ),168				$calcFunc,169			] );170		} else {171			$calcValue = new Alternative( [172				$num,173				$typeMatcher,174				new BlockMatcher( Token::T_LEFT_PAREN, $calcSum ),175				$calcFunc,176			] );177		}178		return new Alternative( [ $typeMatcher, $calcFunc ] );179	}180	/**181	 * Matcher for an integer value, without calc()182	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#integers183	 * @return Matcher184	 */185	protected function rawInteger() {186		if ( !isset( $this->cache[__METHOD__] ) ) {187			$this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {188				// The spec says it must match /^[+-]\d+$/, but the tokenizer189				// should have marked any other number token as a 'number'190				// anyway so let's not bother checking.191				return $t->typeFlag() === 'integer';192			} );193		}194		return $this->cache[__METHOD__];195	}196	/**197	 * Matcher for an integer value198	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#integers199	 * @return Matcher200	 */201	public function integer() {202		if ( !isset( $this->cache[__METHOD__] ) ) {203			$this->cache[__METHOD__] = $this->calc( $this->rawInteger(), 'integer' );204		}205		return $this->cache[__METHOD__];206	}207	/**208	 * Matcher for a real number, without calc()209	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#numbers210	 * @return Matcher211	 */212	public function rawNumber() {213		if ( !isset( $this->cache[__METHOD__] ) ) {214			$this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER );215		}216		return $this->cache[__METHOD__];217	}218	/**219	 * Matcher for a real number220	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#numbers221	 * @return Matcher222	 */223	public function number() {224		if ( !isset( $this->cache[__METHOD__] ) ) {225			$this->cache[__METHOD__] = $this->calc( $this->rawNumber(), 'number' );226		}227		return $this->cache[__METHOD__];228	}229	/**230	 * Matcher for a percentage value, without calc()231	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#percentages232	 * @return Matcher233	 */234	public function rawPercentage() {235		if ( !isset( $this->cache[__METHOD__] ) ) {236			$this->cache[__METHOD__] = new TokenMatcher( Token::T_PERCENTAGE );237		}238		return $this->cache[__METHOD__];239	}240	/**241	 * Matcher for a percentage value242	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#percentages243	 * @return Matcher244	 */245	public function percentage() {246		if ( !isset( $this->cache[__METHOD__] ) ) {247			$this->cache[__METHOD__] = $this->calc( $this->rawPercentage(), 'percentage' );248		}249		return $this->cache[__METHOD__];250	}251	/**252	 * Matcher for a length-percentage value253	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#typedef-length-percentage254	 * @return Matcher255	 */256	public function lengthPercentage() {257		if ( !isset( $this->cache[__METHOD__] ) ) {258			$this->cache[__METHOD__] = $this->calc(259				new Alternative( [ $this->rawLength(), $this->rawPercentage() ] ),260				'length'261			);262		}263		return $this->cache[__METHOD__];264	}265	/**266	 * Matcher for a frequency-percentage value267	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#typedef-frequency-percentage268	 * @return Matcher269	 */270	public function frequencyPercentage() {271		if ( !isset( $this->cache[__METHOD__] ) ) {272			$this->cache[__METHOD__] = $this->calc(273				new Alternative( [ $this->rawFrequency(), $this->rawPercentage() ] ),274				'frequency'275			);276		}277		return $this->cache[__METHOD__];278	}279	/**280	 * Matcher for a angle-percentage value281	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#typedef-angle-percentage282	 * @return Matcher283	 */284	public function anglePercentage() {285		if ( !isset( $this->cache[__METHOD__] ) ) {286			$this->cache[__METHOD__] = $this->calc(287				new Alternative( [ $this->rawAngle(), $this->rawPercentage() ] ),288				'angle'289			);290		}291		return $this->cache[__METHOD__];292	}293	/**294	 * Matcher for a time-percentage value295	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#typedef-time-percentage296	 * @return Matcher297	 */298	public function timePercentage() {299		if ( !isset( $this->cache[__METHOD__] ) ) {300			$this->cache[__METHOD__] = $this->calc(301				new Alternative( [ $this->rawTime(), $this->rawPercentage() ] ),302				'time'303			);304		}305		return $this->cache[__METHOD__];306	}307	/**308	 * Matcher for a number-percentage value309	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#typedef-number-percentage310	 * @return Matcher311	 */312	public function numberPercentage() {313		if ( !isset( $this->cache[__METHOD__] ) ) {314			$this->cache[__METHOD__] = $this->calc(315				new Alternative( [ $this->rawNumber(), $this->rawPercentage() ] ),316				'number'317			);318		}319		return $this->cache[__METHOD__];320	}321	/**322	 * Matcher for a dimension value323	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#dimensions324	 * @return Matcher325	 */326	public function dimension() {327		if ( !isset( $this->cache[__METHOD__] ) ) {328			$this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION );329		}330		return $this->cache[__METHOD__];331	}332	/**333	 * Matches the number 0334	 * @return Matcher335	 */336	protected function zero() {337		if ( !isset( $this->cache[__METHOD__] ) ) {338			$this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {339				return $t->value() === 0 || $t->value() === 0.0;340			} );341		}342		return $this->cache[__METHOD__];343	}344	/**345	 * Matcher for a length value, without calc()346	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#lengths347	 * @return Matcher348	 */349	protected function rawLength() {350		if ( !isset( $this->cache[__METHOD__] ) ) {351			$unitsRe = '/^(' . join( '|', self::$lengthUnits ) . ')$/i';352			$this->cache[__METHOD__] = new Alternative( [353				$this->zero(),354				new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) use ( $unitsRe ) {355					return preg_match( $unitsRe, $t->unit() );356				} ),357			] );358		}359		return $this->cache[__METHOD__];360	}361	/**362	 * Matcher for a length value363	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#lengths364	 * @return Matcher365	 */366	public function length() {367		if ( !isset( $this->cache[__METHOD__] ) ) {368			$this->cache[__METHOD__] = $this->calc( $this->rawLength(), 'length' );369		}370		return $this->cache[__METHOD__];371	}372	/**373	 * Matcher for an angle value, without calc()374	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#angles375	 * @return Matcher376	 */377	protected function rawAngle() {378		if ( !isset( $this->cache[__METHOD__] ) ) {379			$unitsRe = '/^(' . join( '|', self::$angleUnits ) . ')$/i';380			$this->cache[__METHOD__] = new Alternative( [381				$this->zero(),382				new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) use ( $unitsRe ) {383					return preg_match( $unitsRe, $t->unit() );384				} ),385			] );386		}387		return $this->cache[__METHOD__];388	}389	/**390	 * Matcher for an angle value391	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#angles392	 * @return Matcher393	 */394	public function angle() {395		if ( !isset( $this->cache[__METHOD__] ) ) {396			$this->cache[__METHOD__] = $this->calc( $this->rawAngle(), 'angle' );397		}398		return $this->cache[__METHOD__];399	}400	/**401	 * Matcher for a duration (time) value, without calc()402	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#time403	 * @return Matcher404	 */405	protected function rawTime() {406		if ( !isset( $this->cache[__METHOD__] ) ) {407			$unitsRe = '/^(' . join( '|', self::$timeUnits ) . ')$/i';408			$this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION,409				function ( Token $t ) use ( $unitsRe ) {410					return preg_match( $unitsRe, $t->unit() );411				}412			);413		}414		return $this->cache[__METHOD__];415	}416	/**417	 * Matcher for a duration (time) value418	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#time419	 * @return Matcher420	 */421	public function time() {422		if ( !isset( $this->cache[__METHOD__] ) ) {423			$this->cache[__METHOD__] = $this->calc( $this->rawTime(), 'time' );424		}425		return $this->cache[__METHOD__];426	}427	/**428	 * Matcher for a frequency value, without calc()429	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#frequency430	 * @return Matcher431	 */432	protected function rawFrequency() {433		if ( !isset( $this->cache[__METHOD__] ) ) {434			$unitsRe = '/^(' . join( '|', self::$frequencyUnits ) . ')$/i';435			$this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION,436				function ( Token $t ) use ( $unitsRe ) {437					return preg_match( $unitsRe, $t->unit() );438				}439			);440		}441		return $this->cache[__METHOD__];442	}443	/**444	 * Matcher for a frequency value445	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#frequency446	 * @return Matcher447	 */448	public function frequency() {449		if ( !isset( $this->cache[__METHOD__] ) ) {450			$this->cache[__METHOD__] = $this->calc( $this->rawFrequency(), 'frequency' );451		}452		return $this->cache[__METHOD__];453	}454	/**455	 * Matcher for a resolution value456	 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#resolution457	 * @return Matcher458	 */459	public function resolution() {460		if ( !isset( $this->cache[__METHOD__] ) ) {461			$this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) {462				return preg_match( '/^(dpi|dpcm|dppx)$/i', $t->unit() );463			} );464		}465		return $this->cache[__METHOD__];466	}467	/**468	 * Matchers for color functions469	 * @return Matcher[]470	 */471	protected function colorFuncs() {472		if ( !isset( $this->cache[__METHOD__] ) ) {473			$i = $this->integer();474			$n = $this->number();475			$p = $this->percentage();476			$this->cache[__METHOD__] = [477				new FunctionMatcher( 'rgb', new Alternative( [478					Quantifier::hash( $i, 3, 3 ),479					Quantifier::hash( $p, 3, 3 ),480				] ) ),481				new FunctionMatcher( 'rgba', new Alternative( [482					new Juxtaposition( [ $i, $i, $i, $n ], true ),483					new Juxtaposition( [ $p, $p, $p, $n ], true ),484				] ) ),485				new FunctionMatcher( 'hsl', new Juxtaposition( [ $n, $p, $p ], true ) ),486				new FunctionMatcher( 'hsla', new Juxtaposition( [ $n, $p, $p, $n ], true ) ),487			];488		}489		return $this->cache[__METHOD__];490	}491	/**492	 * Matcher for a color value493	 * @see https://www.w3.org/TR/2011/REC-css3-color-20110607/#colorunits494	 * @return Matcher495	 */496	public function color() {497		if ( !isset( $this->cache[__METHOD__] ) ) {498			$this->cache[__METHOD__] = new Alternative( array_merge( [499				new KeywordMatcher( [500					// Basic colors501					'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',502					'lime', 'maroon', 'navy', 'olive', 'purple', 'red',503					'silver', 'teal', 'white', 'yellow',504					// Extended colors505					'aliceblue', 'antiquewhite', 'aquamarine', 'azure',506					'beige', 'bisque', 'blanchedalmond', 'blueviolet', 'brown',507					'burlywood', 'cadetblue', 'chartreuse', 'chocolate',508					'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan',509					'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray',510					'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta',511					'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',512					'darksalmon', 'darkseagreen', 'darkslateblue',513					'darkslategray', 'darkslategrey', 'darkturquoise',514					'darkviolet', 'deeppink', 'deepskyblue', 'dimgray',515					'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite',516					'forestgreen', 'gainsboro', 'ghostwhite', 'gold',517					'goldenrod', 'greenyellow', 'grey', 'honeydew', 'hotpink',518					'indianred', 'indigo', 'ivory', 'khaki', 'lavender',519					'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue',520					'lightcoral', 'lightcyan', 'lightgoldenrodyellow',521					'lightgray', 'lightgreen', 'lightgrey', 'lightpink',522					'lightsalmon', 'lightseagreen', 'lightskyblue',523					'lightslategray', 'lightslategrey', 'lightsteelblue',524					'lightyellow', 'limegreen', 'linen', 'magenta',525					'mediumaquamarine', 'mediumblue', 'mediumorchid',526					'mediumpurple', 'mediumseagreen', 'mediumslateblue',527					'mediumspringgreen', 'mediumturquoise', 'mediumvioletred',528					'midnightblue', 'mintcream', 'mistyrose', 'moccasin',529					'navajowhite', 'oldlace', 'olivedrab', 'orange',530					'orangered', 'orchid', 'palegoldenrod', 'palegreen',531					'paleturquoise', 'palevioletred', 'papayawhip',532					'peachpuff', 'peru', 'pink', 'plum', 'powderblue',533					'rosybrown', 'royalblue', 'saddlebrown', 'salmon',534					'sandybrown', 'seagreen', 'seashell', 'sienna', 'skyblue',535					'slateblue', 'slategray', 'slategrey', 'snow',536					'springgreen', 'steelblue', 'tan', 'thistle', 'tomato',537					'turquoise', 'violet', 'wheat', 'whitesmoke',538					'yellowgreen',539					// Other keywords. Intentionally omitting the deprecated system colors.540					'transparent', 'currentColor',541				] ),542				new TokenMatcher( Token::T_HASH, function ( Token $t ) {543					return preg_match( '/^([0-9a-f]{3}|[0-9a-f]{6})$/i', $t->value() );544				} ),545			], $this->colorFuncs() ) );546		}547		return $this->cache[__METHOD__];548	}549	/**550	 * Matcher for an image value551	 * @see https://www.w3.org/TR/2012/CR-css3-images-20120417/#image-values552	 * @return Matcher553	 */554	public function image() {555		if ( !isset( $this->cache[__METHOD__] ) ) {556			// https://www.w3.org/TR/2012/CR-css3-images-20120417/#image-list-type557			// Note the undefined <element-reference> production has been dropped from the Editor's Draft.558			$imageDecl = new Alternative( [559				$this->url( 'image' ),560				$this->urlstring( 'image' ),561			] );562			// https://www.w3.org/TR/2012/CR-css3-images-20120417/#gradients563			$c = $this->comma();564			$colorStops = Quantifier::hash( new Juxtaposition( [565				$this->color(),566				// Not really <length-percentage>, but grammatically the same567				Quantifier::optional( $this->lengthPercentage() ),568			] ), 2, INF );569			$atPosition = new Juxtaposition( [ new KeywordMatcher( 'at' ), $this->position() ] );570			$linearGradient = new Juxtaposition( [571				Quantifier::optional( new Juxtaposition( [572					new Alternative( [573						$this->angle(),574						new Juxtaposition( [ new KeywordMatcher( 'to' ), UnorderedGroup::someOf( [575							new KeywordMatcher( [ 'left', 'right' ] ),576							new KeywordMatcher( [ 'top', 'bottom' ] ),577						] ) ] )578					] ),579					$c580				] ) ),581				$colorStops,582			] );583			$radialGradient = new Juxtaposition( [584				Quantifier::optional( new Juxtaposition( [585					new Alternative( [586						new Juxtaposition( [587							new Alternative( [588								UnorderedGroup::someOf( [ new KeywordMatcher( 'circle' ), $this->length() ] ),589								UnorderedGroup::someOf( [590									new KeywordMatcher( 'ellipse' ),591									// Not really <length-percentage>, but grammatically the same592									Quantifier::count( $this->lengthPercentage(), 2, 2 )593								] ),594								UnorderedGroup::someOf( [595									new KeywordMatcher( [ 'circle', 'ellipse' ] ),596									new KeywordMatcher( [597										'closest-side', 'farthest-side', 'closest-corner', 'farthest-corner'598									] ),599								] ),600							] ),601							Quantifier::optional( $atPosition ),602						] ),603						$atPosition604					] ),605					$c606				] ) ),607				$colorStops,608			] );609			// Putting it all together610			$this->cache[__METHOD__] = new Alternative( [611				$this->url( 'image' ),612				new FunctionMatcher( 'image', new Juxtaposition( [613					Quantifier::star( new Juxtaposition( [ $imageDecl, $c ] ) ),614					new Alternative( [ $imageDecl, $this->color() ] ),615				] ) ),616				new FunctionMatcher( 'linear-gradient', $linearGradient ),617				new FunctionMatcher( 'radial-gradient', $radialGradient ),618				new FunctionMatcher( 'repeating-linear-gradient', $linearGradient ),619				new FunctionMatcher( 'repeating-radial-gradient', $radialGradient ),620			] );621		}622		return $this->cache[__METHOD__];623	}624	/**625	 * Matcher for a position value626	 * @see https://www.w3.org/TR/2014/CR-css3-background-20140909/#ltpositiongt627	 * @return Matcher628	 */629	public function position() {630		if ( !isset( $this->cache[__METHOD__] ) ) {631			$lp = $this->lengthPercentage();632			$olp = Quantifier::optional( $lp );633			$center = new KeywordMatcher( 'center' );634			$leftRight = new KeywordMatcher( [ 'left', 'right' ] );635			$topBottom = new KeywordMatcher( [ 'top', 'bottom' ] );636			$this->cache[__METHOD__] = new Alternative( [637				new Alternative( [ $center, $leftRight, $topBottom, $lp ] ),638				new Juxtaposition( [639					new Alternative( [ $center, $leftRight, $lp ] ),640					new Alternative( [ $center, $topBottom, $lp ] ),641				] ),642				UnorderedGroup::allOf( [643					new Alternative( [ $center, new Juxtaposition( [ $leftRight, $olp ] ) ] ),644					new Alternative( [ $center, new Juxtaposition( [ $topBottom, $olp ] ) ] ),645				] ),646			] );647		}648		return $this->cache[__METHOD__];649	}650	/**651	 * Matcher for a CSS media query652	 * @see https://www.w3.org/TR/2016/WD-mediaqueries-4-20160706/#mq-syntax653	 * @param bool $strict Only allow defined query types654	 * @return Matcher655	 */656	public function cssMediaQuery( $strict = true ) {657		$key = __METHOD__ . ':' . ( $strict ? 'strict' : 'unstrict' );658		if ( !isset( $this->cache[$key] ) ) {659			if ( $strict ) {660				$generalEnclosed = new NothingMatcher();661				$mediaType = new KeywordMatcher( [662					'all', 'print', 'screen', 'speech',663					// deprecated664					'tty', 'tv', 'projection', 'handheld', 'braille', 'embossed', 'aural'665				] );666				$rangeFeatures = [667					'width', 'height', 'aspect-ratio', 'resolution', 'color', 'color-index', 'monochrome',668					// deprecated669					'device-width', 'device-height', 'device-aspect-ratio'670				];671				$discreteFeatures = [672					'orientation', 'scan', 'grid', 'update', 'overflow-block', 'overflow-inline', 'color-gamut',673					'pointer', 'hover', 'any-pointer', 'any-hover', 'scripting'674				];675				$mfName = new KeywordMatcher( array_merge(676					$rangeFeatures,677					array_map( function ( $f ) {678						return "min-$f";679					}, $rangeFeatures ),680					array_map( function ( $f ) {681						return "max-$f";682					}, $rangeFeatures ),683					$discreteFeatures684				) );685			} else {686				$anythingPlus = new AnythingMatcher( [ 'quantifier' => '+' ] );687				$generalEnclosed = new Alternative( [688					new FunctionMatcher( null, $anythingPlus ),689					new BlockMatcher( Token::T_LEFT_PAREN,690						new Juxtaposition( [ $this->ident(), $anythingPlus ] )691					),692				] );693				$mediaType = $this->ident();694				$mfName = $this->ident();695			}696			$posInt = $this->calc(697				new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {698					return $t->typeFlag() === 'integer' && preg_match( '/^\+?\d+$/', $t->representation() );699				} ),700				'integer'701			);702			$eq = new DelimMatcher( '=' );703			$oeq = Quantifier::optional( new Juxtaposition( [ new NoWhitespace, $eq ] ) );704			$ltgteq = Quantifier::optional( new Alternative( [705				$eq,706				new Juxtaposition( [ new DelimMatcher( [ '<', '>' ] ), $oeq ] ),707			] ) );708			$lteq = new Juxtaposition( [ new DelimMatcher( '<' ), $oeq ] );709			$gteq = new Juxtaposition( [ new DelimMatcher( '>' ), $oeq ] );710			$mfValue = new Alternative( [711				$this->number(),712				$this->dimension(),713				$this->ident(),714				new Juxtaposition( [ $posInt, new DelimMatcher( '/' ), $posInt ] ),715			] );716			$mediaInParens = new NothingMatcher(); // temporary717			$mediaNot = new Juxtaposition( [ new KeywordMatcher( 'not' ), &$mediaInParens ] );718			$mediaAnd = new Juxtaposition( [719				&$mediaInParens,720				Quantifier::plus( new Juxtaposition( [ new KeywordMatcher( 'and' ), &$mediaInParens ] ) )721			] );722			$mediaOr = new Juxtaposition( [723				&$mediaInParens,724				Quantifier::plus( new Juxtaposition( [ new KeywordMatcher( 'or' ), &$mediaInParens ] ) )725			] );726			$mediaCondition = new Alternative( [ $mediaNot, $mediaAnd, $mediaOr, &$mediaInParens ] );727			$mediaConditionWithoutOr = new Alternative( [ $mediaNot, $mediaAnd, &$mediaInParens ] );728			$mediaFeature = new BlockMatcher( Token::T_LEFT_PAREN, new Alternative( [729				new Juxtaposition( [ $mfName, new TokenMatcher( Token::T_COLON ), $mfValue ] ), // <mf-plain>730				$mfName, // <mf-boolean>731				new Juxtaposition( [ $mfName, $ltgteq, $mfValue ] ), // <mf-range>, 1st alternative732				new Juxtaposition( [ $mfValue, $ltgteq, $mfName ] ), // <mf-range>, 2nd alternative733				new Juxtaposition( [ $mfValue, $lteq, $mfName, $lteq, $mfValue ] ), // <mf-range>, 3rd alt734				new Juxtaposition( [ $mfValue, $gteq, $mfName, $gteq, $mfValue ] ), // <mf-range>, 4th alt735			] ) );736			$mediaInParens = new Alternative( [737				new BlockMatcher( Token::T_LEFT_PAREN, $mediaCondition ),738				$mediaFeature,739				$generalEnclosed,740			] );741			$this->cache[$key] = new Alternative( [742				$mediaCondition,743				new Juxtaposition( [744					Quantifier::optional( new KeywordMatcher( [ 'not', 'only' ] ) ),745					$mediaType,746					Quantifier::optional( new Juxtaposition( [747						new KeywordMatcher( 'and' ),748						$mediaConditionWithoutOr,749					] ) )750				] )751			] );752		}753		return $this->cache[$key];754	}755	/**756	 * Matcher for a CSS media query list757	 * @see https://www.w3.org/TR/2016/WD-mediaqueries-4-20160706/#mq-syntax758	 * @param bool $strict Only allow defined query types759	 * @return Matcher760	 */761	public function cssMediaQueryList( $strict = true ) {762		$key = __METHOD__ . ':' . ( $strict ? 'strict' : 'unstrict' );763		if ( !isset( $this->cache[$key] ) ) {764			$this->cache[$key] = Quantifier::hash( $this->cssMediaQuery( $strict ), 0, INF );765		}766		return $this->cache[$key];767	}768	/************************************************************************//**769	 * @name   CSS Selectors Level 3770	 * @{771	 *772	 * https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#w3cselgrammar773	 */774	/**775	 * List of selectors776	 *777	 *     selector [ COMMA S* selector ]*778	 *779	 * Capturing is set up for the `selector`s.780	 *781	 * @return Matcher782	 */783	public function cssSelectorList() {784		if ( !isset( $this->cache[__METHOD__] ) ) {785			// Technically the spec doesn't allow whitespace before the comma,786			// but I'd guess every browser does. So just use Quantifier::hash.787			$selector = $this->cssSelector()->capture( 'selector' );788			$this->cache[__METHOD__] = Quantifier::hash( $selector );789			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );790		}791		return $this->cache[__METHOD__];792	}793	/**794	 * A single selector795	 *796	 *     simple_selector_sequence [ combinator simple_selector_sequence ]*797	 *798	 * Capturing is set up for the `simple_selector_sequence`s (as 'simple') and `combinator`.799	 *800	 * @return Matcher801	 */802	public function cssSelector() {803		if ( !isset( $this->cache[__METHOD__] ) ) {804			$simple = $this->cssSimpleSelectorSeq()->capture( 'simple' );805			$this->cache[__METHOD__] = new Juxtaposition( [806				$simple,807				Quantifier::star( new Juxtaposition( [808					$this->cssCombinator()->capture( 'combinator' ),809					$simple,810				] ) )811			] );812			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );813		}814		return $this->cache[__METHOD__];815	}816	/**817	 * A CSS combinator818	 *819	 *     PLUS S* | GREATER S* | TILDE S* | S+820	 *821	 * (combinators can be surrounded by whitespace)822	 *823	 * @return Matcher824	 */825	public function cssCombinator() {826		if ( !isset( $this->cache[__METHOD__] ) ) {827			$this->cache[__METHOD__] = new Alternative( [828				new Juxtaposition( [829					$this->optionalWhitespace(),830					new DelimMatcher( [ '+', '>', '~' ] ),831					$this->optionalWhitespace(),832				] ),833				$this->significantWhitespace(),834			] );835			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );836		}837		return $this->cache[__METHOD__];838	}839	/**840	 * A simple selector sequence841	 *842	 *     [ type_selector | universal ]843	 *     [ HASH | class | attrib | pseudo | negation ]*844	 *     | [ HASH | class | attrib | pseudo | negation ]+845	 *846	 * The following captures are set:847	 *  - element: [ type_selector | universal ]848	 *  - id: HASH849	 *  - class: class850	 *  - attrib: attrib851	 *  - pseudo: pseudo852	 *  - negation: negation853	 *854	 * @return Matcher855	 */856	public function cssSimpleSelectorSeq() {857		if ( !isset( $this->cache[__METHOD__] ) ) {858			$hashEtc = new Alternative( [859				$this->cssID()->capture( 'id' ),860				$this->cssClass()->capture( 'class' ),861				$this->cssAttrib()->capture( 'attrib' ),862				$this->cssPseudo()->capture( 'pseudo' ),863				$this->cssNegation()->capture( 'negation' ),864			] );865			$this->cache[__METHOD__] = new Alternative( [866				new Juxtaposition( [867					Alternative::create( [868						$this->cssTypeSelector(),869						$this->cssUniversal(),870					] )->capture( 'element' ),871					Quantifier::star( $hashEtc )872				] ),873				Quantifier::plus( $hashEtc )874			] );875			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );876		}877		return $this->cache[__METHOD__];878	}879	/**880	 * A type selector (i.e. a tag name)881	 *882	 *     [ namespace_prefix ] ? element_name883	 *884	 * where element_name is885	 *886	 *     IDENT887	 *888	 * @return Matcher889	 */890	public function cssTypeSelector() {891		if ( !isset( $this->cache[__METHOD__] ) ) {892			$this->cache[__METHOD__] = new Juxtaposition( [893				$this->cssOptionalNamespacePrefix(),894				new TokenMatcher( Token::T_IDENT )895			] );896			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );897		}898		return $this->cache[__METHOD__];899	}900	/**901	 * A namespace prefix902	 *903	 *      [ IDENT | '*' ]? '|'904	 *905	 * @return Matcher906	 */907	public function cssNamespacePrefix() {908		if ( !isset( $this->cache[__METHOD__] ) ) {909			$this->cache[__METHOD__] = new Juxtaposition( [910				Quantifier::optional( new Alternative( [911					$this->ident(),912					new DelimMatcher( [ '*' ] ),913				] ) ),914				new DelimMatcher( [ '|' ] ),915			] );916			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );917		}918		return $this->cache[__METHOD__];919	}920	/**921	 * An optional namespace prefix922	 *923	 *     [ namespace_prefix ]?924	 *925	 * @return Matcher926	 */927	private function cssOptionalNamespacePrefix() {928		if ( !isset( $this->cache[__METHOD__] ) ) {929			$this->cache[__METHOD__] = Quantifier::optional( $this->cssNamespacePrefix() );930			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );931		}932		return $this->cache[__METHOD__];933	}934	/**935	 * The universal selector936	 *937	 *     [ namespace_prefix ]? '*'938	 *939	 * @return Matcher940	 */941	public function cssUniversal() {942		if ( !isset( $this->cache[__METHOD__] ) ) {943			$this->cache[__METHOD__] = new Juxtaposition( [944				$this->cssOptionalNamespacePrefix(),945				new DelimMatcher( [ '*' ] )946			] );947			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );948		}949		return $this->cache[__METHOD__];950	}951	/**952	 * An ID selector953	 *954	 *     HASH955	 *956	 * @return Matcher957	 */958	public function cssID() {959		if ( !isset( $this->cache[__METHOD__] ) ) {960			$this->cache[__METHOD__] = new TokenMatcher( Token::T_HASH, function ( Token $t ) {961				return $t->typeFlag() === 'id';962			} );963			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );964		}965		return $this->cache[__METHOD__];966	}967	/**968	 * A class selector969	 *970	 *     '.' IDENT971	 *972	 * @return Matcher973	 */974	public function cssClass() {975		if ( !isset( $this->cache[__METHOD__] ) ) {976			$this->cache[__METHOD__] = new Juxtaposition( [977				new DelimMatcher( [ '.' ] ),978				$this->ident()979			] );980			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );981		}982		return $this->cache[__METHOD__];983	}984	/**985	 * An attribute selector986	 *987	 *     '[' S* [ namespace_prefix ]? IDENT S*988	 *         [ [ PREFIXMATCH |989	 *             SUFFIXMATCH |990	 *             SUBSTRINGMATCH |991	 *             '=' |992	 *             INCLUDES |993	 *             DASHMATCH ] S* [ IDENT | STRING ] S*994	 *         ]? ']'995	 *996	 * Captures are set for the attribute, test, and value. Note that these997	 * captures will probably be relative to the contents of the SimpleBlock998	 * that this matcher matches!999	 *1000	 * @return Matcher1001	 */1002	public function cssAttrib() {1003		if ( !isset( $this->cache[__METHOD__] ) ) {1004			// An attribute is going to be parsed by the parser as a1005			// SimpleBlock, so that's what we need to look for here.1006			$this->cache[__METHOD__] = new BlockMatcher( Token::T_LEFT_BRACKET,1007				new Juxtaposition( [1008					$this->optionalWhitespace(),1009					Juxtaposition::create( [1010						$this->cssOptionalNamespacePrefix(),1011						$this->ident(),1012					] )->capture( 'attribute' ),1013					$this->optionalWhitespace(),1014					Quantifier::optional( new Juxtaposition( [1015						Alternative::create( [1016							new TokenMatcher( Token::T_PREFIX_MATCH ),1017							new TokenMatcher( Token::T_SUFFIX_MATCH ),1018							new TokenMatcher( Token::T_SUBSTRING_MATCH ),1019							new DelimMatcher( [ '=' ] ),1020							new TokenMatcher( Token::T_INCLUDE_MATCH ),1021							new TokenMatcher( Token::T_DASH_MATCH ),1022						] )->capture( 'test' ),1023						$this->optionalWhitespace(),1024						Alternative::create( [1025							$this->ident(),1026							$this->string(),1027						] )->capture( 'value' ),1028						$this->optionalWhitespace(),1029					] ) ),1030				] )1031			);1032			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );1033		}1034		return $this->cache[__METHOD__];1035	}1036	/**1037	 * A pseudo-class or pseudo-element1038	 *1039	 *     ':' ':'? [ IDENT | functional_pseudo ]1040	 *1041	 * Although this actually only matches the pseudo-selectors defined in the1042	 * following sources:1043	 * - https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#pseudo-classes1044	 * - https://www.w3.org/TR/2016/WD-css-pseudo-4-20160607/1045	 *1046	 * @return Matcher1047	 */1048	public function cssPseudo() {1049		if ( !isset( $this->cache[__METHOD__] ) ) {1050			$colon = new TokenMatcher( Token::T_COLON );1051			$ows = $this->optionalWhitespace();1052			$anplusb = new Juxtaposition( [ $ows, $this->cssANplusB(), $ows ] );1053			$this->cache[__METHOD__] = new Alternative( [1054				new Juxtaposition( [1055					$colon,1056					new Alternative( [1057						new KeywordMatcher( [1058							'link', 'visited', 'hover', 'active', 'focus', 'target', 'enabled', 'disabled', 'checked',1059							'indeterminate', 'root', 'first-child', 'last-child', 'first-of-type',1060							'last-of-type', 'only-child', 'only-of-type', 'empty',1061							// CSS2-compat elements with class syntax1062							'first-line', 'first-letter', 'before', 'after',1063						] ),1064						new FunctionMatcher( 'lang', new Juxtaposition( [ $ows, $this->ident(), $ows ] ) ),1065						new FunctionMatcher( 'nth-child', $anplusb ),1066						new FunctionMatcher( 'nth-last-child', $anplusb ),1067						new FunctionMatcher( 'nth-of-type', $anplusb ),1068						new FunctionMatcher( 'nth-last-of-type', $anplusb ),1069					] ),1070				] ),1071				new Juxtaposition( [1072					$colon,1073					$colon,1074					new KeywordMatcher( [1075						'first-line', 'first-letter', 'before', 'after', 'selection', 'inactive-selection',1076						'spelling-error', 'grammar-error', 'placeholder'1077					] ),1078				] ),1079			] );1080			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );1081		}1082		return $this->cache[__METHOD__];1083	}1084	/**1085	 * An "AN+B" form1086	 *1087	 * https://www.w3.org/TR/2014/CR-css-syntax-3-20140220/#anb1088	 *1089	 * @return Matcher1090	 */1091	public function cssANplusB() {1092		if ( !isset( $this->cache[__METHOD__] ) ) {1093			// Quoth the spec:1094			//  > The An+B notation was originally defined using a slightly1095			//  > different tokenizer than the rest of CSS, resulting in a1096			//  > somewhat odd definition when expressed in terms of CSS tokens.1097			// That's a bit of an understatement1098			$plus = new DelimMatcher( [ '+' ] );1099			$plusQ = Quantifier::optional( new DelimMatcher( [ '+' ] ) );1100			$n = new KeywordMatcher( [ 'n' ] );1101			$dashN = new KeywordMatcher( [ '-n' ] );1102			$nDash = new KeywordMatcher( [ 'n-' ] );1103			$plusQN = new Juxtaposition( [ $plusQ, $n ] );1104			$plusQNDash = new Juxtaposition( [ $plusQ, $nDash ] );1105			$nDimension = new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) {1106				return $t->typeFlag() === 'integer' && !strcasecmp( $t->unit(), 'n' );1107			} );1108			$nDashDimension = new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) {1109				return $t->typeFlag() === 'integer' && !strcasecmp( $t->unit(), 'n-' );1110			} );1111			$nDashDigitDimension = new TokenMatcher( Token::T_DIMENSION, function ( Token $t ) {1112				return $t->typeFlag() === 'integer' && preg_match( '/^n-\d+$/i', $t->unit() );1113			} );1114			$nDashDigitIdent = new TokenMatcher( Token::T_IDENT, function ( Token $t ) {1115				return preg_match( '/^n-\d+$/i', $t->value() );1116			} );1117			$dashNDashDigitIdent = new TokenMatcher( Token::T_IDENT, function ( Token $t ) {1118				return preg_match( '/^-n-\d+$/i', $t->value() );1119			} );1120			$signedInt = new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {1121				return $t->typeFlag() === 'integer' && preg_match( '/^[+-]/', $t->representation() );1122			} );1123			$signlessInt = new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {1124				return $t->typeFlag() === 'integer' && preg_match( '/^\d/', $t->representation() );1125			} );1126			$plusOrMinus = new DelimMatcher( [ '+', '-' ] );1127			$S = $this->optionalWhitespace();1128			$this->cache[__METHOD__] = new Alternative( [1129				new KeywordMatcher( [ 'odd', 'even' ] ),1130				new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {1131					return $t->typeFlag() === 'integer';1132				} ),1133				$nDimension,1134				$plusQN,1135				$dashN,1136				$nDashDigitDimension,1137				new Juxtaposition( [ $plusQ, $nDashDigitIdent ] ),1138				$dashNDashDigitIdent,1139				new Juxtaposition( [ $nDimension, $S, $signedInt ] ),1140				new Juxtaposition( [ $plusQN, $S, $signedInt ] ),1141				new Juxtaposition( [ $dashN, $S, $signedInt ] ),1142				new Juxtaposition( [ $nDashDimension, $S, $signlessInt ] ),1143				new Juxtaposition( [ $plusQNDash, $S, $signlessInt ] ),1144				new Juxtaposition( [ new KeywordMatcher( [ '-n-' ] ), $S, $signlessInt ] ),1145				new Juxtaposition( [ $nDimension, $S, $plusOrMinus, $S, $signlessInt ] ),1146				new Juxtaposition( [ $plusQN, $S, $plusOrMinus, $S, $signlessInt ] ),1147				new Juxtaposition( [ $dashN, $S, $plusOrMinus, $S, $signlessInt ] )1148			] );1149			$this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] );1150		}1151		return $this->cache[__METHOD__];1152	}1153	/**1154	 * A negation1155	 *1156	 *     ':' not( S* [ type_selector | universal | HASH | class | attrib | pseudo ] S* ')'1157	 *1158	 * @return Matcher1159	 */1160	public function cssNegation() {1161		if ( !isset( $this->cache[__METHOD__] ) ) {1162			// A negation is going to be parsed by the parser as a colon1163			// followed by a CSSFunction, so that's what we need to look for1164			// here.1165			$this->cache[__METHOD__] = new Juxtaposition( [1166				new TokenMatcher( Token::T_COLON ),1167				new FunctionMatcher( 'not',1168					new Juxtaposition( [1169						$this->optionalWhitespace(),1170						new Alternative( [1171							$this->cssTypeSelector(),1172							$this->cssUniversal(),1173							$this->cssID(),1174							$this->cssClass(),1175							$this->cssAttrib(),1176							$this->cssPseudo(),1177						] ),1178						$this->optionalWhitespace(),1179					] )1180				)...Parser.php
Source:Parser.php  
...22     */23    public function parse(string $json): void24    {25        $buffer = Unicode\CharBufferFactory::createFromString($json);26        $lexer = new Lexer\TokenReader($buffer, $this->getTokenMatcher(), $this->getTokenFactory());27        $parser = new LL1\Parser($this->getGrammar(), $lexer, $this->getTranslator());28        try {29            $parser->run();30        } catch (\Throwable $e) {31            throw new Exception("Failed to parse JSON", 0, $e);32        }33    }34    /**35     * @return ContextFree\Grammar36     * @throws Exception37     */38    private function getGrammar(): ContextFree\Grammar39    {40        if (!isset($this->grammar)) {41            try {42                $this->grammar = ContextFree\GrammarLoader::loadFile(__DIR__ . "/../spec/GrammarSpec.php");43            } catch (\Throwable $e) {44                throw new Exception("Failed to load JSON grammar specification", 0, $e);45            }46        }47        return $this->grammar;48    }49    private function getTokenMatcher(): Lexer\TokenMatcherInterface50    {51        if (!isset($this->tokenMatcher)) {52            $this->tokenMatcher = new TokenMatcher;53        }54        return $this->tokenMatcher;55    }56    /**57     * @return Lexer\TokenFactoryInterface58     * @throws Exception59     */60    private function getTokenFactory(): Lexer\TokenFactoryInterface61    {62        if (!isset($this->tokenFactory)) {63            $this->tokenFactory = new ContextFree\TokenFactory($this->getGrammar());64        }65        return $this->tokenFactory;66    }...TemplateStylesFontFaceAtRuleSanitizer.php
Source:TemplateStylesFontFaceAtRuleSanitizer.php  
...6use Wikimedia\CSS\Grammar\Alternative;7use Wikimedia\CSS\Grammar\Juxtaposition;8use Wikimedia\CSS\Grammar\MatcherFactory;9use Wikimedia\CSS\Grammar\Quantifier;10use Wikimedia\CSS\Grammar\TokenMatcher;11use Wikimedia\CSS\Objects\Token;12use Wikimedia\CSS\Sanitizer\FontFaceAtRuleSanitizer;13/**14 * Extend the standard `@font-face` matcher to require a prefix on families.15 */16class TemplateStylesFontFaceAtRuleSanitizer extends FontFaceAtRuleSanitizer {17	/**18	 * @param MatcherFactory $matcherFactory19	 */20	public function __construct( MatcherFactory $matcherFactory ) {21		parent::__construct( $matcherFactory );22		// Only allow the font-family if it begins with "TemplateStyles"23		$this->propertySanitizer->setKnownProperties( [24			'font-family' => new Alternative( [25				new TokenMatcher( Token::T_STRING, function ( Token $t ) {26					return substr( $t->value(), 0, 14 ) === 'TemplateStyles';27				} ),28				new Juxtaposition( [29					new TokenMatcher( Token::T_IDENT, function ( Token $t ) {30						return substr( $t->value(), 0, 14 ) === 'TemplateStyles';31					} ),32					Quantifier::star( $matcherFactory->ident() ),33				] ),34			] ),35		] + $this->propertySanitizer->getKnownProperties() );36	}37}...TokenMatcher
Using AI Code Generation
1require_once 'vendor/autoload.php';2use Behat\Gherkin\TokenMatcher;3use Behat\Gherkin\Keywords\KeywordsInterface;4use Behat\Gherkin\Keywords\ArrayKeywords;5use Behat\Gherkin\Token;6use Behat\Gherkin\Lexer;7use Behat\Gherkin\Parser;8use Behat\Gherkin\Node\ScenarioNode;9use Behat\Gherkin\Node\FeatureNode;10$keywords = new ArrayKeywords(array(11    'en' => array(12));13$lexer = new Lexer($keywords);14$parser = new Parser($keywords);15$feature = $parser->parse(16    $lexer->tokenize($featureText)17);18$feature->getFeature();19$feature->getDescription();20$feature->getTags();21$feature->getBackground();22$feature->getScenarios();23$scenario = $feature->getScenarios()[0];24$scenario->getTitle();25$scenario->getTags();26$scenario->getSteps();27$step = $scenario->getSteps()[0];28$step->getType();29$step->getText();30$step->getArguments();31$argument = $step->getArguments()[0];32$argument->getKeyword();33$argument->getRows();34$row = $argument->getRows()[0];35$row->getCells();36$cell = $row->getCells()[0];37$cell->getValue();38$keywords = $lexer->getKeywords();39$keywords->getKeywords();40$keywords->getKeyword('given');41$keywords->getKeywordType('Given');42$keywords->hasKeyword('given');43$keywords->hasKeywordType('Given');44$token = new Token(0, 'Given', 1);45$token->getLine();46$token->getRaw();47$token->getTrimmed();48$token->getIndent();49$token->getColumn();TokenMatcher
Using AI Code Generation
1require_once 'vendor/autoload.php';2use Behat\Gherkin\TokenMatcher;3use Behat\Gherkin\Lexer;4use Behat\Gherkin\Parser;5use Behat\Gherkin\Keywords\ArrayKeywords;6use Behat\Gherkin\Keywords\KeywordsInterface;7use Behat\Gherkin\Keywords\Keywords;8use Behat\Gherkin\Keywords\KeywordsArray;9$keywords = new KeywordsArray(array(10    'en' => array(11));12$lexer = new Lexer($keywords);13$parser = new Parser($lexer);14$feature = $parser->parse(file_get_contents('1.feature'));15$tokenMatcher = new TokenMatcher();16$tokens = $lexer->tokenize(file_get_contents('1.feature'));17foreach ($tokens as $token) {18    if ($tokenMatcher->isGivenKeyword($token)) {19        print $token->getLine()." ".$token->getKeyword()."20";21    }22}TokenMatcher
Using AI Code Generation
1$tokenMatcher = new \Behat\Gherkin\TokenMatcher();2$tokenMatcher->isStepKeyword("Given");3$tokenMatcher = new \Behat\Gherkin\TokenMatcher();4$tokenMatcher->isStepKeyword("Given");5$tokenMatcher = new \Behat\Gherkin\TokenMatcher();6$tokenMatcher->isStepKeyword("Given");7$tokenMatcher = new \Behat\Gherkin\TokenMatcher();8$tokenMatcher->isStepKeyword("Given");9$tokenMatcher = new \Behat\Gherkin\TokenMatcher();10$tokenMatcher->isStepKeyword("Given");11$tokenMatcher = new \Behat\Gherkin\TokenMatcher();12$tokenMatcher->isStepKeyword("Given");13unset($tokenMatcher::$keywords);TokenMatcher
Using AI Code Generation
1$gherkin = new \Behat\Gherkin\Gherkin();2$gherkin->registerLanguage('en', new \Behat\Gherkin\Language('en', 'en', array()));3$parser = new \Behat\Gherkin\Parser($gherkin);4$features = $parser->parse(file_get_contents('features/1.feature'));5$feature = $features[0];6$scenario = $feature->getScenarios()[0];7$steps = $scenario->getSteps();8$keywords = array();9foreach ($steps as $step) {10    $keywords[] = $step->getKeyword();11}12echo json_encode($keywords);13$gherkin = new \Behat\Gherkin\Gherkin();14$gherkin->registerLanguage('en', new \Behat\Gherkin\Language('en', 'en', array()));15$parser = new \Behat\Gherkin\Parser($gherkin);16$features = $parser->parse(file_get_contents('features/2.feature'));17$feature = $features[0];18$scenario = $feature->getScenarios()[0];19$steps = $scenario->getSteps();20$keywords = array();21foreach ($steps as $step) {22    $keywords[] = $step->getKeyword();23}24echo json_encode($keywords);25$gherkin = new \Behat\Gherkin\Gherkin();26$gherkin->registerLanguage('en', new \Behat\Gherkin\Language('en', 'en', array()));27$parser = new \Behat\Gherkin\Parser($gherkin);28$features = $parser->parse(file_get_contents('features/3.feature'));29$feature = $features[0];30$scenario = $feature->getScenarios()[0];31$steps = $scenario->getSteps();32$keywords = array();33foreach ($steps as $step) {34    $keywords[] = $step->getKeyword();35}36echo json_encode($keywords);37$gherkin = new \Behat\Gherkin\Gherkin();TokenMatcher
Using AI Code Generation
1$gherkin = new Gherkin\Gherkin();2$parser = $gherkin->getParser();3$matcher = new Gherkin\TokenMatcher();4$parser->match($matcher->EOF(), array(new Gherkin\Token('EOF', '')));5$gherkin = new Gherkin\Gherkin();6$parser = $gherkin->getParser();7$ast = $parser->parse($text);8$visitor = new MyVisitor();9$visitor->visit($ast);10Fatal error: Uncaught exception 'LogicException' with message 'Method visitFeature() must be defined in class MyVisitor' in C:\xampp\htdocs\gherkin\vendor\behat\gherkin\src\Gherkin\Ast\Visitor\AstNodeVisitor.php on line 5611$gherkin = new Gherkin\Gherkin();12$parser = $gherkin->getParser();TokenMatcher
Using AI Code Generation
1require_once 'vendor/autoload.php';2use Behat\Gherkin\TokenMatcher\TokenMatcher;3$matcher = new TokenMatcher();4echo $matcher->match('Given', 'Given');5echo $matcher->match('Given', 'When');6echo $matcher->match('Given', 'Then');7echo $matcher->match('When', 'When');8echo $matcher->match('When', 'Then');9echo $matcher->match('Then', 'Then');10echo $matcher->match('Then', 'Given');11echo $matcher->match('And', 'And');12echo $matcher->match('And', 'When');13echo $matcher->match('And', 'Then');14echo $matcher->match('But', 'But');15echo $matcher->match('But', 'When');16echo $matcher->match('But', 'Then');TokenMatcher
Using AI Code Generation
1$lexer = new Lexer();2$parser = new Parser($lexer);3$token_matcher = new TokenMatcher();4$token_matcher->isGivenTableNode($parser->parse($feature));5$lexer = new Lexer();6$parser = new Parser($lexer);7$token = new Token();8$token->isGivenTableNode($parser->parse($feature));Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Trigger Selenium automation tests on a cloud-based Grid of 3000+ real browsers and operating systems.
Test now for FreeGet 100 minutes of automation test minutes FREE!!
