{"id":21659634,"url":"https://github.com/brundonsmith/rust_lisp","last_synced_at":"2025-04-13T07:52:01.808Z","repository":{"id":37754533,"uuid":"276995060","full_name":"brundonsmith/rust_lisp","owner":"brundonsmith","description":"A Rust-embeddable Lisp, with support for interop with native Rust functions","archived":false,"fork":false,"pushed_at":"2023-08-03T11:57:54.000Z","size":264,"stargazers_count":250,"open_issues_count":6,"forks_count":21,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T05:47:47.025Z","etag":null,"topics":["embeddable","ffi","interpreter","lisp","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brundonsmith.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-03T22:08:14.000Z","updated_at":"2025-03-31T08:25:19.000Z","dependencies_parsed_at":"2024-11-25T09:43:13.318Z","dependency_job_id":null,"html_url":"https://github.com/brundonsmith/rust_lisp","commit_stats":{"total_commits":193,"total_committers":11,"mean_commits":"17.545454545454547","dds":"0.24352331606217614","last_synced_commit":"e0f571bd9575eb79d0dbd75552c605eaa86a6e28"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brundonsmith%2Frust_lisp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brundonsmith%2Frust_lisp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brundonsmith%2Frust_lisp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brundonsmith%2Frust_lisp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brundonsmith","download_url":"https://codeload.github.com/brundonsmith/rust_lisp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248681493,"owners_count":21144700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["embeddable","ffi","interpreter","lisp","rust"],"created_at":"2024-11-25T09:31:19.770Z","updated_at":"2025-04-13T07:52:01.779Z","avatar_url":"https://github.com/brundonsmith.png","language":"Rust","readme":"[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/brundonsmith/rust_lisp/rust.yml?branch=master)](https://github.com/brundonsmith/rust_lisp/actions)\n[![rust_lisp shield](https://img.shields.io/crates/v/rust_lisp)](https://crates.io/crates/rust_lisp)\n[![docs.rs](https://img.shields.io/docsrs/rust_lisp/latest)](https://docs.rs/rust_lisp/latest/rust_lisp/)\n\n# What is this?\n\nThis is a Lisp interpreter, written in Rust, intended to be embeddable as a\nlibrary in a larger application for scripting purposes. Goals:\n\n- Small footprint (both code size and memory usage)\n- No runtime dependencies [1]\n- Easy, ergonomic interop with native Rust functions\n- Small but practical set of Lisp functionality\n\n[1] `cfg-if` is build-time, `num-traits` add (I believe) no runtime presence,\nand `num-bigint` is entirely opt-in (at build time)\n\n# Basic Usage\n\n```rust\n[dependencies]\nrust_lisp = \"0.18.0\"\n```\n\n```rust\nuse std::{cell::RefCell, rc::Rc};\n\nuse rust_lisp::default_env;\nuse rust_lisp::parser::parse;\nuse rust_lisp::interpreter::eval;\n\nfn main() {\n\n    // create a base environment\n    let env = Rc::new(RefCell::new(default_env()));\n\n    // parse into an iterator of syntax trees (one for each root)\n    let mut ast_iter = parse(\"(+ \\\"Hello \\\" \\\"world!\\\")\");\n    let first_expression = ast_iter.next().unwrap().unwrap();\n\n    // evaluate\n    let evaluation_result = eval(env.clone(), \u0026first_expression).unwrap();\n\n    // use result\n    println!(\"{}\", \u0026evaluation_result);\n}\n```\n\nAs you can see, the base environment is managed by the user of the library, as\nis the parsing stage. This is to give the user maximum control, including\nerror-handling by way of `Result`s.\n\n# The data model\n\nThe heart of the model is `Value`, an enum encompassing every type of valid Lisp\nvalue. Most of these are trivial, but `Value::List` is not. It holds a recursive\n`List` data structure which functions internally like a linked-list.\n`into_iter()` and `from_iter()` have been implemented for `List`, and there is\nalso a `lisp!` macro (see below) which makes working with Lists, in particular,\nmuch more convenient.\n\n`Value` does not implement `Copy` because of cases like `Value::List`, so if you\nread the source you'll see lots of `value.clone()`. This almost always amounts\nto copying a primitive, except in the `Value::List` case where it means cloning\nan internal `Rc` pointer. In all cases, it's considered cheap enough to do\nliberally.\n\n# The environment and exposing Rust functions\n\nThe base environment is managed by the user of the library mainly so that it can\nbe customized. `default_env()` prepopulates the environment with a number of\ncommon functions, but these can be omitted (or pared down) if you wish. Adding\nan entry to the environment is also how you would expose your Rust functions to\nyour scripts, which can take the form of either regular functions or closures:\n\n```rust\nfn my_func(env: Rc\u003cRefCell\u003cEnv\u003e\u003e, args: \u0026Vec\u003cValue\u003e) -\u003e Result\u003cValue, RuntimeError\u003e {\n  println!(\"Hello world!\");\n  return Ok(Value::NIL);\n}\n\n...\n\nenv.borrow_mut().define(\n  Symbol::from(\"sayhello\"),\n  Value::NativeFunc(my_func)\n);\n```\n\n```rust\nenv.borrow_mut().define(\n  Symbol::from(\"sayhello\"),\n  Value::NativeFunc(\n    |env, args| {\n      println!(\"Hello world!\");\n      return Ok(Value::NIL);\n    })\n);\n```\n\nIn either case, a native function must have the following function signature:\n\n```rust\ntype NativeFunc = fn(env: Rc\u003cRefCell\u003cEnv\u003e\u003e, args: \u0026Vec\u003cValue\u003e) -\u003e Result\u003cValue, RuntimeError\u003e;\n```\n\nThe first argument is the environment at the time and place of calling (closures\nare implemented as environment extensions). The second argument is the Vec of\nevaluated argument values. For convenience, utility functions (`require_arg()`,\n`require_int_parameter()`, etc) have been provided for doing basic argument\nretrieval with error messaging. See `default_environment.rs` for examples.\n\n# The `lisp!` macro\n\nA Rust macro, named `lisp!`, is provided which allows the user to embed\nsanitized Lisp syntax inside their Rust code, which will be converted to an AST\nat compile-time:\n\n```rust\nfn parse_basic_expression() {\n  let ast = parse(\"\n    (list \n      (* 1 2)  ;; a comment\n      (/ 6 3 \\\"foo\\\"))\").next().unwrap().unwrap();\n\n  assert_eq!(ast, lisp! {\n    (list \n      (* 1 2)\n      (/ 6 3 \"foo\"))\n  });\n}\n```\n\nNote that this just gives you a syntax tree (in the form of a `Value`). If you\nwant to actually evaluate the expression, you would need to then pass it to\n`eval()`.\n\nThe macro also allows Rust expressions (of type `Value`) to be embedded within\nthe lisp code using `{  }`:\n\n```rust\nfn parse_basic_expression() {\n  let ast = parse(\"\n    (+ 3 1)\").next().unwrap().unwrap();\n\n  let n = 2;\n\n  assert_eq!(ast, lisp! {\n    (+ { Value::Int(n + 1) } 1)\n  });\n}\n```\n\nNOTE: When parsing lisp code from a string, dashes (`-`) are allowed to be used\nin identifiers. _However_, due to the limitations of declarative Rust macros,\nthese cannot be handled correctly by `lisp! {}`. So it's recommended that you\nuse underscores in your identifiers instead, which the macro will be able to\nhandle correctly. The built-in functions follow this convention.\n\nNOTE 2: The macro cannot handle the syntax for negative numbers! To get around \nthis you can insert negative numbers as Rust expressions using the escape \nsyntax, or you can parse your code as a string.\n\n# `Value::Foreign()`\n\nSometimes if you're wanting to script an existing system, you don't want to\nconvert your data to and from lisp-compatible values. This can be tedious, and\ninefficient.\n\nIf you have some type - say a struct - that you want to be able to work with\ndirectly from your lisp code, you can place it in a `Value::Foreign()` which\nallows lisp code to pass it around and (native) lisp functions to operate on it:\n\n```rust\nstruct Foo {\n    some_prop: f32,\n}\n```\n```rust\nlet v: Value = Value::Foreign(Rc::new(Foo { some_prop: 1.0 }));\n```\n\n# Included functionality\n\nSpecial forms: `define`, `set`, `defun`, `defmacro`, `lambda`, `quote`, `let`,\n`begin`, `cond`, `if`, `and`, `or`\n\nFunctions (in `default_env()`): `print`, `is_null`, `is_number`, `is_symbol`,\n`is_boolean`, `is_procedure`, `is_pair`, `car`, `cdr`, `cons`, `list`, `nth`,\n`sort`, `reverse`, `map`, `filter`, `length`, `range`, `hash`, `hash_get`,\n`hash_set`, `+`, `-`, `*`, `/`, `truncate`, `not`, `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`,\n`\u003e=`, `apply`, `eval`\n\nOther features:\n\n- Quoting with comma-escapes\n- Lisp macros\n- Tail-call optimization\n","funding_links":[],"categories":["Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrundonsmith%2Frust_lisp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrundonsmith%2Frust_lisp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrundonsmith%2Frust_lisp/lists"}