lips-scheme / lips Goto Github PK
View Code? Open in Web Editor NEWScheme based powerful lisp interpreter in JavaScript
Home Page: https://lips.js.org
License: Other
Scheme based powerful lisp interpreter in JavaScript
Home Page: https://lips.js.org
License: Other
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)
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.
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)))))))
They should never be documented but they should just works so examples in Kent Dybvig The Scheme Programming Language should just works.
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.
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))))
))
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:
lips.env.set('x', new Proxy(lips.LNumber(100), {}))
await lips.exec('(= x 100)')
// [true]
it will be problematic for native values.
(define log (. console "log"))
(define (foo x y) (log x) (log y))
(foo 10)
give
LNumber {value: 10n}
Nil {}
lambda when creating closure function should define this so you can have JS classes (using lambda as constructor function).
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.
Implement those functions, it can use already defined Value.
Object is function and it's native so it's hard bind and because of this it lost all props.
min files should be source maps for dist file or for src file.
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)))
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):
if you access code on functions that are not created by lambda it should return something.
(* 10 1.0)
this return LNumber with BigNum, it should return float.
(/ (* 10 1.0) 3)
this return 3
and it should return 3.3333333333333335
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.
this works:
(let () 10)
but this throw exception when type checking.
(let iter () 10)
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.
It should not be done by lambda but by the parser because he know what is the indent of first character. It may require in terminal to remove prompt offset from strings before evaluation.
This still works, old empty list should be replaced by nil.
(null? (cons undefined nil))
#t
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.
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:
#\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.#(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)
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:
timer
function (timer 1000 (print "x"))
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))))
TODO:
(make-rectangular 1/2 2/4)
(/ 10+10i 10+2i)
works in Kawa +nan.0+5.0i
3.0+inf.0i
#i1/2+2/4i
-> (exact->inexact 1/2+2/4i)
.10e+10i
.#b
#o
#x
#d
)
#b1/100
#b100+100i
#e
#i
exact inexact literals that do conversion
#b#x
#x#b
#i#b1/100
inexact rational binary#e1e-1000
or #e1.2e-1000
+nan.0
and -nan.0
return by parser#[ieoxb]
string->number
sqrt
abs
/
+
-
*
Bugs
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.
Add alt to LIPS logo (use alt="LIPS - Scheme Lisp Logo"
so it show up in image search results).
TODO
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.
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.
Way to reproduce:
(map 1+ (range 9))
(1 2 3 4 5 6 7 8 9 )
If you try to parse single special macro ,@
you will get parse error.
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.
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.
This throw exception:
await lips.exec(`(list "xxxx\\ xxx" "xxxx\\'xxx")`)
escaped space and escaped quote, check jQuery Terminal.
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.
(define-macro (foo x) (eq? undefined x))
(foo)
;; true
it should be nil the same as with lambdas.
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
(x ... ...)
There are few things to do to make LIPS more scheme:
#f
and #t
that works with vectors '(#f #t)
.<
, >
, ==
, <=
and >=
variable number of argumentsletrec
, let
and let*
add-special!
)#\x40
==> @
nil
that represents ()
'#(a b)
should quote vector (added using add-special!
same as #
)|foo bar|
''#f
is (quote #f)
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.
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:
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:
Found weird case of quasiquote:
`(,@'() . foo)
it should expand into symbol 'foo acording to http://community.schemewiki.org/?scheme-faq-language
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.
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))
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.