jscl-project / jscl Goto Github PK
View Code? Open in Web Editor NEWA Lisp-to-JavaScript compiler bootstrapped from Common Lisp
Home Page: https://jscl-project.github.io
License: GNU General Public License v3.0
A Lisp-to-JavaScript compiler bootstrapped from Common Lisp
Home Page: https://jscl-project.github.io
License: GNU General Public License v3.0
CL incf semantics is different from what jscl provides; for example
(let ((x 0))
(incf x (setf x 1))
x)
should return 2 in a CL implementation and instead jscl returns 1.
Not sure how easy this would be to fix because in Javascript x=0; x += (x = 1); results in 1.
The subtlety is that (incf x y) where x and y are arbitrary expressions is not evaluated "left-to-right", but the evaluation of x is split in two steps: evaluating the place and getting the value. The place x is evaluated before y, but the value of x used for incf is evaluated after y.
This one, together the lambda list, will be useful to get a more friendly REPL.
Not obvious at all to a non-web person like myself.
Floats ending in .0
get read in as ints:
CL-USER> (read-from-string "1.0")
1
Various functions in JSCL, such as CAR
and CONS
are defined as builtins, and the function definition is simply a wrapper around this builtin, so that they are first-class objects. The definition therefore looks like:
(defun cons (x y) (cons x y))
I was attempting to do the same for <
, >
, etc. How would this work with variadic functions such as these?
Attempting to do the same as above:
(defun < (x y) (< x y))
would restrict it to only two arguments. Using APPLY
:
(defun < (x &rest args) (apply #'< x args))
wouldn't work, as APPLY
expects a function, not a builtin. Should I just redefine the functions myself, like:
(defun < (x &rest args)
(while args
... ))
? Or is there some other solution I'm missing?
See http://asmjs.org/ for specs, FAQ and presentation.
It is useful to use another name for existing functions in the host Lisp implementation. So we will be able to use it and test it interactively without compiling JSCL once and again.
We could use !
to prefix such symbols and rename them in the end of the bootstrap.
Currently, we have the internal js-vset
and js-vref
special forms, as well as oget
and oset
functions to reference Javascript variables and access to Javascript objects respectively. We have not special forms to call Javascript functions and access Javascript objects yet.
It would be to clean this interface, add the missing features and export it in a FFI package.
If you want to take this, please discuss the API here.
The current backquote macroexpansion is naive and very inefficient. For example,
`(,1 ,2 ,3 ,4) ;; ==
(append (list 1) (list 2) (list 3) (list 4))
For reference, CLTL has a better implementation by Steele:
http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node367.html
Hi,
I've been (slowly) working away at a very simple OO system to help implement streams per #56. It's based around alists and closures: very simple, very minimal & inefficient algorithms to get things going.
While it's very enjoyable & instructive building an OO system, I'm wondering if this is something worth pursuing beyond just basic scaffolding to make streams play nice.
Essentially, should JSCL support CLOS, or should it have its own OO system? I don't know the licensing definitions for extant CLOS implementations and also the effort level to integrate them into a Lisp system.
It might be fun from a hacking perspective to investigate systems like Flavors and T and have a core "jscl:system-object" substrate which all the object systems support at core.
Anyway, RFC.
Is ECMALisp intended to be Common Lisp or an approximation thereof?
Answering this question would allow people to choose to use/contribute to ECMALisp (or not) based on their preferences for CL.
Seems like TRUNCATE is called infinite times by INTEGER-TO-STRING..
Not sure, but I think the problem is that in javascript 1/0 is Infinity, which in fact is a number (typeof Infinity == 'number'). ¿Any ideas?
Reported by Alexander Shendi. It yields:
Script started on Wed 01 May 2013 02:48:58 AM UTC
Welcome to Clozure Common Lisp Version 1.9 (LinuxARM32)!
? (load "jscl.lisp")
;Compiler warnings for "src/read.lisp" :
; In READ-FLOAT: Undeclared free variable IT (4 references)
Error: Undefined function COLLECT called with arguments ((DEFUN BINDING-NAME (X) (UNLESS (BINDING-P X) (ERROR "The object is not a type BINDING")) (NTH 1 X))) .
While executing: DEF!STRUCT, in process listener(1).
Type :GO to continue, :POP to abort, :R for a list of available restarts.
If continued: Retry applying COLLECT to ((DEFUN BINDING-NAME (X) (UNLESS (BINDING-P X) (ERROR "The object is not a type BINDING")) (NTH 1 X))).
Type :? for other options.
1 > (quit)
It looks that js source map support becomes a standard now. At least Chrome and Firefox have support for it. And source maps are really useful for debugging purposes.
Several links about standard and current implementations can be found here:
https://github.com/ryanseddon/source-map/wiki/Source-maps%3A-languages%2C-tools-and-other-info
Tried to add a test:
(test (= 42 (progn
(eval '(defun test-fun (x y)
(+ x y)))
(eval '(test-fun 40 2)))))
Turns out TEST-FUN and Y end up uninterned, causing a somewhat opaque error in the js console: [18:53:49.921] uncaught exception: Variable `G597' is unbound.
(Added a PRINT around the expansion of the DEFUN to observe this.)
CL-USER> (flet ((a () ())) (defmacro b () (a)))
B
CL-USER> (b)
ERROR: Function `A' is undefined.
The defined macro should close over the function bound by FLET
. Closures over functions in other cases seem to work fine though, e.g.:
CL-USER> (defvar foo (flet ((a () ())) (lambda () (a))))
FOO
CL-USER> (funcall foo)
NIL
so I think it's just an issue with DEFMACRO
The codes below should return the /multiple values/ 1, 2 and 3, but return and throw discard all but the primary value.
(block nil
(return (values 1 2 3)))
(catch nil
(throw nil (values 1 2 3)))
Currently EQUAL is quite broken... e.g.
(equal '(1 2) '(1 2)) ==> nil
I tried to provide a better implementation (IIUC now is just javascript "==", a basically useless operator) but failed.
Where should be placed an implementation of EQUAL? I don't think I understand the boot process used by jscl.
Use the recent support of keyword symbols to implement keyword arguments to functions. It is probably better not to support &allow-other-keys and :allow-other-keys-p in the beginning, to keep it simple.
In the README you specify that JSCL will remain a subset, which implies that there are things you consider to be a bad match for what you want JSCL to do.
One of the things that I would like to see clarified is the future of numbers; do you intend to stick with js numbers only, extend them with eg. complexes, or do you plan on untangling integers and floats and implementing all of CL's number types?
(I think there are perfectly valid reasons for picking any of these -- depending on your goals. I'm absolutely not trying to imply that you should implement the whole panoply of numeric types.)
For example:
(function car)
#<FUNCTION>
(function rplaca)
ERROR: Function `RPLACA' is undefined
Interestingly, in src/list.lisp, many built-in functions (like CAR and CDR) are redefined (with DEFUN) using their built-in functions of the same name, which suggests we could simply wrap all built-in functions with normal functions.
That does work for fixing RPLACA in this particular case, but I'm not sure that's the right solution overall.
Currently the reader has a problem with symbol name escaping. For example
|abc:def|
is read by CL as a regular symbol, the same as
abc\:def
In a similar way you can have a symbol that contains terminal characters, like
\:-\)
a symbol with name ":-)"
The current approach for reading symbols is skip characters up to the first terminal (with hardcoded terminal meaning) and then pass the string to read-symbol that will split on ":" to find an optional package name.
Is jscl instead aiming at the same (and more complex) reading rules of cl?
The difference in behaviour between readers creates also different code when cross-compiling using CL to javascript in respect to using jscl compiled compiler.
The RANDOM function, MAKE-RANDOM-STATE, etc are not implemented.
I think RANDOM could probably just be a thin wrapper around javascript's Math.random()?
I got stuck today compiling a function that used a DO* loop, and kept whittling away at it until I had a pretty minimal DO loop:
(defun jscl-breaker ()
(do ((x))
(t 'result)))
In SBCL, this returns the symbol RESULT, which is what I expect. In JSCL, I get "ERROR: syntax error".
The compiled JS line that it's unhappy with seems to be:
return ();
but I haven't investigated enough to find out exactly where this is coming from.
Typing just in the REPL demo one gets "ERROR: Variable `G1' is unbound."
Currently all kind of forms are being defined in boot.lisp, but as supported forms increase so will be this file. I propose to split this file in different ones separated by topic, which should follow CLHS chapter structure, i.e. numbers.lisp should contain forms in numbers dictionary, conses.lisp those in conses dictionary, and so on.
CL-USER> (remove-if-not (lambda (x) (> x 1)) '(1 2 3 4))
(2 3 4)
CL-USER> (remove-if-not (lambda (x) (> x 1)) #(1 2 3 4))
ERROR: Not a number!
Characters are just integers right now
#\newline => 10
Strings are implemented using literal Javascript strings. It is nice for FFI, but it is not correct as Javascript has inmutable strings.
Why arithmetic operations are applied only to two arguments?
I tried to rewrite it like
(defun plus (&rest args)
(if args
(+ (car args) (apply #'plus (cdr args)))
0))
But argument counter considers that it can't be more than two :(
Looking at the generated code I noticed that exceptions used for non-local control transfers (e.g. return-from) use a numeric ID related to lexical context to recognize the jump target.
This is not correct in case of recursive functions. For example with
(defun foo (x)
(when x
(funcall x))
(foo (lambda ()
(return-from foo 1)))
(return-from foo 2))
calling (foo NIL)
should return 1 (like it does on SBCL) and not 2 (like it does on JSCL) because the return-from
in the lambda should exit the toplevel foo and not only the nested call.
The solution I implemented for this problem in my lisp dialect targeting javascript is to use a unique object (an empty array) created on tagbody
entry to identify jump targets so that a closure capturing a target will properly handle recursive functions.
Streams aren't implemented.
http://www.lispworks.com/documentation/lw51/CLHS/Body/v_debug_.htm#STstandard-outputST
I'm inclined to create an ad-hoc closure-based object system to handle streams. standard-output, *error-output, and trace-output would be linked up to the existing WRITE function in JSCL via one of those closures. It doesn't appear that JSCL supports input-via-cl quite yet.
A number of other functions such as make-*-stream should also be supported with this adhoc system.
Would appreciate comments on this approach.
One concern I have is that it might be good to wait until DEFSTRUCT is implemented.
The prin* family isn't implemented.
http://www.lispworks.com/documentation/lw51/CLHS/Body/f_wr_pr.htm
However, to be properly implemented, they require a stream designator.
Calls to the compiler generate Javascript code directly. Several tricks are used to make sure composability. For example, a construction like
(function(){
...
return ...;
})()
is used very often in order to return only Javascript expressions. However it yields very verbose code. Writing a Javascript unparser would fix this problem and would allow some useful features like minimization.
Dotted lists missing the final close-paren aren't detected by the reader:
CL-USER> '(1 . 2
(1 . 2)
CL-USER> '(1 2 3 4 . 5
(1 2 3 4 . 5)
There's also an unhelpful error message in the case where the list element after the dot is missing entirely:
CL-USER> '(1 .
ERROR: `"G1"' is not a symbol.
Since it seems you are interested in implementing a complete CL (issue #40), it might be interesting to evaluate modules of the language in SICL that are complete and whose dependencies are already satisfied. These projects seem complementary to me in that SICL is largely an implementation of a high-quality, conforming, shared CL standard library and JSCL is a new and growing implementation in need of one. This could be a very exciting and energizing collaboration for both projects.
Current macros lambda lists are ordinary ones. Implement extended lambda lists in macros and destructuring-bind
Using a tonight edition of JSCL:
CL-USER> (cons :typename 10)
ERROR: l518 is not defined
CL-USER>
However, I can use :typename-2 just fine.
The reader and the printer are missing float numbers support. In fact, the reader does not support negative numbers, so -1 is read as a symbol.
It is true for both looking for the sources and generating the output files. It should work from other directories probably. Use the CL variable *load-pathname*
.
I was going to implement some of the long list of trivial missing functions, but I'm unsure about where they belong in the source tree. Should I just add a new file in src
with related functions in it, add a :target
entry to *source*
and add the functions I defined to the export
list in toplevel.lisp
? Or is there some other place they should go? Maybe a HACKING
/CONTRIBUTING
file in the root directory would be useful to explain this.
There is a special case for #. in %read-list that I don't understand and that creates a bug when reading a list with floating point numbers without integral part.
(read-from-string "(1 .25)") ==> (1 . 25)
(read-from-string ".25") ==> 0.25
Fixed in my repo. Changes read.lisp and tests/read.lisp.
Use single-quoted or double-quoted for literal strings depending on the string content.
It would be good to have a benchs/ directory with some benchmarking. It is similar to tests/ but for measuring the performance. Nothing sophisticated, but I want to improve the compiler with some optimizations and measures are needed.
Looking at the old MIT/symbolics loop macro implementation http://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/iter/loop/symbolix/, written in ANSI lisp. I hacked on it a bit, and it seems like it might work in JSCL, but it depends on having &optional handled correctly in defmacro lambda lists.
Currently the following fails when it tries to compile-funcall on ((head-var ...
(defmacro with-loop-list-collection-head ((head-var tail-var &optional user-head-var) &body body)
(let ((l (and user-head-var (list (list user-head-var nil)))))
`(let* ((,head-var (list nil)) (,tail-var ,head-var) ,@l)
,@body)))
The error-checking for keyword arguments gives an incorrect error message, when passed an unknown keyword:
CL-USER> (make-array 3)
#(NIL NIL NIL)
CL-USER> (make-array 3 :element-type t)
#(NIL NIL NIL)
CL-USER> (make-array 3 :element-type t :oops 55)
ERROR: Unknown keyword argument ELEMENT-TYPE
CL-USER> (make-array 3 :oops 55)
ERROR: x is undefined
It looks like it might be an off-by-one error, but I'm not sure where.
Put a function like this in one of the Lisp source files:
(defun f ()
"let's go"
())
and run (jscl:bootstrap). In jscl.js, this gets compiled to:
func.fname = 'F';
func.docstring = 'let's go';
which obviously fails to run.
When done in the "JSCL.html" repl, it also fails: "ERROR: missing ; before statement".
So far we have been using lists to store the macros and dump them to the output file in the bootstrap. We should compile the macros in the end of the process and dump reference to the macro-expanders instead.
I wanted to write a test like this:
(test (let ((x (cons 1 2)))
(eql (setf (car x) 0) 0)))
The inner expression (without the "test" wrapper) works fine in the repl. The expression as a test, though, gives a failure (in Firefox) like: "TypeError: l2370.fvalue is not a function".
I see I'm not the first person to find this. In tests/list.lisp, there's a comment:
;; (SETF (CAR (CAR FOO)) 0) doesn't work in the test for some reason,
;; despite working fine in the REPL
I don't know the cause yet, but I did discover that in the compiled tests.js, it includes make_lisp_string("%RPLACA"), so it seems like this symbol is not getting connected somehow.
It would be great to support node.js to make the development easier. So we would provide both web REPL and console one. Same for tests.
The main limitation is how node.js manages module variables. Node wraps modules with something like
(function (context, module, ...){ // module code is here })(....)
so "global" variables are not global and we can't access them or define new variables from eval
. The solution is move all the global variables to an object, which would be exported in node.js. Indeed, it would clean the Javascript namespace too.
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.