Coder Social home page Coder Social logo

lips-scheme / lips Goto Github PK

View Code? Open in Web Editor NEW
399.0 10.0 32.0 27.33 MB

Scheme based powerful lisp interpreter in JavaScript

Home Page: https://lips.js.org

License: Other

JavaScript 50.07% Makefile 0.87% Shell 0.03% Scheme 48.77% CSS 0.26%
lisp lisp-interpreter language macros scheme support-bigint r7rs r7rs-scheme r5rs r5rs-scheme

lips's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lips's Issues

List of false is broken

The printer don't work with list of false values:

lips> (list false false)
( )

it's handled by interpreter only printer is affected:

lips> (map not (list false))
(true)

(env) in terminal is slow

If you type this in Dev tools console:

$.terminal.active().echo((await lips.exec('(env)'))[0].toString(), {keepWords: true})

it's fast but for unknow reason it's slow when typing (env) in terminal.

Parser bug when handling parser extensions

Found this funky expression in Racket Docs:

`(1 ```,,@,,@(list (+ 1 2)) 4)

Here is simplified example that don't work in LIPS

`(1 `,@(list (+ 1 2)) 4)

this works:

```(`,,,,1)

Maybe related:

lips> `(```,,,,@(list 1 2))
((quasiquote (quasiquote (quasiquote (unquote (unquote unquote 1 2))))))

vs

#|kawa:2|# `(```,,,,@(list 1 2))
((quasiquote (quasiquote (quasiquote (unquote (unquote (unquote 1 2)))))))

Can't use set-obj! on functions

You can set prototype of a function when constructing class.

My attempt to create class macro:

