defmacro added, doc improvements

This commit is contained in:
vaclavt 2022-01-29 15:35:42 +01:00
parent 249af03f60
commit f1d102130f
9 changed files with 155 additions and 97 deletions

View File

@ -57,6 +57,7 @@ make
```
cp build/ml /usr/local/bin/ml
cp stdlib/*.lsp /usr/local/var/mlisp/
cp doc/*.md /usr/local/var/mlisp/
```
or
```
@ -69,22 +70,22 @@ utils/local_install.sh
- (read-url "https://api.nasdaq.com/api/calendar/dividends/") ; hangs in sslclient.cpp line 132
### TODO
- add functions from stdlib into doc
- string-case, save-csv fix doc
- add test for >>> (range 1.5 (inc 5.5)) => (1.500000 2.500000 3.500000 4.500000 5.500000)
- better formating of help
- unify -f and -run options
- add debug support, at least call stack
- multiline editing (see kilocpp editor)
- execute system command should capture stderr
- add some mem stats to benchmark
- add mem stats to benchmark
- order of arguments in functions like member and filter - filter swap lambda and list
- better output of !e in repl
#### Doc
- fix an error in printing multiline examples in doc
- string-case, save-csv fix doc
- add functions from stdlib into doc
- fix man entry on (member) example and retval
- doc for set!
- doc for doc::??? functions
#### Code
- tcpnet should use RAII

View File

@ -1,3 +1,13 @@
(def l '(nil 1 2 3))
(print (filter (lambda (e) (eval e)) l))
(print (filter (lambda (eval e) (print e)) l))
(defn concat-lists (seq1 seq2)
(do (def l seq1)
(dotimes i (len seq2)
(set! l (push l (index seq2 i))))
))
(print (concat-lists '(1 2 3) '(4 5 6)))
;; (1 2 3 4 5 6)
; pridat example
; >>> (doc::man "push")
; (push list element) - Add an item to the end of a list

View File

@ -28,6 +28,7 @@
|`(do a b c ...)`|`do` takes a list of s-expressions and evaluates them in the order they were given (in the current scope), and then returns the result of the last s-expression.|This special form allows lambda functions to have multi-step bodies.|Language|
|`(scope a b c ...)`|`scope` takes a list of s-expressions and evaluates them in the order they were given _in a new scope_, and then returns the result of the last s-expression.|This special form allows the user to evaluate blocks of code in new scopes.|Language|
|`(defn name params body)`|`defn` evaluates none of its arguments.|This special form allows the user to conveniently define functions.|Language|
|`(defmacro name params body)`|`defmacro` evaluates none of its arguments.|This special form allows the user to conveniently define macros.|Language|
|`(def name value)`|`def` evaluates the `value` argument, which is then assigned to `name` in the current scope.|This special form allows the user to bind atoms to values in a scope.|Language|
|`(lambda params body)`|`lambda` evaluates none of its arguments.|This special form allows the user to define anonymous functions.|Language|
|`(quote x)`|`quote` evaluates none of its arguments.|This is equivalent to the `'expr` syntactic sugar.|Language|
@ -36,6 +37,7 @@
|`(and ...)`|`and` evaluates logical and on its list argument.|`and` evaluates only until its true, it stops when becomes false.|Language|
|`(or ...)`|`or` evaluates logical or on its list argument.|`or` evaluates while its false, it stops when becomes true.|Language|
|`(benchmark msg_string ...)`|`benchmark` takes a list of s-expressions and evaluates them in the order they were given (in the current scope), and then returns the result of the last s-expression and prints msg_string and timing info.||Language|
|`(set! x)`|`set!` ...|....|Language|
## Library
|Signature|Description|Return values|Section|
@ -68,7 +70,9 @@
|`(flatten list)`|Flattens a list to single level|`>>> (flatten '(1 (2 2) 3)) => (1 2 2 3)`|List manipulation|
|`(take list count)`|Returns sublist with count element of list|`>>> (take '(1 2 3 4) 3) => (1 2 3)`|List manipulation|
|`(make-list len)`|Return list with len nil elements|`(make-list 3) => (nil nil nil)`|List manipulation|
|`(make-list-of len value)`|Return list with len value elements||List manipulation|
|`(make-list-of size value)`|Makes list with size elements of values|`>>> (make-list-of 5 0) => (0 0 0 0 0)`|List manipulation|
|`(concat-lists seq1 seq2)`|Appends all elements from seq2 to seq1|`>>> (concat-lists '(1 2 3) '(4 5 6)) => (1 2 3 4 5 6)`|List manipulation|
|`(take lst size)`|Makes sublist of a lst with size length|`>>> (take '(1 2 3 4 5 6) 3) => (1 2 3)`|List manipulation|
|`(map ..)`|Returns list that is the result of executing lambda on its elements|`(map (lambda (x) (+ x 10)) '(1 2 3 4 5 6)) => (11 12 13 14 15 16)`|List manipulation|
|`(filter lambda list)`|Returns list with elements for which passed lambda returns true|`(filter (lambda (x) (> x 2)) '(1 2 3 4 5)) => (3 4 5)`|List manipulation|
|`(reduce lambda acumulator list)`|Reduces list|`>>> (reduce (lambda (x y) (+ (* x 10) y)) 0 '(1 2 3 4)) => 1234`|List manipulation|
@ -133,20 +137,31 @@
|`(string value)`|Cast int or float item to a string|`>>> (string 3.14) => "3.14"`|Type casting|
|`(eval <exp>)`|Eval returns the value of the second evaluation|`>>> (eval '(+ 1 2)) => 3`|Language|
|`(type e)`|Returns data type of e|`>>> (type (+ 1 2)) => "int"`|Type casting|
|`(list? e)`|Returns true if type of e is list|`>>> (list? 12) => nil >>> (list? '(1 2)) => #t`|Type casting|
|`(empty-list? e)`|Returns true if type of e is empty list|`>>> (empty-list? '(1 2)) => nil >>> (empty-list? '()) => #t`|Type casting|
|`(string? e)`|Returns true if type of e is string|`>>> (string? "str") => #t`|Type casting|
|`(int? e)`|Returns true if type of e is integer|`>>> (int? "str") => nil >>> (int? 9) => #t`|Type casting|
|`(float? e)`|Returns true if type of e is float|`>>> (float? 9) => nil >>> (float? 9.0) => #t`|Type casting|
|`(nil? e)`|Returns true if type of e is nil|`>>> (nil? 9.0) => nil >>> (nil? nil) => #t >>> (nil? '()) => nil`|Type casting|
|`(true? e)`|Returns true if type of e is #t||Type casting|
|`(parse ..)`|Parses string to be evaluated|`>>> (eval (first (parse "(+ 1 2)"))) => 3`|Language|
|`(make-list-of size value)`|Makes list with size elements of values|`>>> (make-list-of 5 0) => (0 0 0 0 0)`|List manipulation|
|`(make-list size)`|Makes list of nil values with size length|`>>> (make-list 5) => (nil nil nil nil nil)`|List manipulation|
|`(empty-list? lst)`|Return true is lst is list with zero elements|`>>> (empty-list? '()) => 1`|Type casting|
|`(quick-sort-by list cmp)`|Returns list sorted by comparsion function||Language|
|`(quick-sort list)`|Return sorted list|`>>> (quick-sort '(2 4 6 1 7 3 3 9 5)) => (1 2 3 3 4 5 6 7 9)`|Language|
|`(quick-sort-reverse list)`|Return reverse sorted list|`>>> (quick-sort-reverse '(2 4 6 1 7 3 3 9 5)) => (9 7 6 5 4 3 3 2 1)`|Language|
|`(not c)`|Logical NOT of c|`>>> (not 1) => nil`|Logical|
|`(not c)`|Logical NOT of c|`>>> (not 1) => nil`|Language|
|`(neg n)`|Negates number|`>>> (neg -5) => 5`|Language|
|`(is-pos? n)`|Returns true if n is positive number||Language|
|`(is-neg? n)`|Returns true if n is negative number||Language|
|`(dec n)`|Return n decremented by 1|`>>> (dec 5) => 4`|Language|
|`(inc n)`|Return n incremented by 1|`>>> (inc 5) => 6`|Language|
|`(sleep time)`|Pauses execution for time interval of seconds||System|
|`(function? e)`|Returns true if type of e is lambda, macro or builtin|`>>> (function? index) => #t`|Language|
|`(unless test v)`|If test is not truthy then v is evaluated. This is macro.|`>>> (unless (> 1 2) "1 < 2") => "1 < 2"`|Language|
|`(dotimes v n body)`|Evaluates body n-times. For each eveluation v is set to value between 0 and n minus one|`(dotimes i 10 (print "i :" i))`|Language|
|`(doc::man "func_name")`|Prints short help for func_name||Language|
|`(doc::appropos "string")`|Looks for functions to be related passed string. String can be more words separated by space||Language|
|`(doc::look "string")`|Alias for appropos||Language|
|`(doc::lookup "string")`|Alias for appropos||Language|
|`(get-env var)`|Return environment variable var|`>>> (get-env "HOME") => "/Users/vaclavt"`|System|
|`(second list)`|Returns second element of list|`>>> (second '(1 2 3 4 5 6 7)) => 2`|List manipulation|
|`(third list)`|Returns third element of list|`>>> (third '(1 2 3 4 5 6 7)) => 3`|List manipulation|
@ -160,5 +175,4 @@
|`(thread-sleep milisecons)`|Sleeps thread for given amount of miliseconds||Threading|
|`(threads-join)`|Wait for all running threads to finish||Threading|
|`(try block catch_block [finally_block])`|Evaluates block and if an exception is thrown, evaluates catch_block.Eventually in both cases evals finally_block. Return evaluated last expression from block if no exception or last expression from catch_block, if exception is thrown from block. Variable ml-exception in catch block is available with astring describing exception||Exceptions|
|`(throw-exception exp_desc)`|Throws an exception with exp_desc describing what happened ||Exceptions|
|`(xx ..)`|Desc|example|section|
|`(throw-exception exp_desc)`|Throws an exception with exp_desc describing what happened ||Exceptions|

