Skip to content

LISP-Imitating Syntax for PHP #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
# LISP-Imitating Syntax for PHP

Notes:

- This may end up breaking assumptions made by opcache and be unsafe.
- This is not going to be fully featured
- This combines the parenthesis of lisp with the behavior of php.

Writing parsers becomes easier and operator precedence is no longer important
- This may be a useful reference point for writing DSLs extending PHP
or for trying to add new syntax to php.

## Supported features

- `(print expr)`, `(echo expr1 expr2)`
- Variables `(= $var expr)`
- Some unary and binary operators
- Support reading and writing constants as `(const name expr)`
- Add `(if cond truestmt falsestmt)` statements
- Support `(while cond stmts)` statements
- Support `(do stmt1 stmt2)` for statement lists
- Support function calls `(name_or_var arg1 arg2)`

See [Zend/tests/lisp](Zend/tests/lisp) for examples.

<div align="center">
<a href="https://php.net">
<img
Expand Down
13 changes: 13 additions & 0 deletions Zend/tests/lisp/call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Function calls
--FILE--
(?lisp

(printf "Hello, %s\n" "World")
(= $printer (. 'pri' 'ntf'))
($printer "Dynamic %s\n" $printer)

?)
--EXPECTF--
Hello, World
Dynamic printf
13 changes: 13 additions & 0 deletions Zend/tests/lisp/constants.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Setting and reading constants
--FILE--
(?lisp
(const MY_CONSTANT "My constant")
(echo MY_CONSTANT "\n")
(echo \MY_CONSTANT "\n")
(echo namespace\MY_CONSTANT "\n")
?)
--EXPECTF--
My constant
My constant
My constant
19 changes: 19 additions & 0 deletions Zend/tests/lisp/if.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
If statements
--FILE--
(?lisp

(if 1 (= $x "yes") (= $x "no"))
(echo $x "\n")
(if 0 (= $x "yes") (= $x "no"))
(echo $x "\n")
(if 1 (= $x "!"))
(echo $x "\n")
(if 0 (= $x "?"))
(echo $x "\n")
?)
--EXPECTF--
yes
no
!
!
13 changes: 13 additions & 0 deletions Zend/tests/lisp/print.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Print basic expressions test
--FILE--
(?lisp
(print "test from lisp\n")
(print (. (+ 1 2) "\n"))
(print (* 3 2))
(print "\n")
?)
--EXPECTF--
test from lisp
3
6
12 changes: 12 additions & 0 deletions Zend/tests/lisp/variables.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Setting and reading variables
--FILE--
(?lisp
(= $var (- 10 (- 2)))
(echo $var "\n")
(= $var (* $var 2))
(echo (. $var "\n"))
?)
--EXPECTF--
12
24
19 changes: 19 additions & 0 deletions Zend/tests/lisp/while.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
While statements
--FILE--
(?lisp

(= $i 1)
(while (<= $i 3) (do (echo $i "\n") (++ $i)))
(while (<= $i 0) (do (echo "unreachable") (= $i (+ $i 1))))
(echo "After loop $i\n")
(echo (++$i) "\n")
(echo (--$i) "\n")
?)
--EXPECTF--
1
2
3
After loop 4
5
4
170 changes: 170 additions & 0 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_DOC_COMMENT "doc comment"
%token T_OPEN_TAG "open tag"
%token T_OPEN_TAG_WITH_ECHO "'<?='"
%token T_LISP_START "'(?lisp'"
%token T_LISP_END "'?)'"
%token T_CLOSE_TAG "'?>'"
%token T_WHITESPACE "whitespace"
%token T_START_HEREDOC "heredoc start"
Expand Down Expand Up @@ -268,6 +270,10 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> attributed_statement attributed_class_statement attributed_parameter
%type <ast> attribute_decl attribute attributes namespace_declaration_name
%type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
%type <ast> lisp_scalar lisp_expr lisp_dereferenceable_expr lisp_top_statement lisp_top_statement_list lisp_statement lisp_statement_list
%type <ast> lisp_echo_expr lisp_echo_expr_list lisp_const_list lisp_const_decl
%type <ast> lisp_if_stmt lisp_while_stmt
%type <ast> lisp_argument_list lisp_non_empty_argument_list lisp_argument lisp_function_call lisp_callable_expr

%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
%type <num> method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier
Expand Down Expand Up @@ -487,6 +493,7 @@ statement:
| T_STATIC static_var_list ';' { $$ = $2; }
| T_ECHO echo_expr_list ';' { $$ = $2; }
| T_INLINE_HTML { $$ = zend_ast_create(ZEND_AST_ECHO, $1); }
| T_LISP_START lisp_top_statement_list T_LISP_END { $$ = $2; }
| expr ';' { $$ = $1; }
| T_UNSET '(' unset_variables possible_comma ')' ';' { $$ = $3; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
Expand Down Expand Up @@ -1464,6 +1471,169 @@ isset_variable:
expr { $$ = zend_ast_create(ZEND_AST_ISSET, $1); }
;

lisp_top_statement_list:
lisp_top_statement_list lisp_top_statement { $$ = zend_ast_list_add($1, $2); }
| %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); }
;

