« Поставить закладку » « Сделать стартовой »

« Форумы » « Блоги » « Статьи » « Новости » « Файлы » « Realcoding IRC » « Site map » « Поиск »


Главная Главная
Анонсы Анонсы
Форумы Форумы
Каталог Каталог
Поиск Поиск
Опросы Опросы
Книжный магазин Книжный магазин
Реклама на сайте
Публикации Публикации
Партнеры Партнеры
Карта Карта сайта
Рассылки Рассылки
RSS экспорт
Настройки Настройки
О нас пишут О нас пишут
Контакты Контакты
Гостевая книга Гостевая книга

Создание сайта за 3999 руб.!

ПнВтСрЧтПтСбВс
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
    Популярное
Функция OEMKeyScan

Функция AccessResource

Русификация Kylix

Типы данных C++ и общеязыковая среда выполнения CLR

Вывод в файл

Функция OpenIcon

Функция date() - вывод даты и времени в PHP

Реализация Win32 API в Windows 2000

Диалоговое окно Advanced

Нахождение вчерашнего дня на PHP




    Архив файлов



    Сообщества



    Документация

Статьи:: Интернет технологии :: PHP :: Парсер на РНР - это возможно!


отправить ссылку другу версия для печати  Обсудить на форуме

Парсер на РНР - это возможно!



Антон Калмыков (Antonio), http://phpclub.ru/

В данной коротенькой статье я хочу продемонстрировать, что РНР может очень хорошо справляться с функцией синтаксического разбора выражений. Для тех, кто никогда не касался данной тематики, я думаю, статья будет так же интересна, поскольку в ней мы рассмотрим метод программирования в виде конечных автоматов.

Начну с утверждения, что метод программирования с применением конечных автоматов очень прост, поскольку большая часть программы содержится внутри автомата, который вы готовите заранее в виде матрицы и используете в своей программе.



Что же такое автомат?

Представьте себе дискретную функцию от двух аргументов Ft(d, Ft-1). В качестве первого аргумента мы используем конечное счетное множество (массив данных), которое поступает извне. На каждом шаге в функцию поступает только одно число из данного массива. Вторым аргументом функции является значение функции на предыдущем шаге. Добавлю еще одно условие. Область значений данной функции представляет собой конечное счетное множество.

В чем прелесть такой функции? Вся прелесть заключается в том, то мы можем представить ее в виде матрицы, где номера строк будут задавать поступающие данные, а номера столбцов будут представлять область значений функции. Тогда, записав в ячейку (строка, столбец) число из множества значений функции, мы получим матрицу, которая описывает зависимость функции от входных данных и всего спектра значений. Будем называть число из множества значений СОСТОЯНИЕМ, а функцию АВТОМАТОМ.

Если вы не поняли предыдущего абзаца, то не пугайтесь, на практике все выглядит гораздо проще. Давайте рассмотрим простой пример. Допустим, нам надо построить разбор обычного арифметического выражения. Для этого нам придется создать два автомата. Первый будет выделять "слова" из буфера данных (т.е. сканировать его). Второй будет проверять грамматический порядок следования слов в выражении.

Начнем со сканера. Словами являются знаки операций +, -, *, / и последовательности символов, если они не содержат разделителей, такие как перевод строки, пробел и символ табуляции. Разделители мы будем просто игнорировать. Автомат для сканера, в этом случае, будет следующим.

    // состояния   0,  1,  2
     "0" => array( 0, -1, -1),//разделитель
     "1" => array( 2, -1, -1),//слово из одного символа
     "2" => array( 1,  1, -1),//символ

Номера строк задают тип символа, поскольку нам надо выделить знаки операций в отдельные слова. Состояния (номера столбцов) будут означать следующее.

    -1 слово готово, пора возвращать
     0 начало сканирования
     1 получили символ, надо копить пока это символ
     2 получили предопределенное слово из одного символа

в состоянии 1 мы будем копить символы, чтобы вернуть их как слово в состоянии -1. Наш сканер будет вызываться из парсера и завершать свою работу, когда он распознает хотя бы одно "слово", поэтому нет смысла вводить состояние -1 в таблицу автомата. Для парсера автомат будет такой.

     // состояния  0,  1,  2,  3,  4,  5
     "0" => array( 1, -1,  1,  1,  1,  1), // оператор
     "1" => array( 2,  4, -1,  2, -1, -1), // операнд
     "2" => array( 3,  3, -1,  3, -1, -1), // левая скобка
     "3" => array(-1, -1,  5, -1,  5,  5), // правая скобка
