Można analizować kod za pomocą wyrażeń regularnych?

0

Wyobraźcie sobie taki kod:

MAPA lokal_wyborczy
{
  OBIEKT skrzynka
  {
    x = 50; y = 20;
  }
}

MAPA studio_telewizyjne {}

Czy da się za pomocą wyrażeń regularnych wyciągnąć pierwszą mapę?

Oto przykłady:

bierze za dużo - połyka obie mapy
bierze za mało - od { mapy do } obiektu

Wyrażenia RegExp:

var re = /MAPA\s+[A-Za-z_]+\s+\{[^]*?\}/gim;  /* ze znakiem ? */
var re = /MAPA\s+[A-Za-z_]+\s+\{[^]*\}/gim;  /* bez znaku ? */

Czy nie obejdzie się z pisaniem parsera tradycyjnymi metodami, czyli dzieleniem całego ciągu znaków na elementy składniowe (czasami między identyfikatorem a { może nie być spacji!)?

1

Dlaczego nie chcesz tego po ludzku parsować?

0

Tradycyjna metoda - analiza leksykalna - analiza składniowa - jest bardziej niezawodna, ale wymaga pisania więcej kodu. Wyrażenia regularne w językach wysokiego poziomu dają dużo możliwości. Jednak tu pojawia się problem, który opisałem wyżej.

A jeśli chcę znaleźć w kodzie konkretną mapę i dopisać jej obiekty to bez sensu analizować cały kod.

3

W ciągu tych 40 minut od założenia wątku (a rozwiązania regexpami szukasz pewnie jeszcze dłużej) zdążyłbyś już napisać ten parser.
Policz, co Ci się bardziej opłaca.

0

nie bardzo,

chyba, żę masz PEWNOŚĆ, że słowo MAPA nie wystąpi nigdzie wewnątrz definicji mapy - to jeszcze od biedy byś coś napisał

0

W ciągu 40 minut z użyciem ANTLR może tak, ale chcę napisać od podstaw. Wyodrębnianie jednostek leksykalnych działa, a teraz czas na kompilator. Czy muszę budować drzewko, czy wystarczą listy z otwartymi {, (, nazwami funkcji i flagi typu czekam_na_nawias?

Prosty przykład:

komendant.powiedz( zasoby("Teraz możecie liczyć"), 200 );

  1. Nazwa - oczekujemy na . (
  2. Kropka - oczekujemy na nazwę - ustawiamy flagę, że to obiekt
  3. Nazwa - oczekujemy na . (
  4. Nawias - oczekujemy na nazwę, liczbę, ciąg znaków - ustawiamy flagę
  5. Nazwa - oczekujemy na . , (
  6. Nawias - podobnie jak w 4 czekamy na argument
  7. Ciąg znaków - oczekujemy na ) , - + / * ^
  8. Nawias zamykający - oczekujemy na , ) + - * / ^
  9. Przecinek - podobnie jak w 4 i 6 czekamy na argument
  10. Liczba - podobnie jak w 7
  11. Nawias zamykający - czekamy na ; bo jesteśmy poza funkcją
0

Drzewka będziesz musiał budować tak czy siak, chyba że nie zależy Ci np.na kolejności wykonywania działań czy wygodzie (analiza, optymalizacja i generowanie kodu z takiego gotowego drzewka to cud miód pod każdym względem, w porównaniu do generacji kodu z listy tokenów).

0

Aktualnie piszę to w PHP, ale może przepiszę do JavaScript. Wracając do analizatora leksykalnego, dokonałem 3 zasadnicze zmiany:

  1. Przestrzeń nazw - raczej niewielki wpływ na wydajność
  2. Nowe jednostki leksykalne
  3. Duży plik wejściowy

Wyciągnięcie wszystkich jednostek leksykalnych (5459) z pliku 30 KB trwa 2 sekundy, a po dodaniu flagi u (Unicode) ponad 5 sekund. Na pewno da się to przyspieszyć. Obecnie wygląda to tak:

namespace My\Parser;

$tokenizer = new Tokenizer;
$tokenizer->add('"(?:[^"\\\\]|\\.)*"', TOKEN_STRING);
$tokenizer->add('//.*?\n', TOKEN_COMMENT);
$tokenizer->add('MAPA', TOKEN_MAPA);
$tokenizer->add('OBIEKT', TOKEN_OBIEKT);
$tokenizer->add('if', TOKEN_IF);
$tokenizer->add('else', TOKEN_ELSE);
$tokenizer->add('for', TOKEN_FOR);
$tokenizer->add('while', TOKEN_WHILE);
$tokenizer->add('\\(', TOKEN_LEFT_PARENTHESIS);
$tokenizer->add('\\)', TOKEN_RIGHT_PARENTHESIS);
$tokenizer->add('[0-9]+\\.[0-9]+', TOKEN_FLOAT);
$tokenizer->add('0x[0-9A-F]+', TOKEN_INT);
$tokenizer->add('[0-9]+', TOKEN_INT);
$tokenizer->add('true', TOKEN_BOOL);
$tokenizer->add('false', TOKEN_BOOL);
$tokenizer->add('=', TOKEN_ASSIGN);
$tokenizer->add(':', TOKEN_COLON);
$tokenizer->add(';', TOKEN_SEMICOLON);
$tokenizer->add('[a-zA-Z_$][a-zA-Z0-9_$]*', TOKEN_NAME);
//I tak dalej...
$tokenizer->tokenize($input_string);

const TOKEN_EPSILON = 0;
const TOKEN_STRING = 1;
const TOKEN_COMMENT = 2;
const TOKEN_MAPA = 3;
const TOKEN_OBIEKT = 4;
//I tak dalej...

class Tokenizer
{
	private $regexs = [];
	private $tokens = [];
	public function add($regex, $token)
	{
		$this->regexs['#^'.$regex.'#i'] = $token;
	}
	public function tokenize($input)
	{
		$this->tokens = [];
		$bom = pack('H*','EFBBBF');
		$input = trim($input);
		$input = preg_replace("/^$bom/", '', $input);
		while($input !== '')
		{
			$match = false;
			foreach($this->regexs as $regex=>$token)
			{
				if(preg_match($regex, $input, $matches))
				{
					$match = true;
					$this->tokens[] = new Token($token, $matches[0]);
					$input = trim(preg_replace($regex, '', $input, 1));
					break;
				}
			}
			if(!$match)
			{
				throw new \Exception(sprintf('Invalid character %s', $input));
			}
		}
		return $this->tokens;
	}
}

class Token
{
	public $token;
	public $sequence;
	public function __construct($token, $sequence)
	{
		$this->token = $token;
		$this->sequence = $sequence;
	}
}

Wcześniej stałe znajdowały się w klasie, ale Token::IF nie przejdzie ze względu na słowo kluczowe IF, więc czytelniej będzie TOKEN_IF. Tu pojawiają się pytania:

  1. Czy trzeba definiować osobny typ (osobną stałą) jednostki leksykalnej dla plus (+) i minus (-), if i else, pętli for i while? Od czego zależy ich ilość?
  2. Czy te stałe są przydatne? Równie dobrze można sprawdzać na etapie budowy drzewka, czy coś jest ciągiem znaków, liczbą, słowem kluczowym.
  3. Jeśli znajdę ciąg znaków "tekst", zapisywać "tekst" z cudzysłowami czy tekst bez "" do listy znalezionych jednostek leksykalnych?
0

Na pewno da się to przyspieszyć.

Tak, nie korzystaj z wyrażeń regularnych.

Ad 1: zasadniczo, tak. Pytania drugiego nie rozumiem.
Ad 2: to jest zadaniem tokenizera, chodzi o wydajność i klarowność kodu.
Ad 3: bez.

0

W niektórych przypadkach trzeba skorzystać z wyrażeń regularnych, a w pozostałych wystarczy stripos(). Rozwinąć wszystkie jednostki leksykalne do IF(...) ELSEIF(...) lub 2 pętle osobno dla preg_match i stripos(). Obecnie:

  • 127079 wywołań preg_match() zajmuje 889 ms
  • 5460 wywołań preg_replace() trwa 78 ms
  • 5460 wywołań trim() trwa 36 ms, 5459 obiektów Token
  • 5460 wywołań preg_match() to 38,2 ms (policzone kalkulatorem)
  • 5459 obiektów Token - wywołania __construct() zajmują 36 ms

Największy problem to preg_match(). Znacie sposoby na ograniczenie liczby wywołań? Jest jeszcze preg_match_all(). Kolejny sposób to złączyć wszystkie wyrażenia regularne w jedno, dzieląc znakiem | wtedy stripos() już nie używamy. Zamiast trim() i preg_replace() możemy używać argumentu offset (przesunięcie) i pomijać białe znaki, ale wzrost wydajności będzie niewielki.

0

Spytam w drugą stronę może: widziałeś kiedyś kod jakiegoś sensownego tokenizera, który rozbija kod przy pomocy regexpów? :|
Nie potrzebujesz żadnych wyrażeń regularnych do tego.

0

Później zmienię wyrażenia regularne na co innego, a obecnie zajmuję się analizą wyodrębnionych jednostek leksykalnych. Czy da się napisać kompilator w ten sposób, czy takie podejście zaprowadzi mnie w ciemny kąt?

<?php namespace ELECTIONS\Parser;

const TOKEN_EPSILON = 0;
const TOKEN_STRING = 1;
const TOKEN_COMMENT = 2;
const TOKEN_OBIEKT = 3;
const TOKEN_MAPA = 4;
const TOKEN_EVENT = 5;
const TOKEN_DESCRIPTION = 6;
const TOKEN_TALK = 7;
const TOKEN_END = 8;
const TOKEN_IF = 9;
const TOKEN_ELSE = 10;
const TOKEN_FOR = 11;
const TOKEN_WHILE = 12;
const TOKEN_LEFT_STAR = 13;
const TOKEN_RIGHT_STAR = 14;
const TOKEN_LEFT_PARENTHESIS = 15;
const TOKEN_RIGHT_PARENTHESIS = 16;
const TOKEN_LEFT_BRACKET = 17;
const TOKEN_RIGHT_BRACKET = 18;
const TOKEN_PROPERTIES = 19;
const TOKEN_MAIN = 40;
const TOKEN_INT = 20;
const TOKEN_FLOAT = 21;
const TOKEN_BOOL = 22;
const TOKEN_AT = 23;
const TOKEN_RETURN = 24;
const TOKEN_ASSIGN = 25;
const TOKEN_COLON = 26;
const TOKEN_SEMICOLON = 27;
const TOKEN_DOT = 28;
const TOKEN_PLUS_ASSIGN = 29;
const TOKEN_MINUS_ASSIGN = 30;
const TOKEN_DATA_TYPE = 39;
const TOKEN_MACRO = 31;
const TOKEN_NAME = 32;
const TOKEN_COMMA = 33;
const TOKEN_LEFT_BRACE = 34;
const TOKEN_RIGHT_BRACE = 35;
const TOKEN_GREATER_THAN = 36;
const TOKEN_LESS_THAN = 37;
const TOKEN_SIGN = 38;

/** Base class for tokens */
class Token
{
	public $type;
	public $text;
	public function __construct($token, $sequence)
	{
		$this->type = $token;
		$this->text = $sequence;
	}
}

/** Parser */
class Parser
{
	/**
	 * Tokens returned by lexer
	 * @var Token[]
	 */
	private $tokens = [];
	/**
	 *
	 * @var Obiekt[]
	 */
	private $obiekts = [];
	/**
	 *
	 * @var Mapa[]
	 */
	private $mapas = [];
	/**
	 *
	 * @var Talk[]
	 */
	private $talks = [];
	/**
	 * Parse ELECTIONS code
	 * @param string $input source code as string
	 * @return string todo jeszcze nie wiem
	 */
	public function parse($input)
	{
		$lexer = new Lexer;
		$lexer->add('"(?:[^"\\\\]|\\.)*"', TOKEN_STRING);
		$lexer->add('//.*?\n', TOKEN_COMMENT);
		$lexer->add('OBIEKT', TOKEN_OBIEKT);
		$lexer->add('MAPA', TOKEN_MAPA);
		$lexer->add('EVENT', TOKEN_EVENT);
		$lexer->add('DESCRIPTION', TOKEN_DESCRIPTION);
		$lexer->add('TALK', TOKEN_TALK);
		$lexer->add('END', TOKEN_END);
		$lexer->add('if', TOKEN_IF);
		$lexer->add('else', TOKEN_ELSE);
		$lexer->add('for', TOKEN_FOR);
		$lexer->add('while', TOKEN_WHILE);
		$lexer->add('string|int|float|bool', TOKEN_DATA_TYPE);
		$lexer->add('\\(\\*', TOKEN_LEFT_STAR);
		$lexer->add('\\*\\)', TOKEN_RIGHT_STAR);
		$lexer->add('\\(', TOKEN_LEFT_PARENTHESIS);
		$lexer->add('\\)', TOKEN_RIGHT_PARENTHESIS);
		$lexer->add('PROPERTIES', TOKEN_PROPERTIES);
		$lexer->add('[0-9]+\\.[0-9]+', TOKEN_FLOAT);
		$lexer->add('0x[0-9A-F]+', TOKEN_INT);
		$lexer->add('[0-9]+', TOKEN_INT);
		$lexer->add('true|false', TOKEN_BOOL);		
		$lexer->add('@', TOKEN_AT);
		$lexer->add('RETURN', TOKEN_RETURN);
		$lexer->add('=', TOKEN_ASSIGN);
		$lexer->add(':', TOKEN_COLON);
		$lexer->add(';', TOKEN_SEMICOLON);
		$lexer->add('\\.', TOKEN_DOT);
		$lexer->add('\\+=', TOKEN_PLUS_ASSIGN);
		$lexer->add('[a-zA-Z_$][a-zA-Z0-9_$]*', TOKEN_NAME); //MILESTONE
		$lexer->add('\\#', TOKEN_MACRO);
		$lexer->add(',', TOKEN_MACRO);
		$lexer->add('{', TOKEN_LEFT_BRACE);
		$lexer->add('}', TOKEN_RIGHT_BRACE);
		$lexer->add('<', TOKEN_LESS_THAN);
		$lexer->add('>', TOKEN_GREATER_THAN);
		$lexer->add('-', TOKEN_SIGN);
		$lexer->add('\\+', TOKEN_SIGN);

		$this->tokens = $lexer->tokenize($input);
		$this->obiekts = []; //todo dopisac wyzewj
		$this->mapas = [];

		$token = reset($this->tokens);
		while($token)
		{
			switch($token->type)
			{
				case TOKEN_OBIEKT:
					$this->parseObiekt();
					break;
				case TOKEN_MAPA:
					$this->parseMapa();
					break;
				case TOKEN_MAIN:
					$this->parseMain();
					break;
				default:
					throw new \Exception('Unexpected '.$token->text);
			}
			$token = $this->next();
		}
		
		var_dump($this->obiekts, $this->mapas, $this->talks);
	}
	
	/**
	 * 
	 * @param type $type
	 * @return Token
	 * @throws \Exception
	 */
	private function next($type = null)
	{
		if(!$token = next($this->tokens))
		{
			throw new \Exception('No next token');
		}
		if(!empty($type) && $token->type !== $type)
		{
			throw new \Exception(sprintf('Unexpected %s expecting %s', $token->text, $this->getTokenName($type)));
		}
		return $token;
	}
	
	private function prev($type = null)
	{
		if(!$token = prev($this->tokens))
		{
			throw new \Exception('No previous token');
		}
		if(!empty($type) && $token->type !== $type)
		{
			throw new \Exception(sprintf('Unexpected %s expecting %s', $token->text, $this->getTokenName($type)));
		}
		return $token;
	}
	
	//todo do wywalenia inaczej
	private function getTokenName($type = null)
	{
		if($type instanceof Token)
		{
			$type = $token->type;
		}
		elseif(is_null($type))
		{
			 $type = current($this->tokens)->type;
		}
		elseif(!is_numeric($type))
		{
			return $type;
		}
		foreach(get_defined_constants(TRUE)['user'] as $name=>$value)
		{
			if($value === $type && 0 === strpos($name, 'elections\parser\TOKEN_'))
			{
				return substr($name, 17);
			}
		}
		throw new \Exception(sprintf('Token %d does not exist', $type));
	}
	
	private function skip($type = null)
	{
		if(empty($type))
		{
			return end($this->tokens);
		}
		while($token = $this->next())
		{
			if($token->type === $type)
			{
				return $token;
			}
		}
	}

	private function parseObiekt()
	{
		$this->obiekts[] = $this->obiekt = new \Obiekt; //todo do klasy
		$this->obiekt->name = $this->next(TOKEN_NAME)->text;
		$this->next(TOKEN_LEFT_STAR);
		while(true)
		{
			switch($this->next()->type)
			{
				case TOKEN_DESCRIPTION:
					$this->parseDescription();
					break;
				case TOKEN_PROPERTIES:
					$this->parseProperties();
					break;
				case TOKEN_EVENT:
					$this->parseEvent();
					break;
				case TOKEN_RIGHT_STAR:
					return true;
				default:
					throw new \Exception('Unexpected '.$this->getTokenName().' in '.$this->obiekt->name.' definition.');
			}
		}
	}
	
	private function parseDescription()
	{
		$this->next(TOKEN_ASSIGN);
		$token = $this->next();
		switch($token->type)
		{
			case TOKEN_STRING:
				$this->obiekt->description = $token->text;
				break;
			case TOKEN_MACRO:
				$this->obiekt->description = $this->parseMacro();
				break;
			default:
				throw new \Exception('Unexpected '.$this->getTokenName().' expecting string, number or macro');
		}
		$this->next(TOKEN_SEMICOLON);
	}
	
	private function parseProperties()
	{
		$this->next(TOKEN_COLON);
		$next = $this->next();
		
		while($next->type === TOKEN_NAME)
		{
			$this->next(TOKEN_ASSIGN);
			$this->obiekt->addProperty($next->text, $this->parseExpression());
			$this->next(TOKEN_SEMICOLON);
			$next = $this->next();
		}
		
		$this->prev();
	}

	private function parseExpression()
	{
		$token = $this->next();
		switch($token->type)
		{
			case TOKEN_INT:
				break;//todo tu kontynuj
		}
	}
	
	private function parseEvent()
	{
		$token = $this->next();
		if($token->type === TOKEN_DATA_TYPE)
		{
			$returnType = $token->text;
			$name = $this->next(TOKEN_NAME)->text;
		}
		elseif($token->type === TOKEN_NAME)
		{
			$returnType = 'void';
			$name = $token->text;
		}
		else
		{
			throw new \Exception('Unexpected '.$this->getTokenName().' expecting event name or int|void|float|pointer');
		}
		$this->obiekt->addEvent($name,null);
		$this->next(TOKEN_LEFT_PARENTHESIS);
		$args = [];
		
		while(true)
		{
			$token = $this->next();
			switch($token->type)
			{
				case TOKEN_RIGHT_PARENTHESIS:
					break 2;
				case TOKEN_NAME:
					$name = $token->text;
					$this->next(TOKEN_COLON);
					$type = $this->next(TOKEN_DATA_TYPE);
					break;
				default:
					throw new \Exception('Unexpected '.$token->text.' expecting argument name or )');
			}
		}

		$this->next(TOKEN_COLON);
		$this->next(TOKEN_LEFT_BRACE);
		$this->parseCommands();
		$this->next(TOKEN_RIGHT_BRACE); //TODO: finish here, commands not parsed
	}
	
	private function parseMacro()
	{
		$token = $this->next(TOKEN_NAME);
		$this->skip(TOKEN_LEFT_PARENTHESIS);
		$this->skip(TOKEN_RIGHT_PARENTHESIS);
	}
	
	private function parseCommands()
	{
		//TODO: I KNOW I MUST BUILD PARSE TREE OR NOT BUT..............
		$token = $this->next();
		while(true)
		{
			switch($token->type)
			{
				case TOKEN_NAME:
					$this->parseFunctionCall();
					break;
				default:
					$this->prev();
					return;
			}
		}
	}
	
	private function parseFunctionCall()
	{
		$this->next(TOKEN_LEFT_PARENTHESIS);
		$this->next(TOKEN_RIGHT_PARENTHESIS);
	}
	
	/**
	 * Parse ELECTIONS code from file
	 * @param string $path path to source file
	 * @return string todo jeszcze nie wiem
	 */
	public function parseFile($path)
	{
		return $this->parse(file_get_contents($path));
	}
}

/** Lexer */
class Lexer
{
	/**
	 * Regular expressions of tokens to match
	 * @var Array[string]
	 */
	private $regexs = [];

	/**
	 * Adds new token to recognize in source code
	 * @param string $regex PCRE regular expression without delimiter
	 * @param int $token token type (use constant)
	 */
	public function add($regex, $token)
	{
		$this->regexs['~'.$regex.'~iA'] = $token;
	}

	/**
	 * Divides input string into tokens
	 * @param string $input
	 * @return Token[] found tokens 
	 * @throws Exception if an input character matches no defined token
	 */
	public function tokenize($input)
	{
		$tokens = [];
		$bom = pack('H*','EFBBBF');
		$input = preg_replace("/^$bom/", '', trim($input));
		while($input !== '')
		{
			$match = false;
			foreach($this->regexs as $regex=>$token)
			{
				if(preg_match($regex, $input, $matches))
				{					
					$match = true;
					$input = trim(preg_replace($regex, '', $input, 1));
					if($token === TOKEN_COMMENT)
					{
						break;
					}
					elseif($token === TOKEN_STRING)
					{
						$matches[0] = substr($matches[0], 1, -1);
					}
					$tokens[] = new Token($token, $matches[0]);
					break;
				}
			}
			if(!$match)
			{
				throw new \Exception(sprintf('Invalid character %s', $input));
			}
		}
		return $tokens;
	}
}

W przypadku wyrażeń z priorytetami operatorów i zagnieżdżeniami nie obejdzie się bez hierarchii. Oto przykład:

if(lokal_wyborczy.skrzynka.pelna == true)
{
  if(1 > 0 + 0x0)
  {
    ObliczWynik( 50 + 2 * 2 ^ (2 + 2) + ObliczWynik(1) )
  }
}

Czy wystarczy budować drzewka tylko do wyrażeń czy do całego kodu? W sieci nie ma zbyt wiele przykładów. Węzły drzewa to pojedyncze jednostki leksykalne (TokenIntNode, TokenPlusNode, TokenIfNode) czy operacje (MultiplicationNode, AdditionNode, IfNode)?

0

1.Żaaaaaadanych wyrażeń regularnych, jeżeli chcesz osiągnąć jakąś sensowną wydajność na dłuższą metę.
2.Zrób enum zamiast kilkudziesięciu osobnych stałych TOKEN_.
3.regexes, jak już, a najczytelniej regexList.

Czy wystarczy budować drzewka tylko do wyrażeń czy do całego kodu?

To zależy od Ciebie.

0

Zaczyna się. Oto przykład:

int INTRO
Definiujemy 2 typy jednostek leksykalnych:

TYP : string|int|float|bool
NAZWA : [a-zA-Z_$][a-zA-Z0-9_$]*

W jakiej kolejności ustawić powyższe jednostki, aby prawidłowo je wyodrębnić z kodu?

Niekoniecznie po INT musi wystąpić spacja. Wyrażenia regularne są w stanie wykryć krawędzie słów (word boundaries). Bez PCRE pozostaje badać kolejny znak. Ewentualnie sztuczki typu szukać 'INT ', 'INT:', 'INT(' i wszelkie możliwe warianty. Jak rozwiązać ten problem?

PS. Plik 123,39 KB. Niezły czas 21,63 s. Trzeba szukać szybszych technik wydzielania jednostek, bo zamiana PCRE na str_* niewiele zmieni. Po prostu wywołanie funkcji w PHP jest wolne.

0

Nie korzystać z regexów?

0

Podaj przykład. Chodzi o to, że mamy 2 możliwości:

  1. TYP, NAZWA - wydzieli int (typ), INT (typ), RO (nazwa) - źle
  2. NAZWA, TYP - wydzieli int (nazwa), INTRO (nazwa) - też źle

Gdyby nie używać wcale wyrażeń regularnych, jak pobrać dowolną nazwę? Znak po znaku? To będzie jeszcze wolniejsze. Jak wcześniej napisałem, wywołanie funkcji w PHP jest wolne. Dodanie \b do regexów pomaga, ale może są lepsze sposoby.

0

Wtedy masz dwa tokeny - wczytujesz pierwszy i sprawdzasz czy jest typem.
Poza tym taka wieloznaczność składni jest zła sama w sobie.

0

Ciekawostka: zastąpienie trim(preg_replace()) argumentem $offset w preg_match() spowodowało wzrost wydajności o 40%. Trzeba dodatkowo wywołać $offset += strlen($matches[0]), dodać \s+ do listy poszukiwanych jednostek, ale jak widać, jest szybciej.

Czy w preg_match() da się pomijać białe znaki? W końcu w kodzie występują najczęściej, a dodanie ich do listy jednostek leksykalnych ma wpływ na wydajność.

Najlepiej pozbyć się wyrażeń regularnych i tu są 2 możliwości:

  1. Analizować kod znak po znaku.
switch($znak)
{
  case '{':
    //coś takiego

Pojedyncze znaki to nie problem. W przypadku 2 znaków ++ -- += badamy kolejny znak lub zapamiętujemy poprzedni. Podobnie w przypadku 3 znaków, jednak kod zaczyna się komplikować. A co w przypadku ciągów znaków i liczb? Znak po znaku czy preg_match?

  1. Jeden duży preg_match

Sposób opisany tutaj. Autor uzyskał 30% wzrost wydajności.

A może znacie lepszy sposób?

0

A co w przypadku ciągów znaków i liczb? Znak po znaku czy preg_match?

http://ideone.com/wdXXXy

0

Sposób z jednym preg_match() opisany na blogu - 70% wzrostu wydajności w porównaniu do wielu wielu preg_match() i 83% w porównaniu do wersji z trim(preg_replace()). Aby całkowicie pozbyć się wyrażeń regularnych, sprawa nie jest taka oczywista. Liczby - niech będzie is_numeric, bo mogą być zapisane w innych postaciach niż dziesiętnym, a co z identyfikatorami [a-zA-Z_$][a-zA-Z0-9_$]* bo tu ctype_alnum to za mało. Skomplikowany IF w pętli typu:

if($char >== 'A' && $char <== 'Z' || $char === '_' || $char === '$')
{
	++$offset;
	$text = $char;
	$char = $input[$offset];
	while($offset < $len && ($char >== 'A' && $char <== 'Z' || $char >== '0' && $char <== '9' || $char === '_' || $char === '$'))
	{
		++$offset;
		$text .= $char;
		$char = $input[$offset];
	}
}

Kodu nie testowałem. Potem trzeba sprawdzić, czy znaleziony ciąg znaków to nie identyfikator. Wystarczy switch. W wolnej chwili to przepiszę i podam wynik.

0

No mniej więcej tak to powinno wyglądać.
Już trochę większy potworek, ale z odpowiednimi komentarzami nie będzie czym się przejmować, a wydajność skoczy nieźle w górę.

1 użytkowników online, w tym zalogowanych: 0, gości: 1