{"id":15036213,"url":"https://github.com/rzubek/cslisp","last_synced_at":"2025-04-09T23:21:51.588Z","repository":{"id":46556959,"uuid":"163775075","full_name":"rzubek/CSLisp","owner":"rzubek","description":"Scheme / Lisp implementation in pure C# for embedding in .NET projects","archived":false,"fork":false,"pushed_at":"2021-11-27T03:39:59.000Z","size":641,"stargazers_count":61,"open_issues_count":3,"forks_count":10,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-24T01:11:50.645Z","etag":null,"topics":["compiler","csharp","csharp-library","dotnet","dotnet-core","dotnet-standard","interpreter","lisp","macros","scheme"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rzubek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-01T23:55:02.000Z","updated_at":"2025-03-04T09:12:13.000Z","dependencies_parsed_at":"2022-09-06T10:10:46.545Z","dependency_job_id":null,"html_url":"https://github.com/rzubek/CSLisp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzubek%2FCSLisp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzubek%2FCSLisp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzubek%2FCSLisp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzubek%2FCSLisp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rzubek","download_url":"https://codeload.github.com/rzubek/CSLisp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248126418,"owners_count":21051918,"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":["compiler","csharp","csharp-library","dotnet","dotnet-core","dotnet-standard","interpreter","lisp","macros","scheme"],"created_at":"2024-09-24T20:30:31.859Z","updated_at":"2025-04-09T23:21:51.566Z","avatar_url":"https://github.com/rzubek.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"CSLisp\n=======\n\n**CSLisp** is a Scheme / Lisp dialect implemented in C#, intended as an embedded scripting language in .NET projects.\n\nIt is a _bytecode compiled_ language, and comes with a compiler and a bytecode interpreter. The language includes the typical Lisp-dialect features you'd expect, like proper closures, tail-call optimization, and macros. However, like Scheme, it prefers explicit boolean types, and a single namespace. \n\nDesign goals:\n- Easy to embed and use in C# / .NET - no extra dependencies\n- Safe - does not expose .NET libraries to user code unless desired\n- Fast - or at least fast _enough_ with the use of bytecode compilation :)\n- AOT friendly - does not use Reflection.Emit so it can be used in pre-compiled environments (mobile, consoles)\n- Extensible - supports macros and primitives, user primops and reflection coming soon \n\n**CSLisp** is intended to be used as a library, embedded in another host program, and not a standalone executable. The compiler, bytecode interpreter, and runtime environment, are all easy to access and manipulate from host programs. Unit tests and REPL show how to interop with it.\n\nUnlike most .NET Lisp implementations, CSLisp does **not** emit .NET bytecode, it loads text files only, and compiles to its own bytecode. This is intended for compatibility with _ahead-of-time (AOT) compiled_ environments, such as mobile and console games, which do not allow for runtime .NET IL generation or use of Reflection.Emit.\n\nLanguage implementation should be pretty readable and easy to extend. Compiler and bytecode design are heavily ~cribbed from~ influenced by Quinnec's *\"Lisp in Small Pieces\"* and Norvig's *\"Principles of Artificial Intelligence Programming\"* . Standing on the shoulders on giants. :)  \n\nThis is very much a work in progress, so please pardon the dust, use at your own risk, and so on. :)\n\n\n\n### USAGE\n\n```csharp\nContext ctx = new Context();\t// make a new vm + compiler\nctx.Execute(\"(+ 1 2)\");         // =\u003e List\u003cVal\u003e: [ 3 ]\n```\n\n\n### LANGUAGE DETAILS\n\nValues are of type `Val` and can be of the following types:\n-  Nil - a nil value which is the lack of anything else, as well as list terminator\n-  Boolean - #t or #f, same as .net bool\n-  Int - same as .Net Int32\n-  Float - same as .Net Single\n-  String - same as .Net String (immutable char sequence in double quotes)\n-  Symbol - similar to Scheme: unique symbols interned in packages\n-  Cons - pair of values\n-  Closure - non-inspectable pair of environment and compiled code sequence\n-  ReturnAddress - non-inspectable saved continuation\n-  Vector\n\nSmall set of reserved keywords - everything else is a valid symbol\n-  `begin` - used for a block of expressions, the result of the last one is returned\n-  `set!` - destructively reassigns the specified local or global symbol\n-  `if` - standard if statement, evaluates a predicate and then/else clauses\n-  `if*` - disjunctive test, evaluates a predicate and if the result is false, evaluates the rest\n-  `while` - standard while loop, unlike in other lisps this one is promoted to a reserved keyword and produces optimized bytecode\n-  `lambda` - standard closure definition\n-  `defmacro` - macros which are lisp snippets that are evaluated at compilation time, and produce more code\n-  `quote` - a quoted expression evaluates to itself\n-  `.`\n\nTail calls get optimized during compilation, without any language hints\n```lisp\n  (define (rec x) (if (= x 0) 0 (rec (- x 1))))\n  (rec 1000000) ;; look ma, no stack overflow!\n```\n\nBut of course you can also do standard boring iteration\n```lisp\n  (define (iter x) (while (\u003e x 0) (set! x (- x 1))))\n  (iter 1000000) ;; no malloc, no stack pressure\n```\n\nQuotes, quasiquotes and unquotes are supported in the Lisp fashion:\n```lisp\n  'x                 ;; =\u003e 'x\n  `x                 ;; =\u003e 'x\n  `,x                ;; =\u003e x\n  `(1 ,(list 2 3))   ;; =\u003e '(1 (2 3))\n  `(1 ,@(list 2 3))  ;; =\u003e '(1 2 3)\n```\n\nClosures\n```lisp\n  (set! fn (let ((sum 0)) (lambda (delta) (set! sum (+ sum delta)) sum))) \n  (fn 0)    ;; =\u003e 0\n  (fn 100)  ;; =\u003e 100\n  (fn 0)    ;; =\u003e 100\n```\n\nMacros are more like Lisp than Scheme. \n```lisp\n  ;; (let ((x 1) (y 2)) (+ x 1)) =\u003e \n  ;;   ((lambda (x y) (+ x y)) 1 2)\n  (defmacro let (bindings . body) \n    `((lambda ,(map car bindings) ,@body) \n      ,@(map cadr bindings)))\n```\n\nMacroexpansion - single-step and full\n```lisp\n  (and 1 (or 2 3))         ;; =\u003e 2\n  (mx1 '(and 1 (or 2 3)))  ;; =\u003e (if 1 (core:or 2 3) #f)\n  (mx '(and 1 (or 2 3)))   ;; =\u003e (if 1 (if* 2 3) #f)\n```\n\nBuilt-in primitives live in the \"core\" package and can be redefined\n```lisp\n  (+ 1 2)               ;; =\u003e 3\n  (set! core:+ core:*)  ;; =\u003e [Closure]\n  (+ 1 2)               ;; =\u003e 2\n```\n\nPackages \n```lisp\n  (package-set \"math\")       ;; =\u003e \"math\"\n  (package-get)              ;; =\u003e \"math\"\n  (package-import (\"core\"))  ;; =\u003e null\n  (package-export '(sin cos))\n```\n\nBuilt-in primitives are very bare bones (for now):\n-  Functions:\n    -  `+ - * / = != \u003c \u003c= \u003e \u003e=`\n    -  `const list append length`\n    -  `not null? cons? atom? string? number? boolean?`\n    -  `car cdr cadr cddr caddr cdddr map`\n    -  `mx mx1 trace gensym`\n    -  `eval`\n    -  `apply`\n    -  `package-set package-get package-import package-export`\n    -  `nth nth-tail nth-cons`\n    -  `first second third after-first after-second after-third rest`\n    -  `fold-left fold-right`\n    -  `reverse index-of zip`\n    -  `trace`\n-  Macros\n    -  `let let* letrec`\n    -  `define`\n    -  `and or`\n    -  `cond case`\n    -  `for dotimes`\n    -  `chain chain-list`\n\n\n### .NET INTEROP\n\n.NET interop is accomplished via several built-in primitive functions: \n  - the `..` operator which uses reflection to dereference \n    the methods/properties/fields by name, and then potentially call them \n    or retrieve their values.\n  - the `.!` operator similar to `set!` which sets fields and properties\n  - the `.new` operator which creates new instances of types\n\nInterop is a work in progress, and you can find more details in the \n[interop design.txt](interop%20design.txt) document. Meanwhile, here are some examples:\n\n```lisp\n;; simple lookups and function calls\n(.. 'System)                    ;; =\u003e object representing System namespace\n(.. 'System.DateTime)           ;; =\u003e object representing type DateTime\n(.. \"foobar\" 'Length)           ;; =\u003e 6\n(.. \"foobar\" 'ToUpper)          ;; =\u003e \"FOOBAR\"\n(.. 'System.Int32.Parse \"123\")  ;; =\u003e 123\n(.. 'System.DateTime.Now)       ;; =\u003e [new DateTime object]\n(.. (.new 'System.DateTime 1999 12 31) 'ToString \"yyyy\")    ;; =\u003e \"1999\"\n\n;; create an instance of a type\n(.new 'System.DateTime 2021 1 1)        ;; =\u003e [new DateTime object]\n(.new (.. 'System.DateTime) 2021 1 1)   ;; =\u003e [new DateTime object]\n\n;; set field or property\n(let ((array (.new 'System.Collections.ArrayList 10)))\n  (.! array 'Capacity 100)      ;; call setter on the Capacity property\n  array)                        ;; =\u003e array with capacity set to 100\n\n;; indexed getter and setter field or property\n;; uses the special Item property as defined by .Net\n(let ((array (.new 'System.Collections.ArrayList 10)))\n  (.. array 'Add 42)            ;; call array.Add(42)\n  (.! array 'Item 0 43)         ;; call indexed setter, i.e. array[0] = 43\n  (.. array 'Item 0))           ;; =\u003e 43, i.e. returns value of array[0]\n\n```\n\nIn the near future we'll add an equivalent of `using` statements, \nas well as some means for limiting which types and namespaces are accessible.\n\n\n\n### OTHER SCHEME-LIKE GOODIES\n\n##### VECTORS\n\n*Vectors* are like .Net arrays, except they hold Lisp values and have a specific printed format. But similarly to arrays, they feature constant-time access to indexed members, and they're zero-indexed and non-resizable.\n\n```lisp\n  (set! v (make-vector 3))          ;; =\u003e [Vector () () ()]   ;; i.e. 3 nil values\n  (set! v (make-vector 3 0))        ;; =\u003e [Vector 0 0 0]\n  (set! v (make-vector '(a b c)))   ;; =\u003e [Vector a b c]\n\n  (vector-length v)                 ;; =\u003e 3\n  (vector-get v 0)                  ;; =\u003e a\n  (vector-set! v 0 42)              ;; =\u003e 42\n  v                                 ;; =\u003e [Vector 42 b c]\n```\n\n##### RECORDS\n\n*Records* are objects with named fields, inspired by SRFI-9. They need to be defined first, \nin terms of fields, field accessors, constructor, and predicate. \n\n```lisp\n  ;; define a new record:\n\n  (define-record-type \n    point                       ;; record name\n    (make-point x y)            ;; constructor for 2 fields\n    point?                      ;; predicate\n    (x getx setx!)              ;; first field: name, getter, setter\n    (y gety))                   ;; second field: name, getter, but no setter (field is read only)\n\n  ;; now we can create new record instances:\n\n  (define p (make-point 1 2))   ;; =\u003e p\n  (point? p)                    ;; =\u003e #t\n  (point? 1)                    ;; =\u003e #f\n  (point? '(a b))               ;; =\u003e #f\n\n  p                             ;; =\u003e [Vector [Closure] 1 2]\n  (getx p)                      ;; =\u003e 1\n  (gety p)  ;; =\u003e 2\n\n  (setx! p 42)                  ;; =\u003e 42\n  p                             ;; =\u003e [Vector [Closure] 42 2]\n```\n  \n\n\n### TODOS\n\n- Fix bugs, add documentation (hah!)\n- Build out the standard library\n- Flesh out .NET interop \n  - need to enable blocklisting/permlisting of namespaces and types for security purposes\n  - reflection would benefit from type hints to avoid runtime inspection\n- Peephole optimizer; also optimize execution of built-in primitives.\n- Add better debugging: trace function calls, their args and return values, etc\n\n\n\n\n##### KNOWN BUGS\n\n- Error messages are somewhere between opaque and potentially misleading\n- Redefining a known macro as a function will fail silently in weird ways\n- Symbol / package resolution - eg. if a symbol \"foo\" is defined in core \n  but not in the package \"bar\", then \"bar:foo\" will resolve to \"core:foo\" \n  even though it should resolve as undefined.\n\n\n\n###  COMPILATION EXAMPLES\n\nJust a few examples of the bytecode produced by the compiler. More can be found \nby running unit tests and inspecting their outputs - they are _quite_ verbose.\n\nAlso, see [bytecode design.txt](bytecode%20design.txt) for more info.\n\n```\nInputs:  (+ 1 2)\nParsed:  (core:+ 1 2)\nCompiled:\n\n  CODE BLOCK # 42 ; () =\u003e ((+ 1 2))\n  0 MAKE_ENV  0 ; ()\n  1 PUSH_CONST  1\n  2 PUSH_CONST  2\n  3 GLOBAL_GET  +\n  4 JMP_CLOSURE 2\n\nInputs:  (+ (+ 1 2) 3)\nParsed:  (core:+ (core:+ 1 2) 3)\nCompiled:\n\n  CODE BLOCK # 43 ; () =\u003e ((+ (+ 1 2) 3))\n  0 MAKE_ENV  0 ; ()\n  1 SAVE_RETURN \"K0\"  6\n  2 PUSH_CONST  1\n  3 PUSH_CONST  2\n  4 GLOBAL_GET  +\n  5 JMP_CLOSURE 2\n6 LABEL \"K0\"\n  7 PUSH_CONST  3\n  8 GLOBAL_GET  +\n  9 JMP_CLOSURE 2\n\nInputs:  ((lambda (a) a) 5)\nParsed:  ((lambda (a) a) 5)\n\n  CODE BLOCK # 69 ; (a) =\u003e (a)\n  0 MAKE_ENV  1 ; (a)\n  1 LOCAL_GET 0 0 ; a\n  2 RETURN_VAL\n\n  CODE BLOCK # 70 ; () =\u003e (((lambda (a) a) 5))\n  0 MAKE_ENV  0 ; ()\n  1 PUSH_CONST  5\n  2 MAKE_CLOSURE  [Closure] ; #69 : (a)\n  3 JMP_CLOSURE 1\n\nInputs:  (begin (set! incf (lambda (x) (+ x 1))) (incf (incf 5)))\nParsed:  (begin (set! incf (lambda (x) (core:+ x 1))) (incf (incf 5)))\nCompiled:\n\n  CODE BLOCK # 66 ; (x) =\u003e ((+ x 1))\n  0 MAKE_ENV  1 ; (x)\n  1 LOCAL_GET 0 0 ; x\n  2 PUSH_CONST  1\n  3 GLOBAL_GET  +\n  4 JMP_CLOSURE 2\n\n  CODE BLOCK # 67 ; () =\u003e ((begin (set! incf (lambda (x) (+ x 1))) (incf (incf 5))))\n  0 MAKE_ENV  0 ; ()\n  1 MAKE_CLOSURE  [Closure] ; #66 : ((+ x 1))\n  2 GLOBAL_SET  incf\n  3 STACK_POP\n  4 SAVE_RETURN \"K0\"  8\n  5 PUSH_CONST  5\n  6 GLOBAL_GET  incf\n  7 JMP_CLOSURE 1\n8 LABEL \"K0\"\n  9 GLOBAL_GET  incf\n  10  JMP_CLOSURE 1\n\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frzubek%2Fcslisp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frzubek%2Fcslisp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frzubek%2Fcslisp/lists"}