lisp_top_statement:
lisp_statement { $$ = $1; }
;

lisp_statement_list:
lisp_statement_list lisp_statement { $$ = zend_ast_list_add($1, $2); }
| %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); }
;

lisp_statement:
lisp_expr { $$ = $1; }
| '(' T_ECHO lisp_echo_expr_list ')' { $$ = $3; }
| '(' T_CONST lisp_const_list ')' { $$ = $3; }
| '(' T_DO lisp_statement_list ')' { $$ = $3; }
| lisp_if_stmt { $$ = $1; }
| lisp_while_stmt { $$ = $1; }
;

lisp_const_list:
lisp_const_list lisp_const_decl { $$ = zend_ast_list_add($1, $2); }
| lisp_const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CONST_DECL, $1); }
;

lisp_const_decl:
T_STRING lisp_expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $2, ($3 ? zend_ast_create_zval_from_str($3) : NULL)); }
;

lisp_dereferenceable_expr:
T_VARIABLE { $$ = zend_ast_create(ZEND_AST_VAR, $1); }
;

lisp_echo_expr_list:
lisp_echo_expr_list lisp_echo_expr { $$ = zend_ast_list_add($1, $2); }
| lisp_echo_expr { $$ = zend_ast_create_list(1, ZEND_AST_STMT_LIST, $1); }
;

lisp_echo_expr:
lisp_expr { $$ = zend_ast_create(ZEND_AST_ECHO, $1); }
;

lisp_expr:
'(' T_PRINT lisp_expr ')' { $$ = zend_ast_create(ZEND_AST_PRINT, $3); }
| lisp_scalar { $$ = $1; }
| lisp_dereferenceable_expr { $$ = $1; }
| lisp_function_call { $$ = $1; }
// $ordinaryVar

// Assignments
| '(' '=' lisp_dereferenceable_expr lisp_expr ')'
{ $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $4); }
| '(' '=' '&' lisp_dereferenceable_expr lisp_expr ')'
{ $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $4, $5); }

// Unary operators
| '(' '+' lisp_expr ')' { $$ = zend_ast_create(ZEND_AST_UNARY_PLUS, $3); }
| '(' '-' lisp_expr ')' { $$ = zend_ast_create(ZEND_AST_UNARY_MINUS, $3); }
| '(' '!' lisp_expr ')' { $$ = zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BOOL_NOT, $3); }
| '(' '~' lisp_expr ')' { $$ = zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BW_NOT, $3); }
| '(' T_INC lisp_dereferenceable_expr ')' { $$ = zend_ast_create(ZEND_AST_PRE_INC, $3); }
| '(' T_DEC lisp_dereferenceable_expr ')' { $$ = zend_ast_create(ZEND_AST_PRE_DEC, $3); }
// Binary operators
| '(' '|' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_BW_OR, $3, $4); }
| '(' '&' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $3, $4); }
| '(' '^' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_BW_XOR, $3, $4); }
| '(' '.' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_CONCAT, $3, $4); }
| '(' '+' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_ADD, $3, $4); }
| '(' '-' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_SUB, $3, $4); }
| '(' '*' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_MUL, $3, $4); }
| '(' T_POW lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_POW, $3, $4); }
| '(' '/' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_DIV, $3, $4); }
| '(' '%' lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_MOD, $3, $4); }
| '(' T_SL lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_SL, $3, $4); }
| '(' T_SR lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_SR, $3, $4); }

| '(' T_BOOLEAN_OR lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_AST_OR, $3, $4); }
| '(' T_BOOLEAN_AND lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_AST_AND, $3, $4); }
| '(' T_LOGICAL_OR lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_AST_OR, $3, $4); }
| '(' T_LOGICAL_AND lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_AST_AND, $3, $4); }
| '(' T_LOGICAL_XOR lisp_expr lisp_expr ')' { $$ = zend_ast_create_binary_op(ZEND_BOOL_XOR, $3, $4); }
| '(' T_IS_IDENTICAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_IDENTICAL, $3, $4); }
| '(' T_IS_NOT_IDENTICAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_NOT_IDENTICAL, $3, $4); }
| '(' T_IS_EQUAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $3, $4); }
| '(' T_IS_NOT_EQUAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $3, $4); }
| '(' '<' lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $3, $4); }
| '(' T_IS_SMALLER_OR_EQUAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL, $3, $4); }
| '(' '>' lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_AST_GREATER, $3, $4); }
| '(' T_IS_GREATER_OR_EQUAL lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_AST_GREATER_EQUAL, $3, $4); }
| '(' T_SPACESHIP lisp_expr lisp_expr ')'
{ $$ = zend_ast_create_binary_op(ZEND_SPACESHIP, $3, $4); }
;