а состояния соответственно
 -1 Ошибка

  0 Начало разбора

  1 Получили оператор, ожидаем правый операнд
    или левую скобку

  2 Получили левый операнд (надо проверить число ли это),
    ждем оператор или правую скобку

  3 Получили левую скобку,
    ожидаем оператор или левую скобку

  4 Получили правый операнд (надо проверить число ли это),
    ожидаем оператор или правую скобку

  5 Получили правую скобку, ожидаем оператор

Парсер завершит работу, когда сканер вернет FALSE или при возникновении ошибки - состояние -1. По той же причине, что и в сканере мы можем не вносить состояние -1 в таблицу автомата

Далее привожу код программы с подробными комментариями, которые заменят дальнейшие объяснения. Я не строю дерева операций в примере данного парсера. Вы можете сделать это сами, ведь в соответствующих состояниях автомата вы получите оператор и операнды.

<?php
class ExpressionParser {
  var 
$pos,    // Позиция в буфере для разбора
      
$length,    // Длина буфера
      
$line,      // Текущий номер строки
      
$column,    // Текущий номер колонки в строке
      
$data,      // Буфер данных
      
$brackets,  // Количество открытых скобок
      
$state,     // Текущее состояние парсера
      
$errorstr,  // Строка диагностики ошибки
      
$instates,  // Код слова подаваемый на вход автомата
      
$prevstate// Предыдущее состояние парсера
      
$automat;   // Таблица автомата парсера

