PHP Classes

File: src/eMacros/Parser.php

Recommend this page to a friend!
  Classes of Emmanuel Antico   eMacros   src/eMacros/Parser.php   Download  
File: src/eMacros/Parser.php
Role: Class source
Content type: text/plain
Description: Class source
Class: eMacros
PHP LISP language interpreter
Author: By
Last change:
Date: 10 years ago
Size: 4,580 bytes
 

Contents

Class file image Download
<?php
namespace eMacros;

use
eMacros\Exception\ParseException;

class
Parser {
   
/**
     * Validated symbols
     * @var array
     */
   
public static $map = array();
   
   
/*
     * SYMBOL REGEX
     */
   
const PARENTHESES = '()';
    const
COMMENT_PREFIX = ';';
    const
WHITESPACES = " \t\n\r\f\v\0";
   
   
/**
     * Float validation regex
     * @var string
     */
   
const REAL_PATTERN = '{^[+-]?((\d+|(\d*\.\d+|\d+\.\d*))e[+-]?\d+|\d*\.\d+|\d+\.\d*)}i';
   
   
/**
     * Integer validation regex
     * @var unknown
     */
   
const INTEGER_PATTERN = '/^([+-]?)(0x([0-9a-f]+)|0([0-7]+)|[1-9]\d*|0)/i';
   
   
/**
     * String validation regex
     * @var string
     */
   
const STRING_PATTERN = '/^"([^"\\\\]|\\\\.)*"|^\'([^\'\\\\]|\\\\.)*\'/';
   
   
/**
     * Escape string replacement regex
     * @var string
     */
   
const STRING_ESCAPE_PATTERN = '/\\\\(([0-7]{1,3})|x([0-9A-Fa-f]{1,2})|.)/';
   
   
/**
     * Symbol validation regex
     * @var string
     */
   
const SYMBOL_PATTERN = '{^[^\s\d(){}\[\]"\';][^\s\'"(){}\[\];]*}';
   
   
/**
     * Parses a program
     * @param string $program
     * @throws ParseException
     * @return \eMacros\GenericList
     */
   
public static function parse($program) {
       
$i = 0;
       
$len = strlen($program);
       
$forms = array();
       
        while (
$i < $len) {
            if (
strpos(self::WHITESPACES, $program[$i]) === false) {
                try {
                   
$form = self::parseExpression(substr($program, $i), $offset);
                    if (!
is_null($form)) $forms[] = $form;
                 }
                 catch (
ParseException $e) {
                     throw new
ParseException($program, $e->offset + $i);
                 }
               
               
$i += $offset;
            }
            else {
                ++
$i;
            }
        }
       
        return
$forms;
    }
   
   
/**
     * Parses an inner expression
     * @param string $form
     * @param int $offset
     * @throws ParseException
     * @return mixed
     */
   
public static function parseExpression($form, &$offset) {
        static
$parentheses = null;
       
        if (
is_null($parentheses)) {
           
$_parentheses = self::PARENTHESES;
           
$parentheses = array();
           
            for (
$i = 0, $len = strlen($_parentheses); $i < $len; $i += 2) {
               
$parentheses[$_parentheses[$i]] = $_parentheses[$i + 1];
            }
           
            unset(
$_parentheses);
        }
       
        if (isset(
$form[0], $parentheses[$form[0]])) {
           
$end = $parentheses[$form[0]];
           
$values = array();
           
$i = 1;
           
$len = strlen($form);
           
            while (
$i < $len && $form[$i] != $end) {
                if (
strpos(self::WHITESPACES, $form[$i]) !== false) {
                    ++
$i;
                    continue;
                }
               
                try {
                   
$values[] = self::parseExpression(substr($form, $i), $_offset);
                   
$i += $_offset;
                }
                 catch (
ParseException $e) {
                     throw new
ParseException($form, $i + $e->offset);
                 }
            }
           
            if (isset(
$form[$i]) && $form[$i] == $end) {
               
$offset = $i + 1;
                return new
GenericList($values);
            }

            throw new
ParseException($form, $i);
        }
        elseif (isset(
$form[0]) && $form[0] == self::COMMENT_PREFIX) {
           
$offset = strlen(strtok($form, "\n"));
            return
null;
        }
        elseif (
preg_match(self::REAL_PATTERN, $form, $matches)) {
           
$offset = strlen($matches[0]);
   
            return new
Literal((float) $matches[0]);
        }
        elseif (
preg_match(self::INTEGER_PATTERN, $form, $matches)) {
           
$offset = strlen($matches[0]);
           
$sign = $matches[1] == '-' ? -1 : 1;
           
$value = !empty($matches[3]) ? hexdec($matches[3]) : (!empty($matches[4]) ? octdec($matches[4]) : $matches[2]);
   
            return new
Literal($sign * $value);
        }
        elseif (
preg_match(self::STRING_PATTERN, $form, $matches)) {
            list(
$parsed) = $matches;
           
$offset = strlen($parsed);
            return new
Literal(preg_replace_callback(self::STRING_ESCAPE_PATTERN, array(__CLASS__, 'unescapeString'), substr($parsed, 1, -1)));
        }
        elseif (
preg_match(self::SYMBOL_PATTERN, $form, $matches)) {
           
$symbol = $matches[0];
           
$offset = strlen($matches[0]);
           
           
//store validated symbols in order to reduce checks
           
if (array_key_exists($symbol, self::$map)) {
                return
self::$map[$symbol];
            }
           
           
self::$map[$symbol] = new Symbol($symbol);
            return
self::$map[$symbol];
        }
       
        throw new
ParseException($form, 0);
    }
   
   
/**
     * Replaces special characters in a string
     * @param array $matches
     * @return string
     */
   
protected static function unescapeString($matches) {
        static
$map = array('n' => "\n", 'r' => "\r", 't' => "\t", 'v' => "\v", 'f' => "\f");
       
        if (!empty(
$matches[2])) {
            return
chr(octdec($matches[2]));
        }
        elseif (!empty(
$matches[3])) {
            return
chr(hexdec($matches[3]));
        }
        elseif (isset(
$map[$matches[1]])) {
            return
$map[$matches[1]];
        }
       
        return
$matches[1];
    }
   
   
/**
     * Flushes validated symbols table
     */
   
public static function flush() {
       
self::$map = array();
    }
}
?>