Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Midhun Devasia committed Apr 25, 2021
0 parents commit 3fa12da
Show file tree
Hide file tree
Showing 29 changed files with 2,013 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.travis.yml export-ignore
.gitattributes export-ignore
.gitignore export-ignore
tests export-ignore
phpunit.xml.dist export-ignore
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
vendor/
phpunit.xml
composer.lock
*.cache
.idea
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: php

php:
- 7.2
- 7.4
- 8.0.1

before_script:
- composer install

script:
- ./vendor/bin/phpunit --testdox tests
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# json-ast : JSON to AST parser in PHP

[![Latest Stable Version](https://img.shields.io/packagist/v/midhundevasia/json-ast.svg?style=flat-square)](https://packagist.org/packages/midhundevasia/json-ast)
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.0-8892BF.svg?style=flat-square)](https://php.net/)
[![Build Status](https://travis-ci.com/midhundevasia/json-ast.svg?branch=master)](https://travis-ci.com/midhundevasia/json-ast)

## Install
$ composer require midhundevasia/json-ast

## Usage
<?php
use JsonAst\Parser;
$parser = new Parser();
$parser->parse(
'{"hello" : "World"}',
['loc' => true, 'source' => null]
);


## Tests
$ ./vendor/bin/phpunit --testdox tests

## Todo
- write more test cases
- code coverage

## License
json-ast is licensed under GNU General Public License (GPLv3) - see the `LICENSE` file for details.

## Credits
Inspired from following repositories.

https://github.com/vtrushin/json-to-ast
https://github.com/vtrushin/code-error-fragment

##### Development
$ vendor/bin/phpcbf src
$ vendor/bin/php-cs-fixer fix src
35 changes: 35 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "midhundevasia/json-ast",
"type": "library",
"version" : "0.0.1",
"description": "JSON to AST parser in PHP",
"homepage": "https://github.com/midhundevasia/json-ast",
"license": ["GPL-3.0+"],
"authors": [
{
"name": "Midhun Devasia",
"email": "hello@midhundev.asia",
"homepage": "http://midhundev.asia"
}
],
"keywords": [
"json", "json-ast", "ast parser", "json ast", "json parser"
],
"support": {
"issues": "https://github.com/midhundevasia/json-ast/issues"
},
"require": {
"php": ">=7.2"
},
"require-dev": {
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "^3.0@dev",
"friendsofphp/php-cs-fixer": "^2.19@dev"
},
"autoload": {
"psr-4": { "JsonAst\\": "src/" },
"exclude-from-classmap": [
"/tests/"
]
}
}
31 changes: 31 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>

<testsuites>
<testsuite name="JSON AST Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>./src</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>

</phpunit>
25 changes: 25 additions & 0 deletions src/Exception/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace JsonAst\Exception;

use JsonAst\Helper\CodeFragment;

class Exception
{
public function exceptionHandler($exception)
{
print 'SyntaxError: ' . $exception->getMessage() . PHP_EOL;
}

public function getError()
{

}

protected function setErrorMessage($message, $input, $line, $column, $source)
{
$code = (new CodeFragment)::getFragment($input, $line, $column, []);
$message = $line ? $message . "\n" . $code : $message;
return $message;
}
}
61 changes: 61 additions & 0 deletions src/Exception/ParserException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace JsonAst\Exception;

class ParserException extends Exception
{
const UNEXPECTED_END = 1;
const UNEXPECTED_TOKEN = 2;

public function __construct($type, $input, $tokenList, $index, $source)
{
switch ($type) {
case self::UNEXPECTED_END:
$this->unexpectedEnd($input, $tokenList, $source);
break;

case self::UNEXPECTED_TOKEN:
$this->unexpectedToken($input, $tokenList, $index, $source);
break;
}
}

public function unexpectedEnd($input, $tokenList, $source)
{
$loc = count($tokenList) > 0
? $tokenList[count($tokenList) - 1]['loc']['end']
: ['line' => 1, 'column' => 1];

@set_exception_handler(array($this, 'exceptionHandler'));
throw new \Exception(
$this->setErrorMessage(
'Unexpected end of input',
$input,
$loc['line'],
$loc['column'],
$source
)
);
}

public function unexpectedToken($input, $tokenList, $index, $source)
{
$line = $tokenList[$index]['loc']['start']['line'];
$column = $tokenList[$index]['loc']['start']['column'];
$startOffset = $tokenList[$index]['loc']['start']['offset'];
$endOffset = $tokenList[$index]['loc']['end']['offset'];
@set_exception_handler(array($this, 'exceptionHandler'));
$token = substr($input, $startOffset , 1);
throw new \Exception(
$this->setErrorMessage(
sprintf(
'Unexpected token <%s> at %d:%d', $token, $line, $column
),
$input,
$line,
$column,
$source
)
);
}
}
40 changes: 40 additions & 0 deletions src/Exception/TokenizerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
namespace JsonAst\Exception;

class TokenizerException extends Exception
{
const UNEXPECTED_SYMBOL = 1;

public function __construct($message, $type, $input, $line, $column, $source)
{
$this->line = $line;
$this->column = $column;
$this->input = $input;
$this->source = $source;

switch ($type) {
case self::UNEXPECTED_SYMBOL:
$this->unexpectedSymbol($message, $input, $source);
break;
}
}

public function unexpectedSymbol($symbol)
{
@set_exception_handler(array($this, 'exceptionHandler'));
throw new \Exception(
$this->setErrorMessage(
sprintf(
'Unexpected symbol <%s> at %d:%d',
$symbol,
$this->line,
$this->column
),
$this->input,
$this->line,
$this->column,
$this->source
)
);
}
}
67 changes: 67 additions & 0 deletions src/Helper/CodeFragment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace JsonAst\Helper;

class CodeFragment
{
public static function getFragment($code, $linePos, $columnPos, $settings)
{
$settings = array_merge_recursive(
[
'extraLines' => 2,
'tabSize' => 4
],
$settings
);
$lines = preg_split('/\r\n?|\n|\f/', $code);
$startLinePos = max(1, $linePos - $settings['extraLines']) - 1;
$endLinePos = min($linePos + $settings['extraLines'], count($lines));
$maxNumLength = strlen($endLinePos);
$previousLines = self::printLines($lines, $startLinePos, $linePos, $maxNumLength, $settings);
$targetLineBeforeCursor = self::printLine(
substr(
$lines[$linePos - 1],
0,
$columnPos - 1
),
$linePos,
$maxNumLength,
$settings
);

$cursorLine = str_repeat(' ', strlen($targetLineBeforeCursor)) . '^';
$nextLines = self::printLines($lines, $linePos, $endLinePos, $maxNumLength, $settings);

return implode(
"\n",
array_filter(
[$previousLines, $cursorLine, $nextLines],
function ($value) {
return $value ?? false;
}
)
);
}

private static function printLines($lines, $start, $end, $maxNumLength, $settings)
{
$code = array_slice($lines, $start, $end);
array_walk(
$code,
function (&$line, $index, $data) {
$line = self::printLine($line, $data['start'] + $index + 1, $data['max'], $data['settings']);
},
['start' => $start, 'end' => $end, 'max' => $maxNumLength, 'settings' => $settings]
);

return implode("\n", $code);
}

private static function printLine($line, $position, $maxNumLength, $settings)
{
$formattedNum = str_pad($position, $maxNumLength, ' ');
$tabReplacement = str_repeat(' ', $settings['tabSize']);

return $formattedNum . ' | ' . preg_replace('/\t/', $tabReplacement, $line);
}
}
30 changes: 30 additions & 0 deletions src/Helper/Location.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace JsonAst\Helper;

class Location
{
public static function loc(
$startLine,
$startColumn,
$startOffset,
$endLine,
$endColumn,
$endOffset,
$source
) {
return [
'start' => [
'line' => $startLine,
'column' => $startColumn,
'offset' => $startOffset
],
'end' => [
'line' => $endLine,
'column' => $endColumn,
'offset' => $endOffset
],
'source' => $source ?? null
];
}
}
30 changes: 30 additions & 0 deletions src/Helper/NumberHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace JsonAst\Helper;

class NumberHelper
{
public static function isDigit1to9($char)
{
return ($char >= '1' && $char <= '9');
}

public static function isExp($char)
{
return ($char === 'e' || $char === 'E');
}

public static function isDigit($char)
{
return ($char >= '0' && $char <= '9');
}

public static function isHex($char)
{
return (
self::isDigit($char)
|| ($char >= 'a' && $char <= 'f')
|| ($char >= 'A' && $char <= 'F')
);
}
}
Loading

0 comments on commit 3fa12da

Please sign in to comment.