 /**********************************************************************
  *  Конструктор                      *
  **********************************************************************/
  
function ExpressionParser($str) {
    
$this->data=$str;
    
$this->length=strlen($str);
    
$this->pos=0;
    
$this->line=1;
    
$this->column=0;
    
$this->brackets=0;
    
    
// Коды слов, выданных сканером, подаваемых на вход парсера
    // Остальные слова имеют код 1
    
$this->instates = array("+" => 0"*" => 0"-" => 0"/" => 0"(" => 2")" => 3);
    
    
// Автомат парсера
    
$this->automat=array(
    
/*
    -1 Ошибка
     0 Начало разбора
     1 Получили оператор, ожидаем правый операнд или левую скобку
     2 Получили левый операнд (надо проверить число ли это), ждем оператор или правую скобку
     3 Получили левую скобку, ожидаем оператор или левую скобку
     4 Получили правый операнд (надо проверить на число), ожидаем оператор или правую скобку
     5 Получили правую скобку, ожидаем оператор
    */
     //состояния 0,  1,  2,  3,  4,  5
     
"0"=>array( 1, -1,  1,  1,  1,  1),//оператор
     
"1"=>array( 2,  4, -1,  2, -1, -1),//операнд
     
"2"=>array( 3,  3, -1,  3, -1, -1),//левая скобка
     
"3"=>array(-1, -1,  5, -1,  5,  5),//правая скобка
    
);
    
$this->state=$this->prevstate=0;
  }

 
/**********************************************************************
  *  Сканер                     *
  **********************************************************************/
  
function Scan() {
    
// Разделители, которые игнорируем
    
$delimiters=array(" ","t","r","n");

    
// Слова из одного символа
    
$words=array("+","-","*","/","(",")");

    
// автомат сканнера
    
$automat=array(
    
/*
    -1 слово готово, пора возвращать
     0 начало сканирования
     1 получили символ, надо копить пока это символ
     2 получили предопределенное слово из одного символа
    */
    //состояния  0,  1,  2
     
"0"=>array( 0, -1, -1),//разделитель
     
"1"=>array( 2, -1, -1),//слово из одного символа
     
"2"=>array( 1,  1, -1),//символ
    
);
    
$state=0;
    
$word="";
    
    
// Цикл сканирования
    
while ($this->pos<$this->length) {

      
// Устанавливаем код подаваемого на вход автомата символа.
      
if (in_array($this->data[$this->pos],$delimiters)) 
     
$instate=0;
      elseif (
in_array($this->data[$this->pos],$words)) 
     
$instate=1;
      else
     
$instate=2;
      
      
// Получаем состояние автомата
      
$state=$automat[$instate][$state];
      
      
// Наши действия по состояниям автомата
      
switch($state) {
     case 
0// начало сканирования
    
if ($this->data[$this->pos]=="n") {
      
$this->line++;
      
$this->column=0;
    }
    
$word="";
    break;
     case -
1// слово готово, пора возвращать
    
if (strlen($word)) return $word;
    break;
     case 
1// получили символ, надо копить пока это символ
    
$word.=$this->data[$this->pos];
    break;
     case 
2// получили предопределенное слово из одного символа
    
$word=$this->data[$this->pos];
    break;
      }
      
$this->pos++;
      
$this->column++;
      if (
$this->pos==$this->length && strlen($word)) return $word;
    }
    return 
false;
  }

 
/**********************************************************************
  *  Парсер                     *
  **********************************************************************/
  
function Parse() {
    
// Переменная $first равна нулю, если функция разбора была вызвана первый раз
    
$first=$this->pos;

    
// Цикл состояний
    
while(1) {
      
      
// Получаем слово от сканнера
      
$word=$this->Scan();
      
      
// Если слов больше нет, то прерываем цикл
      
if ($word===false) break;
      
      
// Устанавливаем код, подаваемого на вход автомата, слова
      
$instate=isset($this->instates[$word]) ? $this->instates[$word] : 1;
      
      
// Получаем состояние автомата парсера
      
$this->state=$this->automat[$instate][$this->state];
      
      
// Если ошибочное состояние, то прерываем цикл
      
if ($this->state==-1) {
     
$this->errorstr="Ошибка в строке: $this->line, колонка: $this->column<br>";
     break;
      }
      
      
// Наши действия по состояниям автомата парсера
      
switch($this->state) {

     case 
1// Получили оператор, ожидаем правый операнд или левую скобку
     
    // Если первое слово оператор, то это может быть только "+" или "-"
    
if (($this->prevstate==|| $this->prevstate==0) && $word!="-" && $word!="+") {
      
$this->errorstr="Ошибка в строке: $this->line, колонка: $this->column<br>";
      return 
false;
    }
    break;

     case 
2// Получили левый операнд (надо проверить число ли это), ждем оператор 
             //или правую скобку

    // Проверяем число ли это?
    
if (!preg_match("/^[0-9]+(.[0-9]+)?$/",$word)) {
      
$this->errorstr="Ошибка в строке: $this->line, колонка: $this->column<br>";
      return 
false;
    }
    break;

     case 
3// Получили левую скобку, ожидаем оператор или левую скобку

    // Увеличиваем кол-во открытых скобок на 1;
    
$this->brackets++;
    
    
// Удобно использовать рекурсию, т.к. данные в скобках
    // можно рассматривать как самоcтоятельные выражения.
    // Мы вернемся из функции в случае ошибки, конца данных или
    // после получения закрытой скобки
    
if (!$this->Parse()) return false;
    break;

     case 
4// Получили правый операнд (надо проверить число ли это), ожидаем оператор 
             //или правую скобку

    // Проверяем число ли это?
    
if (!preg_match("/^[0-9]+(.[0-9]+)?$/",$word)) {
      
$this->errorstr="Ошибка в строке: $this->line, колонка: $this->column<br>";
      return 
false;
    }
    break;

     case 
5// Получили правую скобку, ожидаем оператор
     
    // Уменьшаем кол-во открытых скобок на 1
    
$this->brackets--;
    return 
true;

      } 
// end switch
      
      // Запоминаем текущее состояние для следующего шага цикла
      
$this->prevstate=$this->state;

    } 
// end while

    // Так как у нас отсутствует состояние конца разбора, то надо
    // Проверить в каком состоянии мы завершили разбор
    // Это надо делать только один раз в самом первом вызове
    // функции разбора. Это первый вызов, если $first==0
    // Итак, мы должны вернуть ошибку, если у нас есть лишние скобки,
    // или если мы не получили правого операнда или правой скобки,
    // т.е. разбор завершился "на середине".
    
    
if (!$first && ($this->brackets || $this->state!=&& $this->state!=5)) return false;
    
    return 
true;
  }
  
}

$p=new ExpressionParser("-4.25*((2+3)*4+1)/5");
print 
$p->data."<br>";
if (
$p->Parse())
  print 
"Выражение корректно.<br>";
else
  print 
$p->errorstr;
?>




Рубрика: PHP




ASP.NET и немного поисковой оптимизации.

Советы