lisp_scalar:
T_LNUMBER { $$ = $1; }
| T_DNUMBER { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING { $$ = $1; }
| '"' encaps_list '"' { $$ = $2; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC { $$ = $2; }
| T_START_HEREDOC T_END_HEREDOC
{ $$ = zend_ast_create_zval_from_str(ZSTR_EMPTY_ALLOC()); }
| T_START_HEREDOC encaps_list T_END_HEREDOC { $$ = $2; }
| constant { $$ = $1; }
// | class_constant { $$ = $1; }
;

// TODO: (cond (cond1 stmts1) (cond2 stmts2))
lisp_if_stmt:
'(' T_IF lisp_expr lisp_statement ')'
{ $$ = zend_ast_create_list(1, ZEND_AST_IF,
zend_ast_create(ZEND_AST_IF_ELEM, $3, $4)); }
| '(' T_IF lisp_expr lisp_statement lisp_statement ')'
{ $$ = zend_ast_create_list(2, ZEND_AST_IF,
zend_ast_create(ZEND_AST_IF_ELEM, $3, $4),
zend_ast_create(ZEND_AST_IF_ELEM, NULL, $5)); }
;

lisp_while_stmt:
'(' T_WHILE lisp_expr lisp_statement ')'
{ $$ = zend_ast_create(ZEND_AST_WHILE, $3, $4); }
;

lisp_argument_list:
%empty { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
| lisp_non_empty_argument_list { $$ = $1; }
;

lisp_non_empty_argument_list:
lisp_argument
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
| lisp_non_empty_argument_list lisp_argument
{ $$ = zend_ast_list_add($1, $2); }
;

lisp_argument:
lisp_expr { $$ = $1; }
| name ':' lisp_expr
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
| T_ELLIPSIS lisp_expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
;

lisp_callable_expr:
name { $$ = $1; }
| T_VARIABLE { $$ = zend_ast_create(ZEND_AST_VAR, $1); }
;


lisp_function_call:
'(' lisp_callable_expr lisp_argument_list ')'
{ $$ = zend_ast_create(ZEND_AST_CALL, $2, $3); }
;

%%

/* Over-ride Bison formatting routine to give better token descriptions.
Expand Down
12 changes: 11 additions & 1 deletion Zend/zend_language_scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -2163,7 +2163,6 @@ string:
RETURN_TOKEN(T_OPEN_TAG_WITH_ECHO);
}


<INITIAL>"<?php"([ \t]|{NEWLINE}) {
HANDLE_NEWLINE(yytext[yyleng-1]);
BEGIN(ST_IN_SCRIPTING);
Expand Down Expand Up @@ -2194,6 +2193,17 @@ string:
}
}

<INITIAL>"(?lisp" {
HANDLE_NEWLINE(yytext[yyleng-1]);
BEGIN(ST_IN_SCRIPTING);
RETURN_TOKEN(T_LISP_START);
}

<ST_IN_SCRIPTING>"?)" {
BEGIN(INITIAL);
RETURN_TOKEN(T_LISP_END);
}

<INITIAL>{ANY_CHAR} {
if (YYCURSOR > YYLIMIT) {
RETURN_END_TOKEN;
Expand Down
4 changes: 4 additions & 0 deletions ext/tokenizer/tokenizer_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
REGISTER_LONG_CONSTANT("T_DOC_COMMENT", T_DOC_COMMENT, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_OPEN_TAG", T_OPEN_TAG, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_OPEN_TAG_WITH_ECHO", T_OPEN_TAG_WITH_ECHO, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_LISP_START", T_LISP_START, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_LISP_END", T_LISP_END, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_CLOSE_TAG", T_CLOSE_TAG, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_WHITESPACE", T_WHITESPACE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_START_HEREDOC", T_START_HEREDOC, CONST_CS | CONST_PERSISTENT);
Expand Down Expand Up @@ -307,6 +309,8 @@ char *get_token_type_name(int token_type)
case T_OPEN_TAG: return "T_OPEN_TAG";
case T_OPEN_TAG_WITH_ECHO: return "T_OPEN_TAG_WITH_ECHO";
case T_CLOSE_TAG: return "T_CLOSE_TAG";
case T_LISP_START: return "T_LISP_START";
case T_LISP_END: return "T_LISP_END";
case T_WHITESPACE: return "T_WHITESPACE";
case T_START_HEREDOC: return "T_START_HEREDOC";
case T_END_HEREDOC: return "T_END_HEREDOC";
Expand Down