justinethier / husk-scheme Goto Github PK
View Code? Open in Web Editor NEWA full implementation of the Scheme programming language for the Haskell Platform.
Home Page: http://justinethier.github.io/husk-scheme
License: MIT License
A full implementation of the Scheme programming language for the Haskell Platform.
Home Page: http://justinethier.github.io/husk-scheme
License: MIT License
There needs to be an interface to enable scheme functions to call into haskell code.
Per SRFI 69
Need to add (at least rudimentary) support for r7rs modules.
Husk should be able to be deployed via pre-compiled packages such as RPM
From http://stackoverflow.com/questions/2526777/how-are-vector-patterns-used-in-syntax-rules - it would be handy to have the ability of husk to show how a macro would look once expanded:
(define-syntax mac
(syntax-rules ()
((mac #(a b c d))
(let ()
(display a)
(newline)
(display d)
(newline)))))
(expand '(mac #(1 2 3 4))) ;; Chicken's expand-full extension shows macroexpansion
=> (let746 () (display747 1) (newline748) (display747 4) (newline748))
It should be possible to write floating point numbers using scientific notation, IE: #e##
For example, the following expressions are valid in chicken scheme:
#;1> 1e30
1e+30
#;2> 1e3
1000.0
Future enhancement: API documentation (xDoc) generated for all built-in functions. Ideally the docs would also link to reference documentation for language keywords.
Consider the following macro and test code:
(define-syntax when
(syntax-rules ()
((when condition . body) (if condition (begin . body) #f))))
;((when condition body ...) (if condition (begin . body) #f))))
(define x -1)
(when (negative? x)
(newline)
(display "bad number: negative"))
The output when run with original version of above is (with debug traces):
huski> (load "scm-unit-tests/test
test.scm test2.scm
huski> (load "scm-unit-tests/test.scm" )
loadLocal pattern = ((condition . body)) input = ((negative? x) (newline) (display "bad number: negative"))
loadLocal pattern = (condition . body) input = (negative? x)
loadLocal pattern = (condition body ...) input = (negative? x)
loadLocal pattern = (body ...) input = (x)
loadLocal pattern = (body ...) input = ()
loadLocal pattern = () input = ((newline) (display "bad number: negative"))
Input does not match a macro pattern: (when (negative? x) (newline) (display "bad number: negative"))
Output when using the ...
above instead of a pair:
huski> (load "scm-unit-tests/test.scm" )
loadLocal pattern = (condition body ...) input = ((negative? x) (newline) (display "bad number: negative"))
loadLocal pattern = (body ...) input = ((newline) (display "bad number: negative"))
loadLocal pattern = (body ...) input = ((display "bad number: negative"))
loadLocal pattern = (body ...) input = ()
"bad number: negative"
The output clearly shows that the problem is that by the time we get to inserting the ...
in the pattern, we have already moved too far into the input. Perhaps the pattern matcher needs to search for the pattern List[DottedList] instead of searching for dotted lists directly. Need to be careful here...
Procedures that are built into the evaluator are not directly exposed. For example, in bigloo the apply
function can be referenced directly:
1:=> apply
#<procedure:295740.-3>
1:=> (procedure? apply)
#t
On husk this currently results in an error:
huski> apply
Getting an unbound variable: apply
huski> (procedure? apply)
Getting an unbound variable: apply
Many functions are affected, including:
Per the "Binding constructs for syntactic keywords" section of the spec.
See: http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-2.html#%_toc_%_sec_4.3.1
See notes on the n-ary branch (and migrate them here)
Example programs should be provided to demonstrate how husk can be used to perform file I/O and network I/O. Husk internals may need to be modified (per spec) on an as-needed basis to support this.
From http://eval.apply.googlepages.com/eccentric.txt -
By using a dotted tail in the pattern we can write macros that take an
arbitrary number of arguments.
(define-syntax when
(syntax-rules ()
((when condition . body) (if condition (begin . body) #f))))
An example usage is
(when (negative? x)
(newline)
(display "Bad number: negative."))
The pattern matches as follows:
condition = (negative? x)
body = ((newline) (display "Bad number: negative."))
Since the pattern variable `body' is in the dotted tail position, it
is matched against the list of remaining elements in the form. This
can lead to unusual errors.
...
*** Implicit Begin idiom
Use this idiom for a macro that allows an arbitrary number of
subforms and processes them sequentially (possibly returning the
value of the last subform).
The pattern should end in "FORM . FORMS)" to ensure a minimum of
one subform.
The template has either (begin form . forms) or uses the implicit
begin of another special form, e.g. (lambda () form . forms)
It would be nice to have some form of call history printed when an error occurs, so the user has more information to track down the problem. As it is now, by just printing the error itself it is difficult to isolate what Scheme code may have caused the problem.
Define should save a reference to objects, for example try the following code in husk and another Scheme:
(define x (list 'a 'b 'c))
(define y x)
(set-cdr! x 4)
; y should be (a . 4) but it is still (a b c)
There are 3 test cases this fails in t-stdlib.scm
Also consider the following test case, where an object is passed to a different scope:
(define vec (make-vector 2 #f))
((lambda (v) (vector-set! v 1 3)) vec)
(write vec)
vec
should be modified at the end, but it is still #(#f #f)
.
The more general problem here is that there are certain data types that can be modified if an instance is passed directly to a mutator such as set!
or set-cdr!
. This is discussed specifically in section 3.4 Storage Model:
Variables and objects such as pairs, vectors, and strings implicitly denote locations or sequences of locations. A string, for example, denotes as many locations as there are characters in the string. (These locations need not correspond to a full machine word.) A new value may be stored into one of these locations using the string-set! procedure, but the string continues to denote the same locations as before.
An object fetched from a location, by a variable reference or by a procedure such as car, vector-ref, or string-ref, is equivalent in the sense of eqv? (section 6.1) to the object last stored in the location before the fetch.
Every location is marked to show whether it is in use. No variable or object ever refers to a location that is not in use. Whenever this report speaks of storage being allocated for a variable or object, what is meant is that an appropriate number of locations are chosen from the set of locations that are not in use, and the chosen locations are marked to indicate that they are now in use before the variable or object is made to denote them.
In many systems it is desirable for constants (i.e. the values of literal expressions) to reside in read-only-memory. To express this, it is convenient to imagine that every object that denotes locations is associated with a flag telling whether that object is mutable or immutable. In such systems literal constants and the strings returned by symbol->string are immutable objects, while all objects created by the other procedures listed in this report are mutable. It is an error to attempt to store a new value into a location that is denoted by an immutable object.
These mutable data types are:
Husk should support the full set of I/O functions from section 6.6 of R5RS:
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-2.html#%_toc_%_sec_6.6
The following test case fails, because internally husk perform an integer operation:
(assert/equal (/ 3 4 5) 3/20)
Scheme should handle this by converting up to a rational, or at least a floating-point type. For example, in chicken (/ 1 2)
evaluates to 0.5
, whereas (/ 2 1)
evaluates to an exact value of 2
.
The following quoted expression is simplified in other implementations such as Chicken:
'(1 2 . (3 . ()))
=> (1 2 3)
husk should do this a well.
When a pair is present in a macro transformation, that transformation does not always agree with transformation of the same code by a reference implementation (Chicken Scheme):
(define-syntax my-pair-test/06
(syntax-rules ()
((_ var . step)
(list (quote (var . step))))))
(write (my-pair-test/06 (1 . 3)))
; outputs: ((1 3))
; reference implementation (chicken) outputs (((1 . 3)))
(write (my-pair-test/06 (1 2)))
; outputs: ((1 2))
More investigation is required to determine exactly what the problem is, if any.
There are many test cases in t-macro.scm that fail due to this issue.
The Haskell code needs to be moved to a Language.Scheme namespace, and the code needs to be moved to a corresponding directory, to better follow established Haskell best practices. This should also fix a warning when submitting the project to Hackage DB.
For an example of how the code should be organized, see: https://github.com/mwotton/Hubris or any number of other open source projects.
When husk is installed on a system, stdlib.scm should automatically be loaded - both when starting up the interpreter and when running a program.
Nested ` forms must be handled properly, as per spec:
For example, the form
`(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f)
Should evaluate to the following, because (+ 1 3)
is at the same depth level as the outer backtick:
(a `(b ,(+ 1 2) ,(foo 4 d) e) f)
However, this form throws an error instead because husk does not presently respect depth level when evaluating quasi-quoted forms.
The semantics of this function should be similar to those from other Schemes, EG:
Finally, some applications, especially those that generate new Scheme code dynamically, need to generate symbols for use in the generated code. The gensym primitive meets this need:
— Scheme Procedure: gensym [prefix]
— C Function: scm_gensym (prefix)
Create a new symbol with a name constructed from a prefix and a counter value. The string prefix can be specified as an optional argument. Default prefix is ‘ g’. The counter is increased by 1 at each call. There is no provision for resetting the counter.
See: http://sisc-scheme.org/r5rs_pitfall.scm
husk should be tested against these pitfalls...
As the title says, vectors should be supported as part of a macro transformation.
Neither of these operations properly conforms to the spec - see corresponding test cases in t-stdlib.scm for examples.
Basically, each operation should accept any value, not just booleans:
The expressions are evaluated from left to right, and the value of the first expression that evaluates to a false value (see section 6.3.1) is returned. Any remaining expressions are not evaluated. If all the expressions evaluate to true values, the value of the last expression is returned. If there are no expressions then #t is returned.
For example:
(assert/equal (and 1 2 'c '(f g)) '(f g))
The function Language.Scheme.Core.evalFuncLoad produces a lazy error instead of succeeding or failing properly when the file being loaded is empty.
Minor problem, but hard to debug!
From the spec:
These procedures return the numerator or denominator of their argument; the result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.
However, these functions do not accept real (floating point) numbers as an argument. For example:
huski> (denominator 1/2)
2
huski> (denominator 0.5)
Invalid type: expected rational number, found 0.5
huski> (numerator 0.5)
Invalid type: expected rational number, found 0.5
In chicken:
#;1> #(a)
Error: illegal non-atomic object: #(a)
#;1> #((+ 2 3))
Error: illegal non-atomic object: #((+ 2 3))
The same forms in husk:
huski> #(a)
#(a)
huski> #((+ 2 3))
#((+ 2 3))
In general I think our code does a decent job of this, but the source code should follow these guidelines:
http://www.haskell.org/haskellwiki/Programming_guidelines
It's worth doing at least a once-over of the code for these...
Consider the following example:
(define-syntax pair-test-00
(syntax-rules ()
((_ (a b . c))
(quote (a b c)))))
(assert/equal (pair-test-00 (1 2)) '(1 2 ()))
This should transform into (1 2 ())
but instead it becomes (1 2)
. This is because the code in flagUnmatchedVars
needs to keep track of whether or not a flagged variable (c
in this case) was part of a match as part of an ellipsis ...
or as the last member of an improper list (pair). This could probably be done using an array of booleans, much as it is handled in the rest of the pattern matching code.
Once this flag is stored, the transform code needs to take it into account when it encounters an "unmatched" variable.
This is really more of a task item than an issue, but the remaining TODO items in Macro.hs need to be inspected and resolved prior to release...
See working group 1's draft:
http://groups.google.com/group/comp.lang.scheme/browse_thread/thread/3027cfa1e8abf74b?pli=1
Progress report: http://developers.slashdot.org/story/11/10/04/1942209/R7RS-Scheme-Progress-Report
Latest draft: http://trac.sacrideo.us/wg/attachment/wiki/WikiStart/r7rs-draft-6.pdf
The FFI code should be moved into its own module. This would isolate it and should allow Core to be debugged via ghci.
Tested using ghc version 7.0.2 on Fedora 15.
The compile output is:
[7 of 9] Compiling Language.Scheme.Core ( hs-src/Language/Scheme/Core.hs, hs-src/Language/Scheme/Core.o )
hs-src/Language/Scheme/Core.hs:738:31:
Couldn't match expected type (GHC.Module, Maybe (GHC.ImportDecl GHC.RdrName))' with actual type
GHC.Module'
In the expression: m
In the second argument of GHC.setContext', namely
[m]'
In a stmt of a 'do' expression: GHC.setContext [] [m]
make: *** [husk] Error 1
Need to build-out t-io.scm
Low-level macros should be available via a standard system such as syntatic closures or explicit renaming - er-macro-transformer
High and low-level support via syntax-case
would be nice, but is probably too complicated to implement at this time (or maybe ever). Anyway, see: http://srfi.schemers.org/srfi-72/srfi-72.html
Consider the following macro:
(define-syntax my-pair-test/06
(syntax-rules ()
((_ var . step)
(list (quote (var . step))))))
An extra pair of parens needs to be added around the match; currently the following test cases are failing by not including one level of outer parentheses:
(assert/equal (my-pair-test/06 (1 . 3))
'(((1 . 3))))
(assert/equal (my-pair-test/06 (1 2))
'(((1 2))))
Not quite sure why these parens are missing, or why they even need to be there...
cond
should be implemented as a macro, not a special form.
Primitive functions should be moved into a new module, Primitives.hs, and away from the core evaluation code.
When throwError
is called, the error is not printed if running from the cmd line, only if running from the shell. in both cases, the user should see the error msg, to make debugging easier.
From the spec:
library procedure: (rationalize x y)
Rationalize returns the simplest rational number differing from x by no more than y. A rational number r1 is simpler than another rational number r2 if r1 = p1/q1 and r2 = p2/q2 (in lowest terms) and |p1| < |p2| and |q1| < |q2|. Thus 3/5 is simpler than 4/7. Although not all rationals are comparable in this ordering (consider 2/7 and 3/5) any interval contains a rational number that is simpler than every other rational number in that interval (the simpler 2/5 lies between 2/7 and 3/5). Note that 0 = 0/1 is the simplest rational of all.
(rationalize
(inexact->exact .3) 1/10) ===> 1/3 ; exact
(rationalize .3 1/10) ===> #i1/3 ; inexact
These functions need to be implemented, per the spec
You don't have hygienic macros. For example using,
(define-syntax orr (syntax-rules () ((orelse <expr1> <expr2>) (let ((temp <expr1>)) (if temp temp <expr2>)))))
I'm getting
(let ((temp 4)) (orr #f temp)) => #f instead of 4
The other side of hygiene is much harder to get right:
(let ((if +)) (orr 1 1)) => 1
seems that it's working, which surprised me after failing the easy side... But apparently, you don't even have lexical scope implemented properly:
(let ((if +)) (if (orr 1 1) 10 100)) => 10 instead of 111
or more directly:
(let ((if +)) (if 1 2 3)) => 2 instead of 6
The quick fix that every toy scheme does is dropping claims that it supports hygiene. In this case, you have even bigger problems...
This part of the interpreter could be improved. Issues include:
Might consider implementing a tokenizer that takes care of these issues prior to loading source code.
From Rotsor's email:
Hello Justin!
I was porting one Scheme script from Guile to Husk and noticed one problem -- (fold (lambda (x y) x) 8 (list 1 2 3)) in Guile yields 3, the same expression in Husk yields 8. The reason for that seems to be the order of arguments for the lambda-expression. In Husk it's flipped relative to the Guile's.
I believe there is no fold in R5RS, it is introduced by SRFI 1. So, my question is -- what are your thoughts on this? What, if anything, are the additional functions in standard library supposed to be compatible with? Should I just rewrite the offending functions?
Thank you!
Arseniy.
Presently, syntax checking only occurs when a macro is evaluated, not when one is defined via (define-syntax). By adding this validation, it would make it much easier to debug macros.
Now that continuations are implemented, the dynamic-wind function should be implemented as well.
The hackage page for v3.2.1 indicates a build failure on ghc-7.2 - see: http://hackage.haskell.org/package/husk-scheme-3.2.1
This needs to be resolved by 3.3 or a quick subsequent release (3.3.x)...
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.