С тех пор, как появилась Альтависта, большинство посетителей стали приходить на сайты из поисковых машин. И головной боли стало больше. Теперь мало написать сайт, нужно сделать его совместимым с поисковыми ботами. Одна из тех задач, которую приходится решать — быстрая переиндексация обновлений на сайте. Поисковые роботы чаще посещают те сайты, которые чаще меняются.


Подробнее... | Рубрика: Советы | Добавлено: 30.07.2008

Protocol buffers: библиотека обмена данными для C++, Java, Python от Google.

Программирование для Web на C

Наконец-то я могу поделиться тем, чему уже давно радуюсь сам: Google открывает исходники Protocol Buffers! Что это и почему надо радоваться? Это простой и удобный способ обмена данными. Можно сказать, альтернатива XML, но гораздо менее амбициозная и (поэтому) более быстрая и компактная. Далее - перевод отрывка анонса в блоге Open Source at Google и пример использования.


Подробнее... | Рубрика: Программирование для Web на C | Добавлено: 30.07.2008

Описание VivaVisualCode.

VivaCore

В данной статье рассмотрена программа VivaVisualCode, демонстрирующая использование библиотеки VivaCore. Программа VivaVisualCode графически отображает дерево разбора для вводимого исходного кода на языке Си++.


Подробнее... | Рубрика: VivaCore | Добавлено: 29.07.2008

Остальные статьи:

JQuery: Пара сотен плагинов в одной заметке
Касание сетки
Разработка элементов управления ASP.NET на примере навигационной панели
Сохранения параметров приложения в .Net
Custom cursors в .Net
Бегун убегает от хозяина
"Битрикс" выпустил седьмую версию CMS
Выбираем систему управления сайтом
Рынок CMS в Европе или впечатления с CeBIT 2008
32 подводных камня OpenMP при программировании на Си++
Проeкт - шифровка
А что если..? (операторы if...else в C#)
Измерение скорости работы скрипта
О том как разработчики пьют кофе
Работаем с LINQ to XML
XmlSerializer - Assembly Leak без спроса


Цитата дня (все,добавить):



    Рубрикатор

Программирование

C/С++
Обучение
Windows API
XAML
Моделирование
Паттерны
Visual Basic 7 .NET
WxWidgets
Функции WinApi
Функции С++
Разработка под Mac OS
Eiffel
Visual Studio 2008
UI дизайн
Алгоритмы
Конкурсные статьи
Turbo Pascal
Visual Studio
CASE-средства
Visual Studio 2005
Без VCL
Delphi
Тех. документация
Тестирование
Software Testing
ООП
TCP/IP
Google Android
Windows Installer
.NET Framework
Драйвера
C# C Sharp
Справка
Проектирование
Информ. системы
Visual Basic
Assembler
Оптимизация кода
Gtk+
Компоненты
Реинжиниринг
Управление проектами
Extreeme programming
Lotus Notes
Алгебраическое проектирование

Интернет технологии

PHP
Perl
ASP
WAP
Cookies
SSI
CGI
Web Servers
VB Script
DNS
CSS
XML
Html
Java Script
Java2ME
Firewall
Flash
.htaccess
Apache
VRML
Протоколы
Поисковые системы
Технология JAVA
Учебник по PHP
Учебник по JavaScript
Учебник по XML
Java Q&A
AJAX
DHTML
XHTML
Dreamweaver
Web 2.0
Python
Вебмастеру
Cisco
Ruby on Rails
Silverlight

Базы данных

Access
InterBase
MySQL
Oracle
ADO .NET
Основы SQL
Учебник по Access 2002
MS
Microsoft FoxPro
Доступ к данным
XML в MS SQL Server 2000
ODBC и MyODBC
Обучение
Caché
DB2
PostgresSQL
Sybase
Теория
Хранилища данных
Безопасность
Реляционные данные
MySQL и mSQL

Остальное:

Разное
Обзоры книг
Безопасность
Графика и дизайн
Юмор
Linux
Фракталы
Microsoft Axapta
Многоядерность
Сети
Microsoft Office
Работа
MS-DOS
Криптография
Графика и игроделание
Новости SDK
Системы защиты
Учебник по AutoCad
CVS
Windows XP
Windows Server 2003
Windows Vista
Windows 7
Мероприятия

    Кто на сайте
Вы не зарегистрированы.
Имя:

Пароль:

Запомнить

Регистрация позволит Вам пользоваться дополнительными сервисами.
Сейчас на сайте:
Гостей: 108
Пользователей: 0