aboutsummaryrefslogtreecommitdiff
path: root/php
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
committerJoel Martin <github@martintribe.org>2015-03-02 21:33:10 -0600
commit835fb7d8b06e2b44792a97ac89994658bf6d00af (patch)
tree578f67726ab9e3ce5fcbc50220e9761a66c5ddf1 /php
parent6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (diff)
parent8a98ef9a3f3a6b6d05d02dc305a0c886c907e0f3 (diff)
downloadmal-835fb7d8b06e2b44792a97ac89994658bf6d00af.tar.gz
mal-835fb7d8b06e2b44792a97ac89994658bf6d00af.zip
Merge branch 'master' into gh-pages
Conflicts: .gitignore
Diffstat (limited to 'php')
-rw-r--r--php/Makefile2
-rw-r--r--php/core.php8
-rw-r--r--php/env.php8
-rw-r--r--php/printer.php4
-rw-r--r--php/reader.php2
-rw-r--r--php/step3_env.php14
-rw-r--r--php/step4_if_fn_do.php8
-rw-r--r--php/step5_tco.php8
-rw-r--r--php/step6_file.php12
-rw-r--r--php/step7_quote.php12
-rw-r--r--php/step8_macros.php20
-rw-r--r--php/step9_try.php (renamed from php/stepA_more.php)24
-rw-r--r--php/stepA_mal.php (renamed from php/step9_interop.php)54
-rw-r--r--php/tests/stepA_mal.mal25
-rw-r--r--php/types.php7
15 files changed, 139 insertions, 69 deletions
diff --git a/php/Makefile b/php/Makefile
index 9b91421..659e89c 100644
--- a/php/Makefile
+++ b/php/Makefile
@@ -2,7 +2,7 @@
TESTS =
SOURCES_BASE = readline.php types.php reader.php printer.php
-SOURCES_LISP = env.php core.php stepA_more.php
+SOURCES_LISP = env.php core.php stepA_mal.php
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
.PHONY: stats tests $(TESTS)
diff --git a/php/core.php b/php/core.php
index 4cb4667..96129b1 100644
--- a/php/core.php
+++ b/php/core.php
@@ -97,7 +97,11 @@ function concat() {
}
function nth($seq, $idx) {
- return $seq[$idx];
+ if ($idx < $seq->count()) {
+ return $seq[$idx];
+ } else {
+ throw new Exception("nth: index out of range");
+ }
}
function first($seq) {
@@ -177,6 +181,8 @@ $core_ns = array(
'false?'=> function ($a) { return _false_Q($a); },
'symbol'=> function () { return call_user_func_array('_symbol', func_get_args()); },
'symbol?'=> function ($a) { return _symbol_Q($a); },
+ 'keyword'=> function () { return call_user_func_array('_keyword', func_get_args()); },
+ 'keyword?'=> function ($a) { return _keyword_Q($a); },
'string?'=> function ($a) { return _string_Q($a); },
'pr-str'=> function () { return call_user_func_array('pr_str', func_get_args()); },
diff --git a/php/env.php b/php/env.php
index 61bedaf..a660d3b 100644
--- a/php/env.php
+++ b/php/env.php
@@ -31,7 +31,7 @@ class Env {
}
}
public function find($key) {
- if (array_key_exists($key, $this->data)) {
+ if (array_key_exists($key->value, $this->data)) {
return $this;
} elseif ($this->outer) {
return $this->outer->find($key);
@@ -40,15 +40,15 @@ class Env {
}
}
public function set($key, $value) {
- $this->data[$key] = $value;
+ $this->data[$key->value] = $value;
return $value;
}
public function get($key) {
$env = $this->find($key);
if (!$env) {
- throw new Exception("'" . $key . "' not found");
+ throw new Exception("'" . $key->value . "' not found");
} else {
- return $env->data[$key];
+ return $env->data[$key->value];
}
}
}
diff --git a/php/printer.php b/php/printer.php
index 3839931..130d31b 100644
--- a/php/printer.php
+++ b/php/printer.php
@@ -23,7 +23,9 @@ function _pr_str($obj, $print_readably=True) {
}
return "{" . implode(" ", $ret) . "}";
} elseif (is_string($obj)) {
- if ($print_readably) {
+ if (strpos($obj, chr(0x7f)) === 0) {
+ return ":".substr($obj,1);
+ } elseif ($print_readably) {
$obj = preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj));
return '"' . $obj . '"';
} else {
diff --git a/php/reader.php b/php/reader.php
index 83e0cff..ed9063f 100644
--- a/php/reader.php
+++ b/php/reader.php
@@ -40,6 +40,8 @@ function read_atom($reader) {
$str = substr($token, 1, -1);
$str = preg_replace('/\\\\"/', '"', $str);
return $str;
+ } elseif ($token[0] === ":") {
+ return _keyword(substr($token,1));
} elseif ($token === "nil") {
return NULL;
} elseif ($token === "true") {
diff --git a/php/step3_env.php b/php/step3_env.php
index 6585fe7..a31c298 100644
--- a/php/step3_env.php
+++ b/php/step3_env.php
@@ -14,7 +14,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -46,12 +46,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
return MAL_EVAL($ast[2], $let_env);
default:
@@ -73,10 +73,10 @@ function rep($str) {
return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
}
-$repl_env->set('+', function ($a, $b) { return intval($a + $b,10); });
-$repl_env->set('-', function ($a, $b) { return intval($a - $b,10); });
-$repl_env->set('*', function ($a, $b) { return intval($a * $b,10); });
-$repl_env->set('/', function ($a, $b) { return intval($a / $b,10); });
+$repl_env->set(_symbol('+'), function ($a, $b) { return intval($a + $b,10); });
+$repl_env->set(_symbol('-'), function ($a, $b) { return intval($a - $b,10); });
+$repl_env->set(_symbol('*'), function ($a, $b) { return intval($a * $b,10); });
+$repl_env->set(_symbol('/'), function ($a, $b) { return intval($a / $b,10); });
// repl loop
do {
diff --git a/php/step4_if_fn_do.php b/php/step4_if_fn_do.php
index 1e88c6a..7266261 100644
--- a/php/step4_if_fn_do.php
+++ b/php/step4_if_fn_do.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -47,12 +47,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
return MAL_EVAL($ast[2], $let_env);
case "do":
@@ -93,7 +93,7 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
// core.mal: defined using the language itself
diff --git a/php/step5_tco.php b/php/step5_tco.php
index 88557b5..a3785cf 100644
--- a/php/step5_tco.php
+++ b/php/step5_tco.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -49,12 +49,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,7 +105,7 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
// core.mal: defined using the language itself
diff --git a/php/step6_file.php b/php/step6_file.php
index 1e83c28..a27acb8 100644
--- a/php/step6_file.php
+++ b/php/step6_file.php
@@ -15,7 +15,7 @@ function READ($str) {
// eval
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -49,12 +49,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,16 +105,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/step7_quote.php b/php/step7_quote.php
index 07d3d2a..37903d0 100644
--- a/php/step7_quote.php
+++ b/php/step7_quote.php
@@ -34,7 +34,7 @@ function quasiquote($ast) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -68,12 +68,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -129,16 +129,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/step8_macros.php b/php/step8_macros.php
index aacf33e..c7e0175 100644
--- a/php/step8_macros.php
+++ b/php/step8_macros.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,7 +105,7 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
case "do":
@@ -154,16 +154,16 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/php/stepA_more.php b/php/step9_try.php
index f38656f..0470763 100644
--- a/php/stepA_more.php
+++ b/php/step9_try.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,11 +105,9 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
- case "php*":
- return eval($ast[1]);
case "try*":
$a1 = $ast[1];
$a2 = $ast[2];
@@ -174,19 +172,18 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
-rep("(def! *host-language* \"php\")");
rep("(def! not (fn* (a) (if a false true)))");
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
@@ -198,7 +195,6 @@ if (count($argv) > 1) {
}
// repl loop
-rep("(println (str \"Mal [\" *host-language* \"]\"))");
do {
try {
$line = mal_readline("user> ");
diff --git a/php/step9_interop.php b/php/stepA_mal.php
index d4a59c7..1dc3b04 100644
--- a/php/step9_interop.php
+++ b/php/stepA_mal.php
@@ -35,13 +35,13 @@ function quasiquote($ast) {
function is_macro_call($ast, $env) {
return is_pair($ast) &&
_symbol_Q($ast[0]) &&
- $env->find($ast[0]->value) &&
- $env->get($ast[0]->value)->ismacro;
+ $env->find($ast[0]) &&
+ $env->get($ast[0])->ismacro;
}
function macroexpand($ast, $env) {
while (is_macro_call($ast, $env)) {
- $mac = $env->get($ast[0]->value);
+ $mac = $env->get($ast[0]);
$args = array_slice($ast->getArrayCopy(),1);
$ast = $mac->apply($args);
}
@@ -50,7 +50,7 @@ function macroexpand($ast, $env) {
function eval_ast($ast, $env) {
if (_symbol_Q($ast)) {
- return $env->get($ast->value);
+ return $env->get($ast);
} elseif (_sequential_Q($ast)) {
if (_list_Q($ast)) {
$el = _list();
@@ -87,12 +87,12 @@ function MAL_EVAL($ast, $env) {
switch ($a0v) {
case "def!":
$res = MAL_EVAL($ast[2], $env);
- return $env->set($ast[1]->value, $res);
+ return $env->set($ast[1], $res);
case "let*":
$a1 = $ast[1];
$let_env = new Env($env);
for ($i=0; $i < count($a1); $i+=2) {
- $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ $let_env->set($a1[$i], MAL_EVAL($a1[$i+1], $let_env));
}
$ast = $ast[2];
$env = $let_env;
@@ -105,11 +105,41 @@ function MAL_EVAL($ast, $env) {
case "defmacro!":
$func = MAL_EVAL($ast[2], $env);
$func->ismacro = true;
- return $env->set($ast[1]->value, $func);
+ return $env->set($ast[1], $func);
case "macroexpand":
return macroexpand($ast[1], $env);
case "php*":
- return eval($ast[1]);
+ $res = eval($ast[1]);
+ switch (gettype($res)) {
+ case "array":
+ if ($res !== array_values($res)) {
+ $new_res = _hash_map();
+ $new_res->exchangeArray($res);
+ return $new_res;
+ } else {
+ return call_user_func_array('_list', $res);
+ }
+ default:
+ return $res;
+ }
+ case "try*":
+ $a1 = $ast[1];
+ $a2 = $ast[2];
+ if ($a2[0]->value === "catch*") {
+ try {
+ return MAL_EVAL($a1, $env);
+ } catch (Error $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->obj));
+ return MAL_EVAL($a2[2], $catch_env);
+ } catch (Exception $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->getMessage()));
+ return MAL_EVAL($a2[2], $catch_env);
+ }
+ } else {
+ return MAL_EVAL($a1, $env);
+ }
case "do":
eval_ast($ast->slice(1, -1), $env);
$ast = $ast[count($ast)-1];
@@ -156,18 +186,19 @@ function rep($str) {
// core.php: defined using PHP
foreach ($core_ns as $k=>$v) {
- $repl_env->set($k, _function($v));
+ $repl_env->set(_symbol($k), _function($v));
}
-$repl_env->set('eval', _function(function($ast) {
+$repl_env->set(_symbol('eval'), _function(function($ast) {
global $repl_env; return MAL_EVAL($ast, $repl_env);
}));
$_argv = _list();
for ($i=2; $i < count($argv); $i++) {
$_argv->append($argv[$i]);
}
-$repl_env->set('*ARGV*', $_argv);
+$repl_env->set(_symbol('*ARGV*'), $_argv);
// core.mal: defined using the language itself
+rep("(def! *host-language* \"php\")");
rep("(def! not (fn* (a) (if a false true)))");
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
@@ -179,6 +210,7 @@ if (count($argv) > 1) {
}
// repl loop
+rep("(println (str \"Mal [\" *host-language* \"]\"))");
do {
try {
$line = mal_readline("user> ");
diff --git a/php/tests/stepA_mal.mal b/php/tests/stepA_mal.mal
new file mode 100644
index 0000000..15f8a94
--- /dev/null
+++ b/php/tests/stepA_mal.mal
@@ -0,0 +1,25 @@
+;; Testing basic php interop
+
+(php* "return 7;")
+;=>7
+
+(php* "return '7';")
+;=>"7"
+
+(php* "return array(7,8,9);")
+;=>(7 8 9)
+
+(php* "return array(\"abc\" => 789);")
+;=>{"abc" 789}
+
+(php* "print \"hello\n\";")
+; hello
+;=>nil
+
+(php* "global $foo; $foo=8;")
+(php* "global $foo; return $foo;")
+;=>8
+
+(php* "global $f; $f = function($v) { return 1+$v; };")
+(php* "global $f; return array_map($f, array(1,2,3));")
+;=>(2 3 4)
diff --git a/php/types.php b/php/types.php
index e3df3ac..fa87197 100644
--- a/php/types.php
+++ b/php/types.php
@@ -53,6 +53,13 @@ class SymbolClass {
function _symbol($name) { return new SymbolClass($name); }
function _symbol_Q($obj) { return ($obj instanceof SymbolClass); }
+// Keywords
+function _keyword($name) { return chr(0x7f).$name; }
+function _keyword_Q($obj) {
+ return is_string($obj) && strpos($obj, chr(0x7f)) === 0;
+}
+
+
// Functions
class FunctionClass {