(define-macro (define-class spec . body)
   (let* ((class (gensym))
          (constructor-s (string->symbol "constructor"))
          (constructor (find-first (lambda (x) (eq? (car x) constructor-s)) body)))
    `(let ((,class (lambda ,@(cdr constructor))))
        (set-obj! ,class 'prototype (--> Object (create ,(cadr spec))))
        (define-global ,(car spec) ,class))))

code:

 (set-obj! ,class 'prototype (--> Object (create ,(cadr spec))))

don't work you get error that function is not an object.

Error in macroexpand

With macro:

(define-macro (define-symbol-macro . rest)
  "(define-symbol-macro (name . args) . body)
   
   Macro that creates special symbol macro for evaluator similar to build in , or `.
   It's like alias for real macro. Similar to CL reader macros but it receive already
   parsed code like normal macros."
   ;; this is executed in two different ways one when there are no macro and the other
   ;; if there is macro defined, in second case it will put list as first element
   ;; of the body even is it's called like this (define-symbol-macro (# code) 
   (let* ((def (if (pair? (car rest)) (caar rest) (car rest)))
          (symbol (car def))
          (code (cdr rest)))
     `(begin (add-special! ',symbol) (define-macro ,def ,@code))))

code:

 (macroexpand (define-symbol-macro (# arg) `(list->array (list ,@arg))))

output:

(begin
  (add-special! (quote list->array (quote )))
  (define-macro (list->array (quote list))
    (quasiquote (list->array (quote (unquote list))))
    ))

Variable documentation

it should work for any variable when using define, including syntax-rules.

(define foo 10 "this is foo variable")
(define bar null "this is bar variable")

implementations ideas:

  • global Map, WeakMap - will not work for define inside function
  • Value object - it will be not very good for interop with JS and messing with the introspection inside lips code it can be invisible to env.get, doc will be special case or it will unbox Value when it's last in chain, Value is already used in get.
  • proxy that have slot for doc, it will only work for objects.
lips.env.set('x', new Proxy(lips.LNumber(100), {}))
await lips.exec('(= x 100)')
// [true]

it will be problematic for native values.

let should work like Promise.all if called on promises

Example code:

(define title (lambda (url)
   (--> (fetch url) (text) (match /<title>([^>]+)<\/title>/) 1)))

(let ((a (title "https://jcubic.github.io/lips/"))
      (b (title "https://terminal.jcubic.pl")))
  (display a)
  (display b))

Second promise is resolved when first finish:

It should work the same as with this code:

((lambda (a b)
   (display a)
   (display b))
 (title "https://jcubic.github.io/lips/")
 (title "https://terminal.jcubic.pl"))

AJAX requests should be paralel for let, only let* and letrec can run in sequence.

Add source maps

min files should be source maps for dist file or for src file.

Make a way to easily unwrap lips numbers to native numbers

Same example as #3

(define log (. console "log"))
(define (foo x y) (log x) (log y))
(foo 10)

give

LNumber {value: 10n}
Nil {}

You can run lips code easily but it's hard to create function, like substring in lips without JS code.

JS code:

        substring: function(string, start, end) {
            return string.substring(start.valueOf(), end && end.valueOf());
        },

LIPS code:

(define (substring string start end)
  (let ((end (if (null? end) undefined ((. end "valueOf"))))
        (start ((. start "valueOf"))))
    ((. string "substring") start end)))

The other issue is that end is nil instead of undefined so you need to check if it's null?

Solution may be value function that convert nil to undefined and LNumber to number.

(define (value obj)
  (if (null? obj)
      undefined
      (if (number? obj)
          ((. obj "valueOf"))
          obj)))

(define (substring string start end)
  ((. string "substring") (value start) (value end)))

Broken example lisp code

I've tried to run this macro that I've created for scheme log ago:

(define-macro (defstruct name . fields)
  "Macro implementing structures in guile based on assoc list."
      (let ((names (map (lambda (symbol) (gensym)) fields))
	    (struct (gensym))
	    (field-arg (gensym)))
	`(if (not (every-unique ',fields))
	    (error 'defstruct "Fields must be unique")
	    (begin
	      (define (,(make-name name) ,@names)
		(map cons ',fields (list ,@names)))
	      ,@(map (lambda (field)
		       `(define (,(make-getter name field) ,struct)
			  (cdr (assq ',field ,struct)))) fields)
	      ,@(map (lambda (field)
		       `(define (,(make-setter name field) ,struct ,field-arg)
			  (assq-set! ,struct ',field ,field-arg)
			  ,field-arg)) fields)
	      (define (,(make-predicate name) ,struct)
		(and (struct? ,struct)
		     (let ((result #t))
		       (for-each (lambda (x y)
				   (if (not (eq? x y)) (set! result #f)))
				 ',fields
				 (map car ,struct))
		       result)))))))

it have few issues (found by running in demo page):

  • indent for define-macro - it should be special like define
  • parenthesis matching is broken
  • it don't evaluate this macro (show error that parenthesis are not balanced)

You can't parse special forms as list

This break parser:

lips.parse(lips.tokenize('(quasiquote list (unquote-splicing (list)))'))

because parser thinks that unquote-splicing came from read macros not from literal thing.

Add colors to pprint in terminal

It would be nice if output code from pprint have colors. the function need to be updated in terminal.js file, to not use stdout.

Getting started guide

  • All features one by one
  • Lisp intro
  • LIPS introduction for scheme and common lips programmers

list function is recursive

list function call Pair.fromArray(args) that just convert everything into list so this don't work:

(list->array (list 1 2 3 (list->array (list 4 5 6))))
;; => [1, 2, 3, (4 5 6)]

it should return:

[1, 2, 3, [4 5 6]]

list should not convert array to list. is it should be shallow.

Improvements to parser

Stuff to do in parser to allow of:

#\x '#(1 2 3) #(1 2 3) -> (vector 1 2 3)

'#(0 (2 2 2 2) "Anna")
> #(0 (2 2 2 2) Anna)

it will require to write better tokenizer that don't use regex as base or don't use specials in first stage of tokenizing: Two step parser:

lips.tokenize(`'x`)
(2) ["'", "x"]
lips.tokenize(`'#x`)
(3) ["'", "#", "x"]

first stage should just return 'x and '#x that will be matched against longest mached symbol special.

It will also require to write two type of special parser macros:

  • Symbol macros - for #\x #\ will be make-character or something like that that will crate string, symbol macros also be use for #(1 2 3) as (macro (1 2 3) because list will be not processed.
  • List macros - for #(1 2 3) that will be converted to (vector 1 2 3)

Alternative names simple-parser-macro (or literal) and splice-parser-macro.

Parser will need to be updated with both types of parer macros.

Ref from duplicate:

options for specials or types of specials:

add-special!          :key -> (make-string key)
add-list-special!     &(1 2 3) -> (address 1 2 3)

Problem invoking setTimeout as function

This don't work:

(setTimeout (lambda () (print "x")) 1000)

give error:

Illegal invocation

probably because of this context.

this works:

((. window "setTimeout") (lambda () (print "x")) 1000)

Notes:

  • you should use timer function (timer 1000 (print "x"))
  • setTimeout call valueOf on LNumber so delay work
  • in second case dot make binded version of the function so you can call it (first case use evaluate)

Sequence of parser macros

with this code:

(define-symbol-macro (# arg)
  "#(1 2 3)

   Parser macro for defining arrays."
  `(list->array (list ,@arg)))

(define-symbol-macro ({} expr)
  "(make-object :name value)

   Macro that create JavaScript object using key like syntax."
  (object-expander expr))

there is problem while executing:

#({}(:foo 10))

The problem probably is with this:

lips.parse(lips.tokenize('`(1 ,(:foo 10))'))[0].toString()
"(quasiquote (1 (unquote (:foo 10))))"
lips.parse(lips.tokenize('`(,(:foo 10))'))[0].toString()
"(quasiquote (unquote (:foo 10)))"

second code should give list with single value:

(quasiquote ((unquote (:foo 10))))

Numerical tower

TODO:

  • Complex Type
    • Complex-Float
    • Complex Integer
    • Complex Rational
      • (make-rectangular 1/2 2/4)
      • (/ 10+10i 10+2i) works in Kawa (Lips convert to Float)
      • Complex NaN e.g. +nan.0+5.0i
      • Complex Infinite e.g. 3.0+inf.0i
    • Mixed complex 1/2+0.1i (all combinations)
    • Rational Complex literal
      • inexact #i1/2+2/4i -> (exact->inexact 1/2+2/4i).
    • big num complex 10e+10i.
  • Float Type
  • Rational
  • BigInteger
  • Number literals
  • hex octal and binary and decimal literals (#b #o #x #d)
    • binary/hex/octal with complex and rational #b1/100 #b100+100i
  • big literal numbers in scientific notation should parse as big int
  • #e #i exact inexact literals that do conversion
    • #b#x #x#b replace in tokenizer (1/4 == 0.25)
    • #i#b1/100 inexact rational binary
    • big exact fractions #e1e-1000 or #e1.2e-1000
  • +nan.0 and -nan.0 return by parser
  • proper negative 0
  • Case insensitive mnemonics
  • string->number should parse all tokens #[ieoxb]
  • proper casting and all combination of operations
  • properly working string->number
  • Unit tests for operations (all types)
    • sqrt
    • abs
    • /
      • nan
      • inf
    • number->string (check if they are ok)
    • string->number
    • +
      • nan
      • inf
    • -
      • nan
      • inf
    • *
      • nan
      • inf
    • modulo integer integer (test types)
    • quotient integer integer (test types)
    • reminder int int (test types)
    • max
    • min
    • gcd integer ...
    • lcm integer ...
    • exp
    • expt
    • log
    • trigonometry
      • sin
      • cos
      • tan
      • asin
      • acos
      • atan
    • round, truncate, floor, ceiling
    • positive? / negative?
    • exact->inexact
    • inexact->exact

Bugs

Help for defined function don't remove indent

If you define macro the help is fine but for functions it include the text before the line.

lips> (help ..)
(.. foo.bar.baz)

Macro that gets value from nested object where argument is comma separated symbol
lips> (help string->symbol)
(string->symbol string)

   Function convert string to LIPS symbol.

Bad indent of let

Example code:

(let* ((nodes (document.querySelectorAll ".terminal .cmd-prompt"))
       (len nodes.length)
       (i 0))
    (while (< i len)
       (let ((div (. nodes i)))
         (print div.innerHTML)
         (++ i))))

renders when pasting as this:

(let* ((nodes (document.querySelectorAll ".terminal .cmd-prompt"))
       (len nodes.length)
       (i 0))
(while (< i len)
       (let ((div (. nodes i)))
         (print div.innerHTML)
         (++ i))))

it don't indent code inside let properly.

Parser swallow slashes while parsing strings

When --> docs is parsed

  "Helper macro that simplify calling methods on objects. It work with chaining

   usage: (--> ($ \"body\")
               (css \"color\" \"red\")
               (on \"click\" (lambda () (display \"click\"))))

          (--> document (querySelectorAll \"div\"))
          (--> (fetch \"https://jcubic.pl\") (text) (match /<title>([^<]+)<\\/title>/) 1)
          (--> document (querySelectorAll \".cmd-prompt\") 0 \"innerText\")"

it have regex as string /<title>([^<]+)<\\/title>/, the escaped slash (for string) is ignored by the parser, so you can't execute that code if you use (help -->) and copy paste.

  • fix issue
  • write unit tests

Rewrite macroexpand

macroexpand is dummy, it invoke macros and ignore names errors so it don't work with quasiquote.

It need to be rewritten using evaluate function or its duplicate. So it will have scope of macros but will not invoke functions. It need to process each macro that create new variables using evaluate.

Better format of strings in terminal

Right now they are just string output so \\ and \" are ignored and they don't look like strings but like symbols. Solution is to use JSON.stringify and replace newlines.

Error in parsing strings

This throw exception:

await lips.exec(`(list "xxxx\\ xxx" "xxxx\\'xxx")`)

escaped space and escaped quote, check jQuery Terminal.

Different types of special parser transformers

options for specials or types of specials:

add-special!          :key -> (make-string key)
add-list-special!     &(1 2 3) -> (address 1 2 3)

this will make #f and #t work also #\x

this will transform:
#(1 2 3) as list-special into

(vector 1 2 3)

other posible name is add-splice-special that remove the list maybe also symbol-special that will work only with symbols so :key will be transformed to (make-key key) but :(1 2 3) will be parse error.

Recognize JavaScript iterators by type system

type/typecheck and repr should recognize iterators.

Working check:

(define (iterator? x)
   "(iterator? x)

     Function check if value is JavaScript iterator object"
   (and (object? x) (procedure? (. x Symbol.iterator))))

Hygienic macro syntax-rules

Hygienic macro syntax-rules

  • Handling of ellipsis.
  • Handling identifiers.
  • Proper macro expand.
  • Ellipsis alias (nested syntax-rules).
  • Proper working identifiers
  • Matching literal atoms
  • Matching symbol as last cdr in pattern
  • Spread ellipsis (x ... ...)
  • SRFI 46 (named ellipsis)
  • vectors
    • pattern
    • template
  • objects
    • pattern
    • template
  • Example Usage:
    • SRFI 26
    • SRFI 156
    • define-values from R7RS spec
    • SRFI 197
    • SRFI 210
    • SRFI 239
    • R6RS do macro
    • Gauche ellipsis test from (SRFI-149)
  • Ellipsis (pattern language) extensions from R7RS
    • Escape ellipsis

List of issues

More scheme compatible

There are few things to do to make LIPS more scheme:

  • #f and #t that works with vectors (this will require to have special list and symbol parser macros) added exeption to parser they are mark as symbols and defined in R5RS.scm. they are parsed as values so you can use '(#f #t).
  • <, >, ==, <= and >= variable number of arguments
  • input/output ports + string ports
  • correct letrec, let and let*
  • list? that return false on cycles
  • hex, ocal and binary number literals
  • exact inexact literals (added using add-special!)
  • assoc and memeber functions
  • character literals - it need to be object.
  • hex character literal #\x40 ==> @
  • string objects
  • R5RS string and character functions
  • remove old empty list and replace it with proper nil that represents ()
  • '#(a b) should quote vector (added using add-special! same as #)
  • vector functions
  • R7RS Symbols in form |foo bar|
  • R7RS hex literals inside symbols and strings
  • ''#f is (quote #f)

Migrate unit tests to ava

Ava allows to run unit tests using async file load, so they can be written in Scheme.

Tests that can should be rewritten in scheme. Tests that are testing the API like parser can be written in ava in JS.

  • Quote and Quasiquote
  • Y and factorial using trampoline
  • Tokenizer (move tokenizer unit tests and extend later to test rest of the tokens)
  • Parser (extend to test all specials literals and splice)
  • Cycles
  • Scope - use code from exec code will already be parsed once
  • Parallel invocation - start async code testing with timings

Common Lisp reader macros

Consider adding reader macros same as in CL. They work like this:

 (get-macro-character #\{) → NIL, false
 (not (get-macro-character #\;)) → false
;; The following is a possible definition for the single-quote reader macro in standard syntax:

 (defun single-quote-reader (stream char)
   (declare (ignore char))
   (list 'quote (read stream t nil t))) → SINGLE-QUOTE-READER
 (set-macro-character #\' #'single-quote-reader) → T

 (defun semicolon-reader (stream char)
   (declare (ignore char))
   ;; First swallow the rest of the current input line.
   ;; End-of-file is acceptable for terminating the comment.
   (do () ((char= (read-char stream nil #\Newline t) #\Newline)))
   ;; Return zero values.
   (values)) → SEMICOLON-READER
 (set-macro-character #\; #'semicolon-reader) → T

TODO:

  • character streams
  • parser to use character stream
  • read function to use stream or string
  • parser need to be called with env (to get functions)
  • parser macros (reader macros)

R7RS: Byte vectors

ref: https://small.r7rs.org/wiki/NumericVectorsCowan/17/

Starting point:

(define (u8-vector . args)
   (Uint8Array.from (list->vector args)))

(define (make-u8-vector k . fill)
 (let ((v (new Uint8Array k)))
    (if (not (null? fill))
       (--> v (fill (car fill))))
    v))

It require core updates:

  • Native instance objects should be marked with class
  • SRFI-4
  • bytevectors
  • write unit tests

Wrong cycle detection

Naive cycle dectection don't work properly:

(let ((x '(1 2))) (list x x))
;; ==> ((1 2) #0#)

this is marked as cycle, which is wrong.
There is need to be implemented proper cycle detection algorithm.

R7RS S-Expression and Block Comments

The parser need to be modified to allow #;(foo bar) it can't be implemented using parser extensions.

Test case:

(let ((foo 10) #;(bar 20))
  (* foo 2))

Problem with quasiquote

lips> (let ((name 'x)) `(let ((name 'y)) `(list ',name)))
(let ((name (quote y)))
  (quasiquote (list (quote x))))

it should be (quote (unquote name)) the value should not be evaluated if only one unquote.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.