From 4f864b0090c05ab1d4aae92ca972105389227a14 Mon Sep 17 00:00:00 2001 From: Eduardo Julian Date: Mon, 4 May 2015 15:43:31 -0400 Subject: v0.1 --- README.md | 543 +++++++++++++++++++++++++++++++++++++++++++-------- source/lux.lux | 2 +- src/lux.clj | 3 +- src/lux/analyser.clj | 88 ++++++++- 4 files changed, 541 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 4ba768a0c..c47c7efc1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,249 @@ +## What is Lux? + +Lux is a new programming language in the making. +It's meant to be a functional, statically-typed Lisp that will run on several platforms, such as the Java Virtual Machine and JavaScript interpreters. + +### What's the current version? + +v0.1 + +### How far ahead is the project? + +The Java-bytecode compiler is close to completion. + +Some features are missing and the compiler is not as fast as I would like. + +However, some small programs can be written to try out Lux and get a feeling for the language. + +### How can I use it? + +Download the 0.1 compiler from here: https://github.com/LuxLang/lux/raw/master/lux-jvm-0.1.0-standalone.jar + +Right now, the current version of Lux (0.1) is mostly to play around with the language, so it's a bit limited on what you can do. +Once you download the compiler, you'll want to create a directory named "source" in the same directory where the compiler is located. + +"source" must contain 2 files. +One will be the Lux prelude (lux.lux), the other will be program.lux +You can write anything you want inside program.lux to play around with the language. + +##### Note: You can download the lux.lux & program.lux files in the source/ directory in this repo to get started. + +To run the compiler, open your terminal and write this: + + java -jar lux-jvm-0.1.0-standalone.jar + +This will generate a directory named "output" and put all the .class files there. +Then, you can package the program and run it using this: + + cd output && jar cvf program.jar * && java -cp "program.jar" program && cd .. + +### What's the license? + +Eclipse Public License v1.0 + +## What's interesting about the language? + +### Inspirations + +The language is mostly inspired by the following 3 languages: + +* Haskell (functional programming) +* Clojure (syntax, overall look & feel) +* ML (module system) + +The compiler is even implemented in Clojure. + +### Types + +They are implemented as plain-old data-structures whose expressions get eval'ed by the compiler and integrated into the type-checker. +That means it's actually possible to generate types via functions. +However, most of the types in the prelude are generated via several macros that provide a more pleasant syntax to types. + +If you wonder what types look like without makeup, feel free to read the first few hundred lines of lux.lux. + +### Module system + +The module system is heavily inspired by ML, and both signatures & structures are suported. + +The main difference between Lux and ML is that ML separates signaturs & structures from the rest of the language, whereas Lux implements them on-top of the base language. + +How? +By implementing signatures as record-types and structures as actual records. + +##### But, why not just use type-classes? + +Haskell's type-class system forces the user to only specify 1 instance for any given type-class and it's argument. +This means that if there are more than 1 possible valid instances (as is the case for Monoid of Int), you have to resort to newtype hacks to be able to provide alternative implementations. + +By using a system like ML's, that problem is averted. +Also, by hosting the module system on top of records, which are regular values, you get the further benefit that structures can be parameterized at runtime just like any other data-structures. +You can also write functions that take and return structures (as Functors do in ML) and you can generate structures on the fly. + +### Functional programming + +While the means to do Java-interop will be provided (and there are already a few ways to do it that you can look-up inside lux.lux), Lux is commited to functional programming. + +Functions are curried and partial application is as simple as just applying a function to less arguments than it needs (as in Haskell). + +e.g. + + (let [inc (int:+ 1)] + (map inc (list 1 2 3 4 5))) + +### Code portability + +Many languages nowadays support compilation to multiple platforms (e.g. Haskell, Scala, Clojure). +However, sharing code between platforms can be a pain in the neck due to the following reasons: + +* differences in features between platforms +* the languages weren't originally designed with the goal of running both native/JVM and in JavaScript + +Lux is being designed from the ground up to target multiple platforms and achieve maximum reusability of code with minimum hassle. + +The mechanism hasn't been added yet to the language (mainly because there's only 1 compiler at the moment), but it will come pretty soon in one of the upcoming releases. + +### Macros + +Unlike in most other lisps, Lux macros are monadic. +The **(Lux a)** type is the one responsibly for the magic by treading **CompilerState** instances through macros. +Macros must have the **Macro** and then be declared as macros. + +However, just using the **defmacro** macro will take care of it for you. + +However, in an upcoming release you'll get another macro for defining macros. +It will be named **defsyntax** and will use monadic parsing of AST tokens to parse the syntax. + +If you want to see how macros are implemented, you can take a look at *lux.lux*. + +### Custom pattern-matching + +##### Wait... wut? + +Custom pattern-matching basically means that you can use macros to provide custom syntax & features on top of the pattern-matching macro (case). + +For instance, the **list** and **list&** macros are used to build lists. +But you can also use them to destructure them inside pattern-matching: + + (case (: (List Int) (list 1 2 3)) + (#Cons [x (#Cons [y (#Cons [z #Nil])])]) + (#Some ($ int:* x y z)) + + _ + #None) + + (case (: (List Int) (list 1 2 3)) + (\ (list 1 2 3)) + (#Some ($ int:* x y z)) + + _ + #None) + +There is also the special **\or** macro, which instroduces *or patterns* + + (deftype Weekday + (| #Monday + #Tuesday + #Wednesday + #Thursday + #Friday + #Saturday + #Sunday)) + + (def (weekend? day) + (-> Weekday Bool) + (case day + (\or #Saturday #Sunday) + true + + _ + false)) + +##### Please note: \ and \or are just macros like and other and anyone can implement them. + +I'll be adding more useful pattern-matching macros in upcoming releases of the language. + +If you want to see how they work, just check out their implementation inside lux.lux + +### Is there a community for this? + +Lux was just born today. + +Come join the budding community by joining the discussion group at: https://groups.google.com/forum/#!forum/lux-programming-language + +If you want to communicate with me directly, just email me at luxlisp@gmail.com + +### How can I edit Lux code? + +Check out the Emacs plugin for it: https://github.com/LuxLang/lux-mode + ## What's available? +### Base syntax + +Comments + + ## This is a single-line comment + ## Multi-line comments are comming soon + +Bool (implemented as java.lang.Boolean) + + true + false + +Int (implemented as java.lang.Long) + + 1 + 20 + 12345 + +Real (implemented as java.lang.Double) + + 1.23 + 0.5 + +Char (implemented as java.lang.Character) + + #"a" + #"\n" + +Text (implemented as java.lang.String) + + "yolo" + "Hello\nWorld!" + +Forms + + (+ 1 2) + (lambda [x] (foo 10 x)) + +Symbols + + foo ## Unprefixed symbol (compiler will assume it's in the current module) + bar;baz ## Prefixed symbol (compiler will assume it's in the module specified by the prefix) + ;fold ## With just the semi-colon, compiler wil assume it's the same as lux;fold + ;;quux ## With 2 semi-colons, it will get automatically prefixed with the current-module + +Tags + + #Nil + #lux;Cons + #;Some + #;;MyTag + +Tuples + + [] + ["yolo" 10 true] + +Variants (aka sum-types, aka discriminated unions) + + #Nil + (#Cons [10 #Nil]) + +Records + + {#name "Eduardo" #alive? true} + ### Types (deftype Bool (^ java.lang.Boolean)) @@ -50,8 +294,9 @@ (-> (,) a)) ### Macros -defmacro +###### defmacro e.g. + (defmacro #export (and tokens) (case (reverse tokens) (\ (list& last init)) @@ -67,121 +312,162 @@ e.g. _ (fail "and requires >=1 clauses."))) -comment +###### comment e.g. + (comment 1 2 3 4) ## Same as not writing anything... -list +###### list e.g. + (list 1 2 3) => (#Cons [1 (#Cons [2 (#Cons [3 #Nil])])]) -list& +###### list& e.g. + (list& 0 (list 1 2 3)) => (#Cons [0 (list 1 2 3)]) -lambda +###### lambda e.g. + (def const (lambda [x y] x)) (def const (lambda const [x y] x)) -let +###### let e.g. (let [x (foo bar) y (baz quux)] ...) -$ +###### $ e.g. + + ## Text/string concatenation ($ text:++ "Hello, " name ".\nHow are you?") => (text:++ "Hello, " (text:++ name ".\nHow are you?")) -|> +###### |> e.g. + + ## Piping macro (|> elems (map ->text) (interpose " ") (fold text:++ "")) => (fold text:++ "" (interpose " " (map ->text elems))) -if +###### if e.g. + (if true "Oh, yeah" "Aw hell naw!") -^ +###### ^ e.g. + + ## Macro to treat classes as types (^ java.lang.Object) -, +###### , e.g. + + ## Tuples (, Text Int Bool) + + (,) ## The empty tuple, aka "unit" -| +###### | e.g. + (| #Yes #No) + + (,) ## The empty variant, aka "void" -& +###### & e.g. + + ## Records (& #name Text #age Int #alive? Bool) --> +###### -> e.g. - (-> Int Int Int) + + ## Function types + (-> Int Int Int) ## This is the type of a function that takes 2 Ints and returns an Int -All +###### All e.g. + + ## Universal quantification. (All List [a] (| #Nil (#Cons [a (List a)]))) + ## It must be explicit, unlike in Haskell. Rank-n types will be possible as well as existential types (All [a] (-> a a)) -type` - ## This macro is not mean to be used directly. It's used by :, :!, deftype, struct, sig +###### type` + + ## This macro is not meant to be used directly. It's used by :, :!, deftype, struct, sig + +###### io -io ## Just makes sure whatever computation you do returns an IO type. It's here mostly for host-interop. -: +###### : e.g. + + ## The type-annotation macro (: (List Int) (list 1 2 3)) -:! +###### :! e.g. + + ## The type-coercion macro (:! Dinosaur (list 1 2 3)) -deftype +###### deftype e.g. + + ## The type-definition macro (deftype (List a) (| #Nil (#Cons [a (List a)]))) -exec +###### exec e.g. + + ## Sequential execution of expressions (great for side-effects). + ## But please use the io macro to help keep the purity. (exec (println "#1") (println "#2") (println "#3") "YOLO") -def +###### def e.g. + + ## Macro for definining global constants/functions. (def (rejoin-pair pair) (-> (, Syntax Syntax) (List Syntax)) (let [[left right] pair] (list left right))) -case +###### case e.g. + + ## The pattern-matching macro. + ## Allows the usage of macros within the patterns to provide custom syntax. (case (: (List Int) (list 1 2 3)) (#Cons [x (#Cons [y (#Cons [z #Nil])])]) (#Some ($ int:* x y z)) @@ -214,27 +500,34 @@ e.g. _ false)) -\ +###### \ + ## It's a special macro meant to be used with case -\or +###### \or + ## It's a special macro meant to be used with case -` +###### ` + ## Quasi-quotation as a macro. Unquote (~) and unquote-splice (~@) must also be used as forms e.g. (` (def (~ name) (lambda [(~@ args)] (~ body)))) -sig +###### sig + ## Not mean to be used directly. Prefer defsig -struct +###### struct + ## Not mean to be used directly. Prefer defstruct -defsig +###### defsig e.g. + + ## Definition of signatures ala ML (defsig #export (Ord a) (: (-> a a Bool) <) @@ -245,8 +538,10 @@ e.g. (: (-> a a Bool) >=)) -defstruct +###### defstruct e.g. + + ## Definition of structures ala ML (defstruct #export Int:Ord (Ord Int) (def (< x y) (jvm-llt x y)) @@ -259,156 +554,215 @@ e.g. (or (jvm-lgt x y) (jvm-leq x y)))) -and +###### and e.g. + (and true false true) ## => false -or +###### or e.g. + (or true false true) ## => true -alias-lux +###### alias-lux + ## Just creates local aliases of everything defined & exported in lux.lux - e.g. +e.g. + (;alias-lux) -using +###### using e.g. + + ## The Lux equivalent to ML's open. + ## Opens up a structure and provides all the definitions as local variables. (using Int:Ord (< 5 10)) ### Functions -fold +###### fold + (All [a b] (-> (-> a b a) a (List b) a)) -reverse +###### reverse + (All [a] (-> (List a) (List a))) -map +###### map + (All [a b] (-> (-> a b) (List a) (List b))) -any? +###### any? + (All [a] (-> (-> a Bool) (List a) Bool)) . + + ## Function composition: (. f g) => (lambda [x] (f (g x))) (All [a b c] (-> (-> b c) (-> a b) (-> a c))) -int:+ +###### int:+ + (-> Int Int Int) -int:- +###### int:- + (-> Int Int Int) -int:* +###### int:* + (-> Int Int Int) -int:/ +###### int:/ + (-> Int Int Int) -int:% +###### int:% + (-> Int Int Int) -int:= +###### int:= + (-> Int Int Bool) -int:> +###### int:> + (-> Int Int Bool) -int:< +###### int:< + (-> Int Int Bool) -real:+ +###### real:+ + (-> Real Real Real) -real:- +###### real:- + (-> Real Real Real) -real:* +###### real:* + (-> Real Real Real) -real:/ +###### real:/ + (-> Real Real Real) -real:% +###### real:% + (-> Real Real Real) -real:= +###### real:= + (-> Real Real Bool) -real:> +###### real:> + (-> Real Real Bool) -real:< +###### real:< + (-> Real Real Bool) -length +###### length + + ## List length (All [a] (-> (List a) Int)) -not +###### not + (-> Bool Bool) -text:++ +###### text:++ + + ## Text/string concatenation (-> Text Text Text) -get-module-name +###### get-module-name + + ## Obtain the name of the currently-compiling module while in a macro. (Lux Text) -find-macro +###### find-macro + + ## Given the name of a macro, try to obtain it. (-> Ident (Lux (Maybe Macro))) -normalize +###### normalize + + ## Normalizes a name so if it lacks a module prefix, it gets the one of the current module. (-> Ident (Lux Ident)) -->text +###### ->text + (-> (^ java.lang.Object) Text) -interpose +###### interpose + (All [a] (-> a (List a) (List a))) -syntax:show +###### syntax:show + + ## Turn Lux synta into user-readable text. (Note: it's not pretty-printed) (-> Syntax Text) -macro-expand +###### macro-expand + + ## The standard macro-expand function. (-> Syntax (Lux (List Syntax))) -gensym +###### gensym + + ## Can't forget gensym! (-> Text (Lux Syntax)) -macro-expand-1 +###### macro-expand-1 + (-> Syntax (Lux Syntax)) -id +###### id + (All [a] (-> a a)) -print +###### print + + ## Neither print or println return IO right now because I've yet to implement monads & do-notation + ## Note: The implementation inside lux.lux is not meant for public consumption. + ## Note: You'll get an implementation of monads, functors et al. soon enough... (-> Text (,)) -println +###### println + (-> Text (,)) -some +###### some + (All [a b] (-> (-> a (Maybe b)) (List a) (Maybe b))) ### Signatures -Eq +###### Eq + (defsig #export (Eq a) (: (-> a a Bool) =)) -Show +###### Show + (defsig #export (Show a) (: (-> a Text) show)) -Ord +###### Ord + (defsig #export (Ord a) (: (-> a a Bool) <) @@ -420,14 +774,35 @@ Ord >=)) ### Structures -Int:Eq -Real:Eq +###### Int:Eq +###### Real:Eq + +###### Bool:Show +###### Int:Show +###### Real:Show +###### Char:Show + +###### Int:Ord +###### Real:Ord + +## Caveats + +### Errors +The compiler is not fully stable so you might get an error if you do anything funny. + +Also, the error messages could really use an overhaul, so any error message you get will probably startle you. + +Don't worry about it, version 0.2 will improve error reporting a lot. +If you have any doubts, feel free to ask/complain in the Google Group. + +### Tags + +Tags that are unprefixed will just assume the prefix of the current module you're in. + +If you want to write variants/records with tags from lux.lux, you must do 1 of the following 2 alternatives: -Bool:Show -Int:Show -Real:Show -Char:Show +* Fully prefix them: #lux;Cons +* Use the ; short-cut: #;Cons -Int:Ord -Real:Ord +##### Copyright (c) 2015 Eduardo Julian. All rights reserved. diff --git a/source/lux.lux b/source/lux.lux index acd913a3c..acaee2265 100644 --- a/source/lux.lux +++ b/source/lux.lux @@ -1705,7 +1705,7 @@ (fail "Wrong syntax for \\or"))) (do-template [ ] - [(def (int:+ ))] + [(def #export (int:+ ))] [inc 1] [dec -1]) diff --git a/src/lux.clj b/src/lux.clj index 1812bf294..de302b260 100644 --- a/src/lux.clj +++ b/src/lux.clj @@ -5,7 +5,8 @@ :reload-all)) (defn -main [& _] - (time (&compiler/compile-all (&/|list "program")))) + (time (&compiler/compile-all (&/|list "program"))) + (System/exit 0)) (comment ;; TODO: Finish total-locals diff --git a/src/lux/analyser.clj b/src/lux/analyser.clj index 679a3fea3..e2cdb83ce 100644 --- a/src/lux/analyser.clj +++ b/src/lux/analyser.clj @@ -2,7 +2,7 @@ (:require (clojure [template :refer [do-template]]) [clojure.core.match :as M :refer [matchv]] clojure.core.match.array - (lux [base :as & :refer [|do return fail |list]] + (lux [base :as & :refer [|do return fail return* fail* |list]] [reader :as &reader] [parser :as &parser] [type :as &type] @@ -29,11 +29,7 @@ (defn ^:private _meta [token] (&/V "lux;Meta" (&/T (&/T "" -1 -1) token))) -(defn ^:private analyse-basic-ast [analyse eval! exo-type token] - ;; (prn 'analyse-basic-ast (aget token 0)) - ;; (when (= "lux;Tag" (aget token 0)) - ;; (prn 'analyse-basic-ast/tag (aget token 1))) - ;; (prn 'analyse-basic-ast token (&/show-ast token)) +(defn ^:private aba1 [analyse eval! exo-type token] (matchv ::M/objects [token] ;; Standard special forms [["lux;Meta" [meta ["lux;Bool" ?value]]]] @@ -67,7 +63,13 @@ [["lux;Meta" [meta ["lux;Symbol" [_ "jvm-null"]]]]] (return (&/|list (&/T (&/V "jvm-null" nil) (&/V "lux;DataT" "null")))) - + + [_] + (fail "") + )) + +(defn ^:private aba2 [analyse eval! exo-type token] + (matchv ::M/objects [token] [["lux;Meta" [meta ["lux;Symbol" ?ident]]]] (&&lux/analyse-symbol analyse exo-type ?ident) @@ -117,6 +119,11 @@ ["lux;Nil" _]]]]]]]]] (&&lux/analyse-export analyse ?ident) + [_] + (fail ""))) + +(defn ^:private aba3 [analyse eval! exo-type token] + (matchv ::M/objects [token] ;; Host special forms ;; Integer arithmetic [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-iadd"]]]] ["lux;Cons" [?y ["lux;Cons" [?x ["lux;Nil" _]]]]]]]]]]] @@ -168,6 +175,11 @@ [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-lgt"]]]] ["lux;Cons" [?y ["lux;Cons" [?x ["lux;Nil" _]]]]]]]]]]] (&&host/analyse-jvm-lgt analyse ?x ?y) + [_] + (fail ""))) + +(defn ^:private aba4 [analyse eval! exo-type token] + (matchv ::M/objects [token] ;; Float arithmetic [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-fadd"]]]] ["lux;Cons" [?y ["lux;Cons" [?x ["lux;Nil" _]]]]]]]]]]] (&&host/analyse-jvm-fadd analyse ?x ?y) @@ -218,6 +230,11 @@ [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-dgt"]]]] ["lux;Cons" [?y ["lux;Cons" [?x ["lux;Nil" _]]]]]]]]]]] (&&host/analyse-jvm-dgt analyse ?x ?y) + [_] + (fail ""))) + +(defn ^:private aba5 [analyse eval! exo-type token] + (matchv ::M/objects [token] ;; Objects [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-null?"]]]] ["lux;Cons" [?object @@ -316,6 +333,11 @@ ["lux;Nil" _]]]]]]]]] (&&host/analyse-jvm-monitorexit analyse ?monitor) + [_] + (fail ""))) + +(defn ^:private aba6 [analyse eval! exo-type token] + (matchv ::M/objects [token] ;; Primitive conversions [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-d2f"]]]] ["lux;Cons" [?value ["lux;Nil" _]]]]]]]]] (&&host/analyse-jvm-d2f analyse ?value) @@ -386,7 +408,12 @@ [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-lushr"]]]] ["lux;Cons" [?x ["lux;Cons" [?y ["lux;Nil" _]]]]]]]]]]] (&&host/analyse-jvm-lushr analyse ?x ?y) - + + [_] + (fail ""))) + +(defn ^:private aba7 [analyse eval! exo-type token] + (matchv ::M/objects [token] ;; Arrays [["lux;Meta" [meta ["lux;Form" ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ "jvm-new-array"]]]] ["lux;Cons" [["lux;Meta" [_ ["lux;Symbol" [_ ?class]]]] @@ -428,7 +455,50 @@ (&&host/analyse-jvm-program analyse ?args ?body) [_] - (fail (str "[Analyser Error] Unmatched token: " (&/show-ast token))))) + (fail ""))) + +(defn ^:private analyse-basic-ast [analyse eval! exo-type token] + ;; (prn 'analyse-basic-ast (aget token 0)) + ;; (when (= "lux;Tag" (aget token 0)) + ;; (prn 'analyse-basic-ast/tag (aget token 1))) + ;; (prn 'analyse-basic-ast token (&/show-ast token)) + (fn [state] + (matchv ::M/objects [((aba1 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba2 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba3 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba4 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba5 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba6 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (matchv ::M/objects [((aba7 analyse eval! exo-type token) state)] + [["lux;Right" [state* output]]] + (return* state* output) + + [_] + (fail* (str "[Analyser Error] Unmatched token: " (&/show-ast token)))))))))))) (defn ^:private analyse-ast [eval! exo-type token] ;; (prn 'analyse-ast (aget token 0)) -- cgit v1.2.3