130
ml.cpp
View File

@ -25,6 +25,7 @@
#include <chrono>
#include <thread>
#include <mutex>
#include <numeric>
#define TOO_FEW_ARGS "too few arguments to function"
@ -47,6 +48,8 @@
#define INDEX_OUT_OF_RANGE "index out of range"
#define MALFORMED_PROGRAM "malformed program"
#define NOT_IMPLEMENTED_YET_ERROR "not implemented yet"
#define DIVISION_BY_ZERO "division by zero"
#define INVALID_HEADER_FORMAT "invalid header format"
#define STRING_TYPE "string"
@ -123,7 +126,7 @@ MlValue MlValue::nil() {
}
// Construct a lambda function
MlValue::MlValue(const std::vector<MlValue> &params, MlValue ret, MlEnvironment const &env) : type(LAMBDA) {
MlValue::MlValue(const std::vector<MlValue> &params, MlValue ret, MlEnvironment const &env, const Type ftype) : type(ftype) {
// We store the params and the result in the list member
// instead of having dedicated members. This is to save memory.
list.emplace_back(params);
@ -162,7 +165,8 @@ std::vector<std::string> MlValue::get_used_atoms() {
result.push_back(as_atom());
return result;
case LAMBDA:
// If this is a lambda, get the list of used atoms in the body
case MACRO:
// If this is a lambda or macro, get the list of used atoms in the body
// of the expression.
return list[1].get_used_atoms();
case LIST:
@ -184,6 +188,10 @@ bool MlValue::is_builtin() const {
return type == BUILTIN;
}
bool MlValue::is_macro() const {
return type == MACRO;
}
bool MlValue::is_number() const {
return type == INT || type == FLOAT;
}
@ -318,8 +326,9 @@ bool MlValue::operator==(MlValue other) const {
// data in the str member.
return str == other.str;
case LAMBDA:
case MACRO:
case LIST:
// Both lambdas and lists store their
// All lambdas, macros and lists store their
// data in the list member.
return list == other.list;
case QUOTE:
@ -526,9 +535,10 @@ std::string MlValue::get_type_name() const {
return STRING_TYPE;
case BUILTIN:
case LAMBDA:
case MACRO:
// Instead of differentiating between
// lambda and builtin types, we group them together.
// This is because they are both callable.
// lambda, macro and builtin types, we group them together.
// This is because they are all callable.
return FUNCTION_TYPE;
case NIL:
return NIL_TYPE;
@ -541,7 +551,6 @@ std::string MlValue::get_type_name() const {
}
std::string MlValue::display() const {
std::string result;
switch (type) {
case QUOTE:
return "'" + list[0].debug();
@ -554,17 +563,14 @@ std::string MlValue::display() const {
case STRING:
return str;
case LAMBDA:
for (size_t i = 0; i < list.size(); i++) {
result += list[i].debug();
if (i < list.size() - 1) result += " ";
}
return "(lambda " + result + ")";
return "(lambda " + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
case MACRO:
return "(macro " + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
case LIST:
for (size_t i = 0; i < list.size(); i++) {
result += list[i].debug();
if (i < list.size() - 1) result += " ";
}
return "(" + result + ")";
return "(" + std::accumulate(list.begin(), list.end(), std::string(), [](const std::string &a, const MlValue &b) -> std::string
{return a + (a.empty() ? "" : " ") + b.debug(); }) + ")";
case BUILTIN:
return "<" + str + " at " + std::to_string(long(stack_data.b)) + ">";
case NIL:
@ -580,41 +586,15 @@ std::string MlValue::display() const {
std::string MlValue::debug() const {
std::string result;
switch (type) {
case QUOTE:
return "'" + list[0].debug();
case ATOM:
return str;
case INT:
return std::to_string(stack_data.i);
case FLOAT:
return std::to_string(stack_data.f);
case STRING:
result.reserve(str.size());
for (char c : str) {
if (c == '"') result += "\\\"";
else result.push_back(c);
}
return "\"" + result + "\"";
case LAMBDA:
for (size_t i = 0; i < list.size(); i++) {
result += list[i].debug();
if (i < list.size() - 1) result += " ";
}
return "(lambda " + result + ")";
case LIST:
for (size_t i = 0; i < list.size(); i++) {
result += list[i].debug();
if (i < list.size() - 1) result += " ";
}
return "(" + result + ")";
case BUILTIN:
return "<" + str + " at " + std::to_string(long(stack_data.b)) + ">";
case NIL:
return "nil";
case TRUE:
return "#t";
default:
// This should never be reached.
throw MlError(*this, MlEnvironment(), INTERNAL_ERROR);
return display();
}
}
@ -646,19 +626,15 @@ const char * MlError::what() const noexcept {
}
void MlEnvironment::combine(MlEnvironment const &other) {
// Normally, I would use the `insert` method of the `map` class,
// but it doesn't overwrite previously declared values for keys.
auto itr = other.defs.begin();
for (; itr != other.defs.end(); itr++) {
// Iterate through the keys and assign each value.
// We need to overwrite previously declared values for keys.
for (auto itr = other.defs.begin(); itr != other.defs.end(); itr++) {
defs[itr->first] = itr->second;
}
}
std::ostream &operator<<(std::ostream &os, MlEnvironment const &e) {
auto itr = e.defs.begin();
os << "{ ";
for (; itr != e.defs.end(); itr++) {
for (auto itr = e.defs.begin(); itr != e.defs.end(); itr++) {
os << '\'' << itr->first << "' : " << itr->second.debug() << ", ";
}
return os << "}";
@ -687,8 +663,11 @@ void MlEnvironment::setX(const std::string &name, const MlValue& value) {
MlValue MlValue::apply(std::vector<MlValue> args, MlEnvironment &env) {
MlEnvironment e;
std::vector<MlValue> params;
MlValue macro_eval;
switch (type) {
case LAMBDA:
case MACRO:
// Get the list of parameter atoms
params = list[0].list;
if (params.size() != args.size())
@ -708,8 +687,17 @@ MlValue MlValue::apply(std::vector<MlValue> args, MlEnvironment &env) {
e.set(params[i].str, args[i]);
}
// Evaluate the function body with the function scope
return list[1].eval(e);
if (type == LAMBDA) {
// Evaluate the function body with the function scope
return list[1].eval(e);
} else {
// macro evals twice
macro_eval = list[1].eval(e);
if (MlPerfMon::instance().isDebugOn())
std::cout << std::endl << "DEBUG macro 1st eval: " << macro_eval.debug() << std::endl << std::endl;
return macro_eval.eval(e);
}
case BUILTIN:
// Here, we call the builtin function with the current scope.
// This allows us to write special forms without syntactic sugar.
@ -740,11 +728,11 @@ MlValue MlValue::eval(MlEnvironment &env) {
args = std::vector<MlValue>(list.begin() + 1, list.end());
function = list[0].eval(env);
if (function.type == BUILTIN || function.type == LAMBDA) {
if (function.type == BUILTIN || function.type == LAMBDA || function.type == MACRO) {
// Only evaluate our arguments if it's not builtin!
// Builtin functions can be special forms, so we
// leave them to evaluate their arguments.
if (!function.is_builtin())
if (!function.is_builtin() && !function.is_macro())
for (auto & arg : args)
arg = arg.eval(env);
@ -785,7 +773,6 @@ MlValue parse(std::string &s, int &ptr) {
if (s[ptr] == ';')
throw std::runtime_error(INTERNAL_ERROR);
if (s.empty()) {
return MlValue();
@ -906,8 +893,7 @@ MlValue run(const std::string &code, MlEnvironment &env) {
if (parsed.empty())
return MlValue::nil();
// Iterate over the expressions and evaluate them
// in this environment.
// Iterate over the expressions and evaluate them in this environment.
for (size_t i = 0; i < parsed.size() - 1; i++)
parsed[i].eval(env);
@ -936,7 +922,7 @@ MlValue lambda(std::vector<MlValue> args, MlEnvironment &env) {
if (args[0].get_type_name() != LIST_TYPE)
throw MlError(MlValue("lambda", lambda), env, INVALID_LAMBDA);
return MlValue(args[0].as_list(), args[1], env);
return MlValue(args[0].as_list(), args[1], env, MlValue::Type::LAMBDA);
}
// if-else (SPECIAL FORM)
@ -992,7 +978,21 @@ MlValue defun(std::vector<MlValue> args, MlEnvironment &env) {
if (args[1].get_type_name() != LIST_TYPE)
throw MlError(MlValue("defn", defun), env, INVALID_LAMBDA);
MlValue f = MlValue(args[1].as_list(), args[2], env);
MlValue f = MlValue(args[1].as_list(), args[2], env, MlValue::Type::LAMBDA);
env.set(args[0].display(), f);
return f;
}
// Define a macro with parameters and a result expression (SPECIAL FORM)
MlValue defmacro(std::vector<MlValue> args, MlEnvironment &env)
{
if (args.size() != 3)
throw MlError(MlValue("defmacro", defmacro), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
if (args[1].get_type_name() != LIST_TYPE)
throw MlError(MlValue("defmacro", defmacro), env, INVALID_LAMBDA);
MlValue f = MlValue(args[1].as_list(), args[2], env, MlValue::Type::MACRO);
env.set(args[0].display(), f);
return f;
}
@ -1213,8 +1213,10 @@ MlValue read_url(std::vector<MlValue> args, MlEnvironment &env) {
// headers
if (args.size() > 1) {
for (const auto &hdr_val_pair: args[1].as_list()) {
// TODO check its 2 string elements list
const auto &pair = hdr_val_pair.as_list();
if (pair.size() != 2)
throw MlError(MlValue("read_url", read_url), env, INVALID_HEADER_FORMAT);
headers[pair[0].as_string()] = pair[1].as_string();
}
}
@ -1466,7 +1468,7 @@ MlValue divide(std::vector<MlValue> args, MlEnvironment &env) {
if ((args[1].get_type_name() == "int" && args[1] == 0l) ||
(args[1].get_type_name() == "float" && args[1] == 0.0))
throw std::invalid_argument("divide by zero.");
throw std::invalid_argument(DIVISION_BY_ZERO);
return args[0] / args[1];
}
@ -1952,7 +1954,6 @@ MlValue range(std::vector<MlValue> args, MlEnvironment &env) {
// Benchmarks a block of expressions in the current environment (SPECIAL FORM)
MlValue benchmark(std::vector<MlValue> args, MlEnvironment &env) {
// TODO add some memory stats
using namespace std::chrono;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
@ -2110,6 +2111,7 @@ std::map <const std::string, Builtin> builtin_funcs
std::make_pair("scope", builtin::scope),
std::make_pair("quote", builtin::quote),
std::make_pair("defn", builtin::defun),
std::make_pair("defmacro", builtin::defmacro),
std::make_pair("and", builtin::do_and),
std::make_pair("or", builtin::do_or),
std::make_pair("set!", builtin::setx),
@ -2225,7 +2227,7 @@ std::map <const std::string, Builtin> builtin_funcs
// Get the value associated with this name in this scope
MlValue MlEnvironment::get(const std::string &name) const {
// PERF, here can be a few of for fast access
if (name == "defe") return MlValue("def", builtin::define);
if (name == "def") return MlValue("def", builtin::define);
if (name == "if") return MlValue("if", builtin::if_then_else);
if (name == "lambda") return MlValue("lambda", builtin::lambda);

32
ml.h
View File

@ -102,7 +102,21 @@ typedef MlValue (*Builtin)(std::vector<MlValue>, MlEnvironment &);
class MlPerfMon;
class MlValue {
public:
enum Type {
QUOTE,
ATOM,
INT,
FLOAT,
LIST,
STRING,
LAMBDA,
MACRO,
BUILTIN,
NIL,
TRUE
} type;
public:
MlValue(); // Constructs a nil value
MlValue(long i);
@ -116,7 +130,7 @@ public:
static MlValue string(const std::string &s);
static MlValue nil();
MlValue(const std::vector<MlValue> &params, MlValue ret, MlEnvironment const &env); // Construct a lambda function
MlValue(const std::vector<MlValue> &params, MlValue ret, MlEnvironment const &env, const Type ftype); // Construct a lambda or macro function
MlValue(const std::string &name, Builtin b); // Construct a builtin function
std::vector<std::string> get_used_atoms();
@ -128,6 +142,7 @@ public:
MlValue eval(MlEnvironment &env);
bool is_builtin() const;
bool is_macro() const;
bool is_number() const;
bool is_string() const;
bool is_list() const;
@ -175,19 +190,6 @@ public:
friend std::ostream &operator<<(std::ostream &os, MlValue const &v);
private:
enum {
QUOTE,
ATOM,
INT,
FLOAT,
LIST,
STRING,
LAMBDA,
BUILTIN,
NIL,
TRUE
} type;
union {
long i;
double f;

View File

@ -31,6 +31,7 @@ public:
void turnOn();
void debugOn();
bool isDebugOn() { return debugStacktraceOn; }
void add_method_call(const MlValue &function, const std::vector<MlValue> &args);
void end_method_call();

View File

@ -86,6 +86,9 @@
(defn doc::appropos (which)
(doc::look which))
(defn doc::lookup (which)
(doc::look which))
;(defn doc::section (which)
; (print (term-red "implement me!")))

View File

@ -60,6 +60,26 @@
(defn tenth (l) (index l 9))
(defn nth (i l) (index l (- i 1)))
(defn list? (e) (= (type e) "list"))
(defn empty-list? (e) (and (list? e) (= (len e) 0)))
(defn string? (e) (= (type e) "string"))
(defn int? (e) (= (type e) "int"))
(defn float? (e) (= (type e) "float"))
(defn nil? (e) (= (type e) "nil"))
(defn true? (e) (= (type e) "#t"))
(defn function? (e) (= (type e) "function"))
(defmacro unless (test v)
(list 'if (list 'not test) v))
(defmacro dotimes (v n body)
(list 'for v '(range 0 (eval n))
body
))
; return 1 when list contains item otherwise nil
(defn member (lst itm)
(do
@ -92,8 +112,6 @@
(defn make-list (size)
(make-list-of size nil))
(defn empty-list? (lst) (and (= (type lst) "list") (= (len lst) 0)))
(defn uniq (lst)
(do
(def rslt '())
@ -126,6 +144,12 @@
(if (> (len lst) n)
(map (lambda (i) (index lst i)) (range 0 n))
lst))
(defn concat-lists (seq1 seq2)
(do (def l seq1)
(dotimes i (len seq2)
(set! l (push l (index seq2 i))))
))
(defn quick-sort-by (l cmp)
(if (<= (len l) 1)
@ -253,5 +277,6 @@
)
))
; load and init do system
; load and init doc system
(include "/usr/local/var/mlisp/doc.lsp")

View File

@ -51,7 +51,7 @@
</dict>
</dict>
<key>match</key>
<string>(?:\()((?i:defn|def|lambda)+)</string>
<string>(?:\()((?i:defn|defmacro|def|lambda)+)</string>
<key>name</key>
<string>meta.function.lisp</string>
</dict>