elm-compiler (empty) → 0.14
raw patch · 70 files changed
+10021/−0 lines, 70 filesdep +HUnitdep +QuickCheckdep +aesonsetup-changed
Dependencies added: HUnit, QuickCheck, aeson, aeson-pretty, base, binary, blaze-html, blaze-markup, bytestring, cmdargs, containers, directory, elm-compiler, filemanip, filepath, indents, language-ecmascript, language-glsl, mtl, parsec, pretty, process, test-framework, test-framework-hunit, test-framework-quickcheck2, text, transformers, union-find, unordered-containers
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- changelog.md +351/−0
- elm-compiler.cabal +234/−0
- runtime/debug.js +604/−0
- src/AST/Annotation.hs +73/−0
- src/AST/Declaration.hs +144/−0
- src/AST/Expression/Canonical.hs +30/−0
- src/AST/Expression/General.hs +218/−0
- src/AST/Expression/Source.hs +32/−0
- src/AST/Expression/Valid.hs +31/−0
- src/AST/Helpers.hs +27/−0
- src/AST/Literal.hs +55/−0
- src/AST/Module.hs +168/−0
- src/AST/Pattern.hs +79/−0
- src/AST/PrettyPrint.hs +42/−0
- src/AST/Type.hs +148/−0
- src/AST/Variable.hs +240/−0
- src/Compile.hs +60/−0
- src/Docs.hs +262/−0
- src/Elm/Compiler.hs +81/−0
- src/Elm/Compiler/Module.hs +112/−0
- src/Elm/Compiler/Type.hs +177/−0
- src/Elm/Compiler/Type/Extract.hs +30/−0
- src/Elm/Compiler/Version.hs +10/−0
- src/Elm/Docs.hs +149/−0
- src/Elm/Utils.hs +121/−0
- src/Generate/Cases.hs +210/−0
- src/Generate/JavaScript.hs +454/−0
- src/Generate/JavaScript/Helpers.hs +118/−0
- src/Generate/JavaScript/Ports.hs +217/−0
- src/Generate/JavaScript/Variable.hs +62/−0
- src/Parse/Binop.hs +85/−0
- src/Parse/Declaration.hs +61/−0
- src/Parse/Expression.hs +239/−0
- src/Parse/Helpers.hs +577/−0
- src/Parse/Literal.hs +72/−0
- src/Parse/Module.hs +88/−0
- src/Parse/Parse.hs +64/−0
- src/Parse/Pattern.hs +63/−0
- src/Parse/Type.hs +91/−0
- src/Transform/AddDefaultImports.hs +86/−0
- src/Transform/Canonicalize.hs +305/−0
- src/Transform/Canonicalize/Environment.hs +91/−0
- src/Transform/Canonicalize/Setup.hs +269/−0
- src/Transform/Canonicalize/Type.hs +96/−0
- src/Transform/Canonicalize/Variable.hs +95/−0
- src/Transform/Check.hs +177/−0
- src/Transform/Declaration.hs +129/−0
- src/Transform/Definition.hs +35/−0
- src/Transform/Expression.hs +100/−0
- src/Transform/Interface.hs +60/−0
- src/Transform/SortDefinitions.hs +170/−0
- src/Transform/Substitute.hs +88/−0
- src/Type/Constrain/Expression.hs +231/−0
- src/Type/Constrain/Literal.hs +21/−0
- src/Type/Constrain/Pattern.hs +81/−0
- src/Type/Environment.hs +167/−0
- src/Type/ExtraChecks.hs +155/−0
- src/Type/Fragment.hs +43/−0
- src/Type/Inference.hs +98/−0
- src/Type/PrettyPrint.hs +20/−0
- src/Type/Solve.hs +195/−0
- src/Type/State.hs +245/−0
- src/Type/Type.hs +513/−0
- src/Type/Unify.hs +348/−0
- tests/Test.hs +14/−0
- tests/Test/Compiler.hs +73/−0
- tests/Test/Property.hs +68/−0
- tests/Test/Property/Arbitrary.hs +137/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012-2014, Evan Czaplicki++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Evan Czaplicki nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changelog.md view
@@ -0,0 +1,351 @@++## 0.14++#### Breaking Changes++ * Keyword `data` renamed to `type`+ * Keyword `type` renamed to `type alias`++## 0.13++#### Improvements:++ * Type aliases in port types + * Add Keyboard.alt and Keyboard.meta+ * Add Debug.crash, Debug.watch, Debug.watchSummary, and Debug.trace+ * Add List.indexedMap and List.filterMap+ * Add Maybe.map+ * Add Basics.negate+ * Add (>>) to Basics as in F#+ * Add --bundle-runtime flag which creates stand-alone Elm programs+ * Error on ambiguious use of imported variables+ * Replace dependency on Pandoc with cheapskate+kate+ * Better architecture for compiler. Uses types to make compilation pipeline+ safer, setting things up for giving programmatic access to the AST to+ improve editor and IDE support.++#### Breaking Changes:++ * Rename (.) to (<<) as in F#+ * Rename Basics.id to Basics.identity+ * Rename Basics.div to (//)+ * Rename Basics.mod to (%)+ * Remove Maybe.justs for (List.filterMap identity)+ * Remove List.and for (List.foldl (&&) True)+ * Remove List.or for (List.foldl (||) False)+ * Unambiguous syntax for importing ADTs and type aliases+ * sqrt and logBase both only work on Floats now++## 0.12.3++ * Minor changes to support webgl as a separate library+ * Switch from HSV to HSL+ * Programmatic access to colors with toHsl and toRgb++## 0.12.1++#### Improvements:++ * New Array library (thanks entirely to @Xashili)+ * Json.Value can flow through ports+ * Improve speed and stack usage in List library (thanks to @maxsnew)+ * Add Dict.filter and Dict.partition (thanks to @hdgarrood)++#### Breaking Changes:++ * Revamp Json library, simpler with better names+ * Revamp JavaScript.Experimental library to have slightly better names+ * Remove JavaScript library which was made redundant by ports++## 0.12++#### Breaking Changes:++ * Overhaul Graphics.Input library (inspired by Spiros Eliopoulos and Jeff Smitts)+ * Overhaul Text library to accomodate new Graphics.Input.Field+ library and make the API more consistent overall+ * Overhaul Regex library (inspired by Attila Gazso)+ * Change syntax for "import open List" to "import List (..)"+ * Improved JSON format for types generated by elm-doc+ * Remove problematic Mouse.isClicked signal+ * Revise the semantics of keepWhen and dropWhen to only update when+ the filtered signal changes (thanks Max New and Janis Voigtländer)++#### Improvements:++ * Add Graphics.Input.Field for customizable text fields+ * Add Trampoline library (thanks to @maxsnew and @timthelion) + * Add Debug library (inspired by @timthelion)+ * Drastically improved performance on markdown parsing (thanks to @Dandandan) + * Add Date.fromTime function+ * Use pointer-events to detect hovers on layered elements (thanks to @Xashili)+ * Fix bugs in Bitwise library+ * Fix bug when exporting Maybe values through ports++## 0.11++ * Ports, a new FFI that is more general and much nicer to use+ * Basic compiler tests (thanks to Max New)++## 0.10.1++ * sort, sortBy, sortWith (thanks to Max Goldstein)+ * elm-repl+ * Bitwise library+ * Regex library+ * Improve Transform2D library (thanks to Michael Søndergaard)++## 0.10++ * Native strings+ * Tango colors+ * custom precedence and associativity for infix operators+ * elm-doc released with new documentation format+ * Realiasing in type errors+ * Rename Matrix2D => Transform2D+ * Add Random.floatList (thank you Max GoldStein)+ * Fix remove function in Dict (thank you Max New)+ * Start using language-ecmascript for JS generation+ * Make compatable with cabal-1.18 (thank you Justin Leitgeb)+ * All functions with 10+ arguments (thanks to Max New)++## 0.9.1++ * Allow custom precedence and associativity for user-defined infix ops+ * Realias types before printing+ * Switch to Tango color scheme, adding a bunch of nice colors+ * add the greyscale function for easily producing greys+ * Check the type of main+ * Fix miscellaneous bugs in type checker+ * Switch name of Matrix2D to Transform2D++## 0.9++Build Improvements:+ * Major speed improvements to type-checker+ * Type-checker should catch _all_ type errors now+ * Module-level compilation, only re-compile if necessary+ * Import types and type aliases between modules+ * Intermediate files are generated to avoid unneeded recompilation+ and shorten compile time. These files go in ElmFiles/ by default+ * Generated files are placed in ElmFiles/ by default, replicating+ the directory structure of your source code.++Error Messages:+ * Cross-module type errors+ * Errors for undefined values+ * Pretty printing of expressions and types+ +Syntax:+ * Pattern matching on literals+ * Pattern aliases with `as` (Andrew)+ * Unary negation+ * Triple-quoted multi-line strings+ * Type annotations in let expressions (Andrew)+ * Record Constructors+ * Record type aliases can be closed on the zeroth column+ * (,,) syntax in types+ * Allow infix op definitions without args: (*) = add+ * Unparenthesized if, let, case, lambda at end of binary expressions++elm-server:+ * Build multi-module projects+ * Report all errors in browser++Libraries:+ * Detect hovering over any Element+ * Set alpha of arbitrary forms in collages+ * Switch Text.height to use px instead of em++Bug Fixes:+ * Many bug fixes for collage, especially when rendering Elements.++Website:+ * Hot-swapping+ * Much faster page load with pre-compiled Elm files (Max New)+++++forgot to fill this in again...+++## 0.7.2++* Add a WebSockets library.+* Add support for the mathematical looking operator for function composition (U+2218).+++forgot to fill this in for a while...+++## 0.5.0++* Add Dict, Set, and Automaton libraries!++* Add (,,) notation for creating tuples.++* Redo HTTP library, allowing any kind of request and more flexibility.++* Remove the library prefixes `Data.`, `Graphics.`, and `Signal.` because+ they were more confusing than helpful.++* Better type error reporting for ambiguous uses of variables and for+ variables in aliased modules.++* Add `readInt` and `readFloat` functions.+* Add `complement` function to compute complementary colors.+* Ensure that `String` is treated as an alias of `[Char]`.++* Fix bug in pattern parsing. `A B _ _` was parsed as `A (B _ _)`.+* Make pattern matching a bit more compact in generated code.+* Make generated JS more readable.++* The Haskell API exports the absolute path to the Elm runtime+ system (with the corresponding version number). This makes it easier+ to run Elm programs with less setup.++++## 0.4.0++This version is all about graphics: nicer API with more features and major+efficiency improvements. I am really excited about this release!++* Add native Markdown support. You can now embed markdown directly in .elm files+ and it is used as an `Element`. Syntax is `[markdown| ... |]` where `...` is+ formatted as described [here](http://daringfireball.net/projects/markdown/).+ Content can span multiple lines too.++* Drastically improve the `collage` interface. You can now move, rotate, and scale+ the following forms:+ - Elements (any Element you want can be turned into a Form with `toForm`)+ - Images+ - Shapes (shapes can be textured now too)+ - Lines+ This will make it way easier to make games in Elm. Games can now include text,+ gifs, videos, and any other Element you can think of.++* Add `--minify` flag, to minify JS code.++* Significantly improve performance of pattern matching.++* Compiler performs beta-reduction in some simple cases.++* The rendering section of the Elm runtume-system (RTS) has been totally rewritten,+ making screen refreshes use fewer cycles, less memory, and cause less garbage-collection.++++## 0.3.6++* Add JSON library.++* Type-error messages improved. Gives better context for error, making them+ easier to find. Better messages for runtime errors as well (errors that+ the type checker cannot find yet).++* Add Comparable super-type which allows the comparision of any values+ of type {Int,Float,Char,String}. Now possible to make Set and Map libraries.++* Parser now handles decimal numbers.++* Added many new functions for manipulating numbers:+ - truncate, round, floor, ceiling :: Float -> Int+ - toFloat :: Int -> Float+ - (^) :: Number -> Number -> Number+ - e :: Float++* Foreign import/export statements no longer have to preceed all other+ variable and datatype definitions. They can be mixed in, making things+ a bit more readable/natural.++* Bug fixes:+ - The `toText` function did not escape strings properly+ - Correct `castJSTupleToTupleN` family of functions+ - `foldr1` took the leftmost element as the base case instead of the rightmost+ - Fix minor display issue in latest version of Chrome.+ - Fix behavior of [ lo .. hi ] syntax (now [4..0] == [], not [0]).++++## 0.3.5++* Add JavaScript event interface. Allows Elm to import and export JS values+ and events. This makes it possible to import and export Elements, so users+ can use JS techniques and libraries if necessary. Conversion between JS+ and Elm values happens with functions from here:+ http://localhost:8000/docs/Foreign/JavaScript.elm+ http://localhost:8000/docs/Foreign/JavaScript/Experimental.elm++* Add new flags to help with JavaScript event interface.++* Add three built-in event listeners (elm_title, elm_log, elm_redirect) that+ make it possible to make some common/simple imperative actions without+ having to worry about writing the JS yourself. For example:+ foreign export jsevent "elm_title"+ title :: Signal JSString+ will update the page's title to the current value of the title signal.+ Empty strings are ignored. "elm_redirect" and "elm_log" events work much+ the same way, except that "elm_log" does not skip empty strings.++* Add new Signal functions:+ count :: Signal a -> Signal Int+ keepIf :: (a -> Bool) -> a -> Signal a -> Signal a+ dropIf :: (a -> Bool) -> a -> Signal a -> Signal a+ keepWhen :: Signal Bool -> a -> Signal a -> Signal a+ dropWhen :: Signal Bool -> a -> Signal a -> Signal a+ dropRepeats :: Signal a -> Signal a+ sampleOn :: Signal a -> Signal b -> Signal b+ clicks :: Signal ()+ The keep and drop functions make it possible to filter events, which+ was not possible in prior releases. More documentation:+ http://elm-lang.org/docs/Signal/Signal.elm++* Add examples of JS event interface and new signal functions:+ https://github.com/evancz/Elm/tree/master/Examples/elm-js++* Use more compressed format for strings. Should make strings 10-12 times+ more space efficient than in previous releases. Anecdotal evidence:+ Elm's home page is now 70% of its previous size.++* Add new function to Data.List:+ last :: [a] -> a++* Fix parenthesization bug with binary operators.++++## 0.3.0++Major Changes (Read this part!)+-------------------------------++* Add a basic module system.+* Elm's JavaScript runtime is now distributed with the elm package.+ Previously it was available for download as an unversioned JavaScript+ file (elm-mini.js). It is now installed with the elm compiler as+ elm-runtime-0.3.0.js. Be sure to serve the Elm runtime system that matches+ the version of the compiler used to generate JavaScript. When working+ locally, the compiler will automatically use your local copy of this file.+* BREAKING CHANGE: rgb and rgba (in the color module) now take their red,+ green, and blue components as integers between 0 and 255 inclusive.+* Improve error messages for parse errors and runtime errors.+++New Functions and Other Additions+---------------------------------++* Add support for keyboard events: Keyboard.Raw+* Add buttons in Signal.Input:+ button :: String -> (Element, Signal Bool)+* Add new basic element (an empty rectangle, good for adding spaces):+ rectangle :: Int -> Int -> Element+* Add (an awkwardly named) way to display right justified text: rightedText+* Add two basic libraries: Data.Char and Data.Maybe+* Add some new colors: magenta, yellow, cyan, gray, grey+* Add functions to Data.List module: take, drop+* Add functions to Prelude (the default imports):+ fst, snd, curry, uncurry, and a bunch of list functions+* Add --make, --separate-js, and --only-js flags to help compile+ with the new module system.
+ elm-compiler.cabal view
@@ -0,0 +1,234 @@++Name: elm-compiler+Version: 0.14++Synopsis:+ Values to help with elm-package, elm-make, and elm-lang.org.++Description:+ Elm aims to make client-side web-development pleasant. It is a+ statically/strongly typed, functional reactive language that compiles to+ HTML, CSS, and JS. This package provides a couple helpful values that are+ intended for use in packages such as elm-package and elm-make.++Homepage: http://elm-lang.org++License: BSD3+License-file: LICENSE++Author: Evan Czaplicki+Maintainer: info@elm-lang.org+Copyright: Copyright (c) 2011-2014 Evan Czaplicki++Category: Compiler, Language++Extra-source-files: changelog.md++Cabal-version: >=1.9+Build-type: Simple++Data-files:+ runtime/debug.js++source-repository head+ type: git+ location: git://github.com/elm-lang/elm-compiler.git++Library+ ghc-options:+ -threaded -O2 -W++ Hs-Source-Dirs:+ src++ exposed-modules:+ Elm.Compiler,+ Elm.Compiler.Module,+ Elm.Compiler.Type,+ Elm.Docs,+ Elm.Utils++ other-modules:+ AST.Annotation,+ AST.Declaration,+ AST.Expression.General,+ AST.Expression.Source,+ AST.Expression.Valid,+ AST.Expression.Canonical,+ AST.Helpers,+ AST.Literal,+ AST.Module,+ AST.Pattern,+ AST.PrettyPrint,+ AST.Type,+ AST.Variable,+ Compile,+ Elm.Compiler.Type.Extract,+ Elm.Compiler.Version,+ Generate.JavaScript,+ Generate.JavaScript.Helpers,+ Generate.JavaScript.Ports,+ Generate.JavaScript.Variable,+ Generate.Cases,+ Transform.AddDefaultImports,+ Transform.Canonicalize,+ Transform.Canonicalize.Environment,+ Transform.Canonicalize.Setup,+ Transform.Canonicalize.Type,+ Transform.Canonicalize.Variable,+ Transform.Check,+ Transform.Expression,+ Transform.Declaration,+ Transform.Definition,+ Transform.Interface,+ Transform.SortDefinitions,+ Transform.Substitute,+ Parse.Binop,+ Parse.Declaration,+ Parse.Expression,+ Parse.Helpers,+ Parse.Literal,+ Parse.Module,+ Parse.Parse,+ Parse.Pattern,+ Parse.Type,+ Type.Constrain.Expression,+ Type.Constrain.Literal,+ Type.Constrain.Pattern,+ Type.Environment,+ Type.ExtraChecks,+ Type.Fragment,+ Type.Inference,+ Type.PrettyPrint,+ Type.Solve,+ Type.State,+ Type.Type,+ Type.Unify,+ Paths_elm_compiler++ Build-depends:+ aeson >= 0.7 && < 0.9,+ aeson-pretty >= 0.7 && < 0.8,+ base >=4.2 && <5,+ binary >= 0.7.0.0 && < 0.8,+ blaze-html >= 0.5 && < 0.8,+ blaze-markup >= 0.5.1 && < 0.7,+ bytestring >= 0.9 && < 0.11,+ cmdargs >= 0.7 && < 0.11,+ containers >= 0.3 && < 0.6,+ directory >= 1.0 && < 2.0,+ filepath >= 1 && < 2.0,+ indents >= 0.3 && < 0.4,+ language-ecmascript >=0.15 && < 0.17,+ language-glsl >= 0.0.2 && < 0.2,+ mtl >= 2 && < 3,+ parsec >= 3.1.1 && < 3.5,+ pretty >= 1.0 && < 2.0,+ process,+ text >= 1 && < 2,+ transformers >= 0.2 && < 0.5,+ union-find >= 0.2 && < 0.3,+ unordered-containers >= 0.1 && < 0.3+++Executable elm-doc+ ghc-options:+ -threaded -O2 -W++ Hs-Source-Dirs:+ src++ Main-is:+ Docs.hs++ other-modules:+ Elm.Compiler.Version,+ AST.Annotation,+ AST.Declaration,+ AST.Expression.General,+ AST.Helpers,+ AST.Literal,+ AST.Module,+ AST.Pattern,+ AST.PrettyPrint,+ AST.Type,+ AST.Variable,+ Parse.Binop,+ Parse.Declaration,+ Parse.Expression,+ Parse.Helpers,+ Parse.Literal,+ Parse.Module,+ Parse.Pattern,+ Parse.Type++ Build-depends:+ aeson >= 0.7 && < 0.9,+ aeson-pretty >= 0.7 && < 0.8,+ base >=4.2 && <5,+ binary >= 0.7.0.0 && < 0.8,+ bytestring >= 0.9 && < 0.11,+ cmdargs >= 0.7 && < 0.11,+ containers >= 0.3 && < 0.6,+ directory >= 1.0 && < 2.0,+ filepath >= 1 && < 2.0,+ indents >= 0.3 && < 0.4,+ language-glsl >= 0.0.2 && < 0.2,+ mtl >= 2 && < 3,+ parsec >= 3.1.1 && < 3.5,+ pretty >= 1.0 && < 2.0,+ process,+ text >= 1 && < 2,+ transformers >= 0.2 && < 0.5,+ union-find >= 0.2 && < 0.3+++Test-Suite compiler-tests+ Type:+ exitcode-stdio-1.0++ Hs-Source-Dirs:+ tests, src++ Main-is:+ Test.hs++ other-modules:+ Test.Compiler+ Test.Property+ Test.Property.Arbitrary+ AST.Helpers+ AST.Literal+ AST.PrettyPrint++ build-depends:+ test-framework > 0.8 && < 0.9,+ test-framework-hunit >= 0.3 && < 0.4,+ test-framework-quickcheck2 >= 0.3 && < 0.4,+ HUnit >= 1.1 && < 2,+ QuickCheck >= 2 && < 3,+ aeson >= 0.7 && < 0.9,+ aeson-pretty >= 0.7 && < 0.8,+ base >=4.2 && <5,+ binary >= 0.7.0.0 && < 0.8,+ blaze-html >= 0.5 && < 0.8,+ blaze-markup >= 0.5.1 && < 0.7,+ bytestring >= 0.9 && < 0.11,+ cmdargs >= 0.7 && < 0.11,+ containers >= 0.3 && < 0.6,+ directory >= 1.0 && < 2.0,+ elm-compiler,+ filemanip >= 0.3.5 && < 0.4,+ filepath >= 1 && < 2.0,+ indents >= 0.3 && < 0.4,+ language-ecmascript >=0.15 && < 0.17,+ language-glsl >= 0.0.2 && < 0.2,+ mtl >= 2 && < 3,+ parsec >= 3.1.1 && < 3.5,+ pretty >= 1.0 && < 2.0,+ process,+ text >= 1 && < 2,+ transformers >= 0.2 && < 0.5,+ union-find >= 0.2 && < 0.3,+ unordered-containers >= 0.1 && < 0.3+
+ runtime/debug.js view
@@ -0,0 +1,604 @@+(function() {+'use strict';++if (typeof window != 'undefined' && !window.location.origin) {+ window.location.origin =+ window.location.protocol + "//" ++ window.location.hostname ++ (window.location.port ? (':' + window.location.port) : '');+}++Elm.fullscreenDebugHooks = function(elmModule, debuggerHistory /* =undefined */) {+ var exposedDebugger = {};+ function debuggerAttach(elmModule, debuggerHistory) {+ return {+ make: function(runtime) {+ var wrappedModule = debugModule(elmModule, runtime);+ exposedDebugger = debuggerInit(wrappedModule, runtime, debuggerHistory);+ return wrappedModule.debuggedModule;+ }+ }+ }+ var mainHandle = Elm.fullscreen(debuggerAttach(elmModule, debuggerHistory));+ mainHandle.debugger = exposedDebugger;+ return mainHandle;+};++var EVENTS_PER_SAVE = 100;++function debugModule(module, runtime) {+ var programPaused = false;+ var recordedEvents = [];+ var asyncCallbacks = [];+ var snapshots = [];+ var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;+ var pauseTime = 0;+ var eventsUntilSnapshot = EVENTS_PER_SAVE;+ runtime.debuggerStatus = runtime.debuggerStatus || {};+ runtime.debuggerStatus.eventCounter = runtime.debuggerStatus.eventCounter || 0;++ // runtime is the prototype of wrappedRuntime+ // so we can access all runtime properties too+ var wrappedRuntime = Object.create(runtime);+ wrappedRuntime.notify = notifyWrapper;+ wrappedRuntime.setTimeout = setTimeoutWrapper;++ // make a copy of the wrappedRuntime+ var assignedPropTracker = Object.create(wrappedRuntime);+ var debuggedModule = module.make(assignedPropTracker);++ // make sure the signal graph is actually a signal & extract the visual model+ if ( !('recv' in debuggedModule.main) ) {+ debuggedModule.main = Elm.Signal.make(runtime).constant(debuggedModule.main);+ }++ // The main module stores imported modules onto the runtime.+ // To ensure only one instance of each module is created,+ // we assign them back on the original runtime object.+ Object.keys(assignedPropTracker).forEach(function(key) {+ runtime[key] = assignedPropTracker[key];+ });++ var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);+ var tracePath = tracePathInit(runtime, debuggedModule.main);++ snapshots.push(snapshotSignalGraph(signalGraphNodes));++ function notifyWrapper(id, v) {+ var timestep = runtime.timer.now();++ if (programPaused) {+ // ignore async events generated while playing back+ // or user events while program is paused+ return false;+ }+ else {+ recordEvent(id, v, timestep);+ var changed = runtime.notify(id, v, timestep);+ snapshotOnCheckpoint();+ if (parent.window) {+ parent.window.postMessage("elmNotify", window.location.origin);+ }+ return changed;+ }+ };++ function setTimeoutWrapper(func, delayMs) {+ if (programPaused) {+ // Don't push timers and such to the callback stack while we're paused.+ // It causes too many callbacks to be fired during unpausing.+ return 0;+ }+ var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };+ var timerId = setTimeout(function() {+ cbObj.executed = true;+ func();+ }, delayMs);+ cbObj.timerId = timerId;+ asyncCallbacks.push(cbObj);+ return timerId;+ }++ function recordEvent(id, v, timestep) {+ watchTracker.pushFrame();+ recordedEvents.push({ id:id, value:v, timestep:timestep });+ runtime.debuggerStatus.eventCounter += 1;+ }++ function clearAsyncCallbacks() {+ asyncCallbacks.forEach(function(timer) {+ if (!timer.executed) {+ clearTimeout(timer.timerId);+ }+ });+ }++ function clearRecordedEvents() {+ recordedEvents = [];+ runtime.debuggerStatus.eventCounter = 0;+ }++ function getRecordedEventsLength() {+ return recordedEvents.length;+ }++ function getRecordedEventAt(i) {+ return recordedEvents[i];+ }++ function copyRecordedEvents() {+ return recordedEvents.slice();+ }++ function loadRecordedEvents(events) {+ recordedEvents = events.slice();+ }++ function clearSnapshots() {+ snapshots = [snapshotSignalGraph(signalGraphNodes)];+ }++ function getSnapshotAt(i) {+ var snapshotEvent = Math.floor(i / EVENTS_PER_SAVE);+ assert(snapshotEvent < snapshots.length && snapshotEvent >= 0,+ "Out of bounds index: " + snapshotEvent);+ return snapshots[snapshotEvent];+ }++ function snapshotOnCheckpoint() {+ if (eventsUntilSnapshot === 1) {+ snapshots.push(snapshotSignalGraph(signalGraphNodes));+ eventsUntilSnapshot = EVENTS_PER_SAVE;+ } else {+ eventsUntilSnapshot -= 1;+ }+ }++ function setPaused() {+ programPaused = true;+ clearAsyncCallbacks();+ pauseTime = Date.now();+ tracePath.stopRecording();+ preventInputEvents();+ }++ function setContinue(position) {+ var pauseDelay = Date.now() - pauseTime;+ runtime.timer.addDelay(pauseDelay);+ programPaused = false;++ // we need to dump the events that are ahead of where we're continuing.+ var lastSnapshotPosition = Math.floor(position / EVENTS_PER_SAVE);+ eventsUntilSnapshot = EVENTS_PER_SAVE - (position % EVENTS_PER_SAVE);+ snapshots = snapshots.slice(0, lastSnapshotPosition + 1);++ if (position < getRecordedEventsLength()) {+ var lastEventTime = recordedEvents[position].timestep;+ var scrubTime = runtime.timer.now() - lastEventTime;+ runtime.timer.addDelay(scrubTime);+ }++ recordedEvents = recordedEvents.slice(0, position);+ tracePath.clearTracesAfter(position);+ runtime.debuggerStatus.eventCounter = position;+ executeCallbacks(asyncCallbacks);+ permitInputEvents();++ tracePath.startRecording();+ }++ function getPaused() {+ return programPaused;+ }++ function preventInputEvents(){+ var events =+ [ "click", "mousemove", "mouseup", "mousedown", "mouseclick"+ , "keydown", "keypress", "keyup", "touchstart", "touchend"+ , "touchcancel", "touchleave", "touchmove", "pointermove"+ , "pointerdown", "pointerup", "pointerover", "pointerout"+ , "pointerenter", "pointerleave", "pointercancel"+ ];++ var ignore = function(e) {+ var evt = e ? e : window.event;+ if (evt.stopPropagation) {+ evt.stopPropagation();+ }+ if (evt.cancelBubble !== null) {+ evt.cancelBubble = true;+ }+ if (evt.preventDefault) {+ evt.preventDefault();+ }+ return false;+ };++ var ignoringDiv = document.getElementById("elmEventIgnorer");+ if (!ignoringDiv) {+ ignoringDiv = document.createElement("div");+ ignoringDiv.id = "elmEventIgnorer";+ ignoringDiv.style.position = "absolute";+ ignoringDiv.style.top = "0px";+ ignoringDiv.style.left = "0px";+ ignoringDiv.style.width = "100%";+ ignoringDiv.style.height = "100%";++ for (var i = events.length; i-- ;) {+ ignoringDiv.addEventListener(events[i], ignore, true);+ }+ runtime.node.appendChild(ignoringDiv);+ }+ }++ function permitInputEvents(){+ var ignoringDiv = document.getElementById("elmEventIgnorer");+ ignoringDiv.parentNode.removeChild(ignoringDiv);+ }++ return {+ debuggedModule: debuggedModule,+ signalGraphNodes: signalGraphNodes,+ initialSnapshot: snapshotSignalGraph(signalGraphNodes),+ initialAsyncCallbacks: asyncCallbacks.slice(),+ // API functions+ clearAsyncCallbacks: clearAsyncCallbacks,+ clearRecordedEvents: clearRecordedEvents,+ getRecordedEventsLength: getRecordedEventsLength,+ getRecordedEventAt: getRecordedEventAt,+ copyRecordedEvents: copyRecordedEvents,+ loadRecordedEvents: loadRecordedEvents,+ clearSnapshots: clearSnapshots,+ getSnapshotAt: getSnapshotAt,+ snapshotOnCheckpoint: snapshotOnCheckpoint,+ getPaused: getPaused,+ setPaused: setPaused,+ setContinue: setContinue,+ tracePath: tracePath,+ watchTracker: watchTracker+ };+}++// The debuggerHistory variable is passed in on swap. It represents+// the a state of the debugger for it to assume during init. It contains+// the paused state of the debugger, the recorded events, and the current+// event being processed.+function debuggerInit(debugModule, runtime, debuggerHistory /* =undefined */) {+ var currentEventIndex = 0;++ function resetProgram(position) {+ var closestSnapshot = debugModule.getSnapshotAt(position);+ debugModule.clearAsyncCallbacks();+ restoreSnapshot(debugModule.signalGraphNodes, closestSnapshot);+ redrawGraphics();+ }++ function restartProgram() {+ pauseProgram();+ resetProgram(0);+ debugModule.watchTracker.clear();+ debugModule.tracePath.clearTraces();+ debugModule.setContinue(0);+ debugModule.clearRecordedEvents();+ debugModule.clearSnapshots();+ executeCallbacks(debugModule.initialAsyncCallbacks);+ }++ function pauseProgram() {+ debugModule.setPaused();+ currentEventIndex = debugModule.getRecordedEventsLength();+ }++ function continueProgram() {+ if (debugModule.getPaused())+ {+ var closestSnapshotIndex =+ Math.floor(currentEventIndex / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;+ resetProgram(currentEventIndex);+ var continueIndex = currentEventIndex;+ currentEventIndex = closestSnapshotIndex;+ stepTo(continueIndex);+ debugModule.setContinue(currentEventIndex);+ }+ }++ function stepTo(index) {+ if (!debugModule.getPaused()) {+ debugModule.setPaused();+ resetProgram();+ }++ if (index < 0 || index > getMaxSteps()) {+ throw "Index out of bounds: " + index;+ }++ if (index < currentEventIndex) {+ var closestSnapshotIndex = Math.floor(index / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;+ resetProgram(index);+ currentEventIndex = closestSnapshotIndex;+ }++ while (currentEventIndex < index) {+ var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);++ currentEventIndex += 1;+ }+ }++ function getMaxSteps() {+ return debugModule.getRecordedEventsLength();+ }++ function redrawGraphics() {+ var main = debugModule.debuggedModule.main+ for (var i = main.kids.length ; i-- ; ) {+ main.kids[i].recv(runtime.timer.now(), true, main.id);+ }+ }++ function getSwapState() {+ var continueIndex = currentEventIndex;+ if (!debugModule.getPaused()) {+ continueIndex = getMaxSteps();+ }+ return {+ paused: debugModule.getPaused(),+ recordedEvents: debugModule.copyRecordedEvents(),+ currentEventIndex: continueIndex+ };+ }++ function dispose() {+ var parentNode = runtime.node.parentNode;+ parentNode.removeChild(debugModule.tracePath.canvas);+ parentNode.removeChild(runtime.node);+ }++ if (debuggerHistory) {+ // The problem is that we want to previous paused state. But+ // by the time JS reaches here, the old code has been swapped out+ // and the new modules are being generated. So we can ask the+ // debugging console what it thinks the pause state is and go+ // from there.+ var paused = debuggerHistory.paused;+ debugModule.setPaused();+ debugModule.loadRecordedEvents(debuggerHistory.recordedEvents);+ var index = getMaxSteps();+ runtime.debuggerStatus.eventCounter = 0;+ debugModule.tracePath.clearTraces();++ // draw new trace path+ debugModule.tracePath.startRecording();+ while(currentEventIndex < index) {+ var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);+ runtime.debuggerStatus.eventCounter += 1;+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);+ debugModule.snapshotOnCheckpoint();+ currentEventIndex += 1;+ }+ debugModule.tracePath.stopRecording();++ stepTo(debuggerHistory.currentEventIndex);+ if (!paused) {+ debugModule.setContinue(debuggerHistory.currentEventIndex);+ }+ }++ runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);++ var elmDebugger = {+ restart: restartProgram,+ pause: pauseProgram,+ kontinue: continueProgram,+ getMaxSteps: getMaxSteps,+ stepTo: stepTo,+ getPaused: debugModule.getPaused,+ getSwapState: getSwapState,+ dispose: dispose,+ allNodes: debugModule.signalGraphNodes,+ watchTracker: debugModule.watchTracker+ };++ return elmDebugger;+}++function Point(x, y) {+ this.x = x;+ this.y = y;++ this.translate = function(x, y) {+ return new Point(this.x + x, this.y + y);+ }++ this.equals = function(p) {+ return this.x == p.x && this.y == p.y;+ }+}++function tracePathInit(runtime, signalGraphMain) {+ var List = Elm.List.make(runtime);+ var Signal = Elm.Signal.make(runtime);+ var tracePathNode = A2(Signal.map, graphicsUpdate, signalGraphMain);+ var tracePathCanvas = createCanvas();+ var tracePositions = {};+ var recordingTraces = true;++ function findPositions(currentScene) {+ var positions = {};+ function processElement(elem, offset) {+ if (elem.element.ctor == "Custom" && elem.element.type == "Collage")+ {+ List.map(F2(processForm)(offset))(elem.element.model.forms);+ }+ }++ function processForm(offset, form) {+ if (form.form.ctor == "FElement")+ {+ processElement(form.form._0, offset.translate(form.x, -form.y));+ }+ if (form.form.ctor == "FGroup")+ {+ var newOffset = offset.translate(form.x, -form.y);+ List.map(F2(processForm)(newOffset))(form.form._1);+ }+ if (form.debugTracePathId)+ {+ positions[form.debugTracePathId] = new Point(form.x + offset.x, -form.y + offset.y);+ }+ }++ processElement(currentScene, new Point(0, 0));+ return positions;+ }++ function appendPositions(positions) {+ for (var id in positions) {+ var pos = positions[id];+ if (tracePositions.hasOwnProperty(id)) {+ tracePositions[id].push(pos);+ }+ else {+ tracePositions[id] = [pos];+ }+ if (tracePositions[id].length < runtime.debuggerStatus.eventCounter) {+ var padCount = runtime.debuggerStatus.eventCounter - tracePositions[id].length;+ var lastTracePosition = tracePositions[id][tracePositions[id].length - 1];+ for (var i = padCount; i--;) {+ tracePositions[id].push(lastTracePosition)+ }+ }+ assert(tracePositions[id].length === runtime.debuggerStatus.eventCounter,+ "We don't have a 1-1 mapping of trace positions to events");+ }+ }++ function graphicsUpdate(currentScene) {+ if (!recordingTraces) {+ return;+ }++ var ctx = tracePathCanvas.getContext('2d');+ ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);++ ctx.save();+ ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);+ appendPositions(findPositions(currentScene));+ for (var id in tracePositions)+ {+ ctx.beginPath();+ var points = tracePositions[id];+ for (var i=0; i < points.length; i++)+ {+ var p = points[i];+ if (i == 0) {+ ctx.moveTo(p.x, p.y);+ }+ else {+ ctx.lineTo(p.x, p.y);+ }+ }+ ctx.lineWidth = 1;+ ctx.strokeStyle = "rgba(50, 50, 50, 0.4)";+ ctx.stroke();+ }++ ctx.restore();+ }++ function clearTraces() {+ tracePositions = {};+ }++ function stopRecording() {+ recordingTraces = false;+ }++ function startRecording() {+ recordingTraces = true;+ }++ function clearTracesAfter(position) {+ var newTraces = {};+ for (var id in tracePositions) {+ newTraces[id] = tracePositions[id].slice(0,position);+ }+ tracePositions = newTraces;+ }++ return {+ graphicsUpdate: graphicsUpdate,+ canvas: tracePathCanvas,+ clearTraces: clearTraces,+ clearTracesAfter: clearTracesAfter,+ stopRecording: stopRecording,+ startRecording: startRecording+ }+}++function executeCallbacks(callbacks) {+ callbacks.forEach(function(timer) {+ if (!timer.executed) {+ var func = timer.func;+ timer.executed = true;+ func();+ }+ });+}++function createCanvas() {+ var c = document.createElement('canvas');+ c.width = window.innerWidth;+ c.height = window.innerHeight;+ c.style.position = "absolute";+ c.style.top = "0";+ c.style.left = "0";+ c.style.pointerEvents = "none";+ return c;+}++function assert(bool, msg) {+ if (!bool) {+ throw "Assertion error: " + msg;+ }+}++function snapshotSignalGraph(signalGraphNodes) {+ var nodeValues = [];++ signalGraphNodes.forEach(function(node) {+ nodeValues.push({ value: node.value, id: node.id });+ });++ return nodeValues;+};++function restoreSnapshot(signalGraphNodes, snapshot) {+ assert(signalGraphNodes.length == snapshot.length,+ "saved program state has wrong length");+ for (var i=0; i < signalGraphNodes.length; i++) {+ var node = signalGraphNodes[i];+ var state = snapshot[i];+ assert(node.id == state.id, "the nodes moved position");++ node.value = state.value;+ }+}++function flattenSignalGraph(nodes) {+ var nodesById = {};++ function addAllToDict(node) {+ nodesById[node.id] = node;+ node.kids.forEach(addAllToDict);+ }+ nodes.forEach(addAllToDict);++ var allNodes = Object.keys(nodesById).sort().map(function(key) {+ return nodesById[key];+ });+ return allNodes;+};++}());
+ src/AST/Annotation.hs view
@@ -0,0 +1,73 @@+module AST.Annotation where++import qualified Text.Parsec.Pos as Parsec+import qualified Text.PrettyPrint as P+import AST.PrettyPrint++data Annotated annotation expr = A annotation expr+ deriving (Show)++data Region+ = Span Position Position P.Doc+ | None P.Doc+ deriving (Show)++data Position = Position+ { line :: Int+ , column :: Int+ } deriving (Show)++type Located expr = Annotated Region expr++none e = A (None (pretty e)) e+noneNoDocs e = A (None P.empty) e++at :: (Pretty expr) => Parsec.SourcePos -> Parsec.SourcePos -> expr+ -> Annotated Region expr+at start end e =+ A (Span (position start) (position end) (pretty e)) e+ where+ position loc = Position (Parsec.sourceLine loc) (Parsec.sourceColumn loc)++merge (A s1 _) (A s2 _) e =+ A (span (pretty e)) e+ where+ span = case (s1,s2) of+ (Span start _ _, Span _ end _) -> Span start end+ (Span start end _, _) -> Span start end+ (_, Span start end _) -> Span start end+ (_, _) -> None++mergeOldDocs (A s1 _) (A s2 _) e =+ A span e+ where+ span = case (s1,s2) of+ (Span start _ d1, Span _ end d2) ->+ Span start end (P.vcat [d1, P.text "\n", d2])++ (Span _ _ _, _) -> s1+ (_, Span _ _ _) -> s2+ (_, _) -> None P.empty++sameAs :: Annotated a expr -> expr' -> Annotated a expr'+sameAs (A annotation _) expr = A annotation expr++getRegionDocs region =+ case region of+ Span _ _ doc -> doc+ None doc -> doc++instance Pretty Region where+ pretty span = + case span of+ None _ -> P.empty+ Span start end _ ->+ P.text $+ case line start == line end of+ False -> "between lines " ++ show (line start) ++ " and " ++ show (line end)+ True -> "on line " ++ show (line end) ++ ", column " +++ show (column start) ++ " to " ++ show (column end)++instance Pretty e => Pretty (Annotated a e) where+ pretty (A _ e) = pretty e+
+ src/AST/Declaration.hs view
@@ -0,0 +1,144 @@+{-# OPTIONS_GHC -Wall #-}+module AST.Declaration where++import Data.Binary+import qualified AST.Expression.Source as Source+import qualified AST.Expression.Valid as Valid+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Type as T+import qualified AST.Variable as Var+import AST.PrettyPrint+import Text.PrettyPrint as P++++data Declaration' port def var+ = Definition def+ | Datatype String [String] [(String, [T.Type var])]+ | TypeAlias String [String] (T.Type var)+ | Port port+ | Fixity Assoc Int String+++data Assoc = L | N | R+ deriving (Eq)+++data RawPort+ = PPAnnotation String T.RawType+ | PPDef String Source.Expr+++data Port expr var+ = Out String expr (T.Type var)+ | In String (T.Type var)+++type SourceDecl = Declaration' RawPort Source.Def Var.Raw+type ValidDecl = Declaration' (Port Valid.Expr Var.Raw) Valid.Def Var.Raw+type CanonicalDecl = Declaration' (Port Canonical.Expr Var.Canonical)+ Canonical.Def+ Var.Canonical+++portName :: Port expr var -> String+portName port =+ case port of+ Out name _ _ -> name+ In name _ -> name+++assocToString :: Assoc -> String+assocToString assoc =+ case assoc of+ L -> "left"+ N -> "non"+ R -> "right"+++-- BINARY CONVERSION++instance Binary Assoc where+ get =+ do n <- getWord8+ return $ case n of+ 0 -> L+ 1 -> N+ 2 -> R+ _ -> error "Error reading valid associativity from serialized string"++ put assoc =+ putWord8 $+ case assoc of+ L -> 0+ N -> 1+ R -> 2+++-- PRETTY STRINGS++instance (Pretty port, Pretty def, Pretty var, Var.ToString var) =>+ Pretty (Declaration' port def var) where+ pretty decl =+ case decl of+ Definition def -> pretty def++ Datatype tipe tvars ctors ->+ P.hang+ (P.text "type" <+> P.text tipe <+> P.hsep (map P.text tvars))+ 4+ (P.sep $ zipWith (<+>) seperators (map prettyCtor ctors))+ where+ seperators =+ map P.text ("=" : repeat "|")++ prettyCtor (name, tipes) =+ P.hang (P.text name) 2 (P.sep (map T.prettyParens tipes))++ TypeAlias name tvars tipe ->+ P.hang+ (P.text "type" <+> P.text "alias" <+> name' <+> P.equals)+ 4+ (pretty tipe)+ where+ name' =+ P.text name <+> P.hsep (map P.text tvars)++ Port port -> pretty port++ Fixity assoc prec op ->+ P.text "infix" <> assoc' <+> P.int prec <+> P.text op+ where+ assoc' =+ case assoc of+ L -> P.text "l"+ N -> P.empty+ R -> P.text "r"+++instance Pretty RawPort where+ pretty port =+ case port of+ PPAnnotation name tipe ->+ prettyPort name ":" tipe++ PPDef name expr ->+ prettyPort name "=" expr+++instance (Pretty expr, Pretty var, Var.ToString var) => Pretty (Port expr var) where+ pretty port =+ case port of+ In name tipe ->+ prettyPort name ":" tipe++ Out name expr tipe ->+ P.vcat+ [ prettyPort name ":" tipe+ , prettyPort name "=" expr+ ]+ ++prettyPort :: (Pretty a) => String -> String -> a -> Doc+prettyPort name op e =+ P.text "port" <+> P.text name <+> P.text op <+> pretty e
+ src/AST/Expression/Canonical.hs view
@@ -0,0 +1,30 @@+{-# OPTIONS_GHC -Wall #-}++module AST.Expression.Canonical where++import AST.PrettyPrint+import Text.PrettyPrint as P+import qualified AST.Expression.General as General+import AST.Type (CanonicalType)+import qualified AST.Annotation as Annotation+import qualified AST.Pattern as Pattern+import qualified AST.Variable as Var+++{-| Canonicalized expressions. All variables are fully resolved to the module+they came from.+-}+type Expr = General.Expr Annotation.Region Def Var.Canonical+type Expr' = General.Expr' Annotation.Region Def Var.Canonical++data Def = Definition Pattern.CanonicalPattern Expr (Maybe CanonicalType)+ deriving (Show)++instance Pretty Def where+ pretty (Definition pattern expr maybeTipe) =+ P.vcat [ annotation, definition ]+ where+ definition = pretty pattern <+> P.equals <+> pretty expr+ annotation = case maybeTipe of+ Nothing -> P.empty+ Just tipe -> pretty pattern <+> P.colon <+> pretty tipe
+ src/AST/Expression/General.hs view
@@ -0,0 +1,218 @@+{-# OPTIONS_GHC -Wall #-}++{-| The Abstract Syntax Tree (AST) for expressions comes in a couple formats.+The first is the fully general version and is labeled with a prime (Expr').+The others are specialized versions of the AST that represent specific phases+of the compilation process. I expect there to be more phases as we begin to+enrich the AST with more information.+-}+module AST.Expression.General where++import AST.PrettyPrint+import Text.PrettyPrint as P+import AST.Type (Type)++import qualified AST.Annotation as Annotation+import qualified AST.Helpers as Help+import qualified AST.Literal as Literal+import qualified AST.Pattern as Pattern+import qualified AST.Variable as Var++---- GENERAL AST ----++{-| This is a fully general Abstract Syntax Tree (AST) for expressions. It has+"type holes" that allow us to enrich the AST with additional information as we+move through the compilation process. The type holes are used to represent:++ ann: Annotations for arbitrary expressions. Allows you to add information+ to the AST like position in source code or inferred types.++ def: Definition style. The source syntax separates type annotations and+ definitions, but after parsing we check that they are well formed and+ collapse them.++ var: Representation of variables. Starts as strings, but is later enriched+ with information about what module a variable came from.++-}+type Expr annotation definition variable =+ Annotation.Annotated annotation (Expr' annotation definition variable)++data Expr' ann def var+ = Literal Literal.Literal+ | Var var+ | Range (Expr ann def var) (Expr ann def var)+ | ExplicitList [Expr ann def var]+ | Binop var (Expr ann def var) (Expr ann def var)+ | Lambda (Pattern.Pattern var) (Expr ann def var)+ | App (Expr ann def var) (Expr ann def var)+ | MultiIf [(Expr ann def var,Expr ann def var)]+ | Let [def] (Expr ann def var)+ | Case (Expr ann def var) [(Pattern.Pattern var, Expr ann def var)]+ | Data String [Expr ann def var]+ | Access (Expr ann def var) String+ | Remove (Expr ann def var) String+ | Insert (Expr ann def var) String (Expr ann def var)+ | Modify (Expr ann def var) [(String, Expr ann def var)]+ | Record [(String, Expr ann def var)]+ -- for type checking and code gen only+ | PortIn String (Type var)+ | PortOut String (Type var) (Expr ann def var)+ | GLShader String String Literal.GLShaderTipe+ deriving (Show)+++---- UTILITIES ----++rawVar :: String -> Expr' ann def Var.Raw+rawVar x = Var (Var.Raw x)++localVar :: String -> Expr' ann def Var.Canonical+localVar x = Var (Var.Canonical Var.Local x)++tuple :: [Expr ann def var] -> Expr' ann def var+tuple es = Data ("_Tuple" ++ show (length es)) es++delist :: Expr ann def var -> [Expr ann def var]+delist (Annotation.A _ (Data "::" [h,t])) = h : delist t+delist _ = []++saveEnvName :: String+saveEnvName = "_save_the_environment!!!"++dummyLet :: (Pretty def) => [def] -> Expr Annotation.Region def Var.Canonical+dummyLet defs = + Annotation.none $ Let defs (Annotation.none $ Var (Var.builtin saveEnvName))++instance (Pretty def, Pretty var, Var.ToString var) => Pretty (Expr' ann def var) where+ pretty expr =+ case expr of+ Literal lit ->+ pretty lit++ Var x ->+ pretty x++ Range e1 e2 ->+ P.brackets (pretty e1 <> P.text ".." <> pretty e2)++ ExplicitList es ->+ P.brackets (commaCat (map pretty es))++ Binop op (Annotation.A _ (Literal (Literal.IntNum 0))) e+ | Var.toString op == "-" ->+ P.text "-" <> prettyParens e++ Binop op e1 e2 ->+ P.hang (prettyParens e1) 2 (P.text op'' <+> prettyParens e2)+ where+ op' = Var.toString op+ op'' = if Help.isOp op' then op' else "`" ++ op' ++ "`"++ Lambda p e ->+ P.text "\\" <> args <+> P.text "->" <+> pretty body+ where+ (ps,body) = collectLambdas (Annotation.A undefined $ Lambda p e)+ args = P.sep (map Pattern.prettyParens ps)++ App _ _ ->+ P.hang func 2 (P.sep args)+ where+ func:args =+ map prettyParens (collectApps (Annotation.A undefined expr))++ MultiIf branches ->+ P.text "if" $$ nest 3 (vcat $ map iff branches)+ where+ iff (b,e) = P.text "|" <+> P.hang (pretty b <+> P.text "->") 2 (pretty e)++ Let defs e ->+ P.sep+ [ P.hang (P.text "let") 4 (P.vcat (map pretty defs))+ , P.text "in" <+> pretty e+ ]++ Case e pats ->+ P.hang pexpr 2 (P.vcat (map pretty' pats))+ where+ pexpr = P.sep [ P.text "case" <+> pretty e, P.text "of" ]+ pretty' (p,b) = pretty p <+> P.text "->" <+> pretty b++ Data "::" [hd,tl] -> pretty hd <+> P.text "::" <+> pretty tl+ Data "[]" [] -> P.text "[]"+ Data name es+ | Help.isTuple name -> P.parens (commaCat (map pretty es))+ | otherwise -> P.hang (P.text name) 2 (P.sep (map prettyParens es))++ Access e x ->+ prettyParens e <> P.text "." <> variable x++ Remove e x ->+ P.braces (pretty e <+> P.text "-" <+> variable x)++ Insert (Annotation.A _ (Remove e y)) x v ->+ P.braces $+ P.hsep+ [ pretty e, P.text "-", variable y, P.text "|"+ , variable x, P.equals, pretty v+ ]++ Insert e x v ->+ P.braces (pretty e <+> P.text "|" <+> variable x <+> P.equals <+> pretty v)++ Modify e fs ->+ P.braces $+ P.hang+ (pretty e <+> P.text "|")+ 4+ (commaSep $ map field fs)+ where+ field (k,v) = variable k <+> P.text "<-" <+> pretty v++ Record fs ->+ P.sep+ [ P.cat (zipWith (<+>) (P.lbrace : repeat P.comma) (map field fs))+ , P.rbrace+ ]+ where+ field (field, value) =+ variable field <+> P.equals <+> pretty value++ GLShader _ _ _ -> P.text "[glsl| ... |]"++ PortIn name _ -> P.text $ "<port:" ++ name ++ ">"++ PortOut _ _ signal -> pretty signal+++collectApps :: Expr ann def var -> [Expr ann def var]+collectApps annExpr@(Annotation.A _ expr) =+ case expr of+ App a b -> collectApps a ++ [b]+ _ -> [annExpr]+++collectLambdas :: Expr ann def var -> ([Pattern.Pattern var], Expr ann def var)+collectLambdas lexpr@(Annotation.A _ expr) =+ case expr of+ Lambda pattern body ->+ let (ps, body') = collectLambdas body+ in (pattern : ps, body')++ _ -> ([], lexpr)+++prettyParens :: (Pretty def, Pretty var, Var.ToString var) => Expr ann def var -> Doc+prettyParens (Annotation.A _ expr) =+ parensIf needed (pretty expr)+ where+ needed =+ case expr of+ Binop _ _ _ -> True+ Lambda _ _ -> True+ App _ _ -> True+ MultiIf _ -> True+ Let _ _ -> True+ Case _ _ -> True+ Data name (_:_) -> not (name == "::" || Help.isTuple name)+ _ -> False
+ src/AST/Expression/Source.hs view
@@ -0,0 +1,32 @@+{-# OPTIONS_GHC -Wall #-}++module AST.Expression.Source where++import AST.PrettyPrint+import Text.PrettyPrint as P+import qualified AST.Expression.General as General+import AST.Type (RawType)+import qualified AST.Variable as Var+import qualified AST.Annotation as Annotation+import qualified AST.Pattern as Pattern+++{-| Expressions created by the parser. These use a split representation of type+annotations and definitions, which is how they appear in source code and how+they are parsed.+-}+type Expr = General.Expr Annotation.Region Def Var.Raw+type Expr' = General.Expr' Annotation.Region Def Var.Raw++data Def+ = Definition Pattern.RawPattern Expr+ | TypeAnnotation String RawType+ deriving (Show)++instance Pretty Def where+ pretty def =+ case def of+ TypeAnnotation name tipe ->+ variable name <+> P.colon <+> pretty tipe+ Definition pattern expr ->+ pretty pattern <+> P.equals <+> pretty expr
+ src/AST/Expression/Valid.hs view
@@ -0,0 +1,31 @@+{-# OPTIONS_GHC -Wall #-}++module AST.Expression.Valid where++import AST.PrettyPrint+import Text.PrettyPrint as P+import qualified AST.Expression.General as General+import AST.Type (RawType)+import qualified AST.Variable as Var+import qualified AST.Annotation as Annotation+import qualified AST.Pattern as Pattern+++{-| "Normal" expressions. When the compiler checks that type annotations and+ports are all paired with definitions in the appropriate order, it collapses+them into a Def that is easier to work with in later phases of compilation.+-}+type Expr = General.Expr Annotation.Region Def Var.Raw+type Expr' = General.Expr' Annotation.Region Def Var.Raw++data Def = Definition Pattern.RawPattern Expr (Maybe RawType)+ deriving (Show)++instance Pretty Def where+ pretty (Definition pattern expr maybeTipe) =+ P.vcat [ annotation, definition ]+ where+ definition = pretty pattern <+> P.equals <+> pretty expr+ annotation = case maybeTipe of+ Nothing -> P.empty+ Just tipe -> pretty pattern <+> P.colon <+> pretty tipe
+ src/AST/Helpers.hs view
@@ -0,0 +1,27 @@+{-# OPTIONS_GHC -Wall #-}+module AST.Helpers where++import qualified Data.Char as Char++splitDots :: String -> [String]+splitDots = go []+ where+ go vars str =+ case break (=='.') str of+ (x,_:rest) | isOp x -> vars ++ [x ++ '.' : rest]+ | otherwise -> go (vars ++ [x]) rest+ (x,[]) -> vars ++ [x]++brkt :: String -> String+brkt s = "{ " ++ s ++ " }"++isTuple :: String -> Bool+isTuple name =+ take 6 name == "_Tuple" && all Char.isDigit (drop 6 name)++isOp :: String -> Bool+isOp = all isSymbol++isSymbol :: Char -> Bool+isSymbol c =+ Char.isSymbol c || elem c "+-/*=.$<>:&|^?%#@~!"
+ src/AST/Literal.hs view
@@ -0,0 +1,55 @@+{-# OPTIONS_GHC -Wall #-}+module AST.Literal where++import AST.PrettyPrint+import Data.Map (Map)+import qualified Text.PrettyPrint as PP++data Literal+ = IntNum Int+ | FloatNum Double+ | Chr Char+ | Str String+ | Boolean Bool+ deriving (Eq, Ord, Show)+++instance Pretty Literal where+ pretty literal =+ case literal of+ IntNum n -> PP.int n+ FloatNum n -> PP.double n+ Chr c -> PP.text . show $ c+ Str s -> PP.text . show $ s+ Boolean bool -> PP.text (show bool)+++data GLTipe+ = Int+ | Float+ | V2+ | V3+ | V4+ | M4+ | Texture+ deriving (Show)+++glTipeName :: GLTipe -> String+glTipeName glTipe =+ case glTipe of+ Int -> "Int"+ Float -> "Float"+ V2 -> "Math.Vector2.Vec2"+ V3 -> "Math.Vector3.Vec3"+ V4 -> "Math.Vector4.Vec4"+ M4 -> "Math.Matrix4.Mat4"+ Texture -> "WebGL.Texture"+++data GLShaderTipe = GLShaderTipe+ { attribute :: Map String GLTipe+ , uniform :: Map String GLTipe+ , varying :: Map String GLTipe+ }+ deriving (Show)
+ src/AST/Module.hs view
@@ -0,0 +1,168 @@+module AST.Module where++import Data.Binary+import qualified Data.List as List+import qualified Data.Map as Map+import Control.Applicative ((<$>),(<*>))++import qualified AST.Expression.Canonical as Canonical+import qualified AST.Declaration as Decl+import qualified AST.Type as Type+import qualified AST.Variable as Var+import AST.PrettyPrint+import qualified Elm.Compiler.Version as Compiler+import Text.PrettyPrint as P+++-- HELPFUL TYPE ALIASES++type Interfaces = Map.Map Name Interface++type Types = Map.Map String Type.CanonicalType+type Aliases = Map.Map String ([String], Type.CanonicalType)+type ADTs = Map.Map String (AdtInfo String)++type AdtInfo v = ( [String], [(v, [Type.CanonicalType])] )+type CanonicalAdt = (Var.Canonical, AdtInfo Var.Canonical)+++-- MODULES++type SourceModule =+ Module (Var.Listing Var.Value) [Decl.SourceDecl]++type ValidModule =+ Module (Var.Listing Var.Value) [Decl.ValidDecl]++type CanonicalModule =+ Module [Var.Value] CanonicalBody+++data Module exports body = Module+ { names :: Name+ , path :: FilePath+ , exports :: exports+ , imports :: [(Name, ImportMethod)]+ , body :: body+ }++data CanonicalBody = CanonicalBody+ { program :: Canonical.Expr+ , types :: Types+ , fixities :: [(Decl.Assoc, Int, String)]+ , aliases :: Aliases+ , datatypes :: ADTs+ , ports :: [String]+ }+++-- HEADERS++{-| Basic info needed to identify modules and determine dependencies. -}+data HeaderAndImports = HeaderAndImports+ { _names :: Name+ , _exports :: Var.Listing Var.Value+ , _imports :: [(Name, ImportMethod)]+ }++type Name = [String] -- must be non-empty++nameToString :: Name -> String+nameToString = List.intercalate "."++nameIsNative :: Name -> Bool+nameIsNative name =+ case name of+ "Native" : _ -> True+ _ -> False++++-- INTERFACES++{-| Key facts about a module, used when reading info from .elmi files. -}+data Interface = Interface+ { iVersion :: String+ , iExports :: [Var.Value]+ , iTypes :: Types+ , iImports :: [(Name, ImportMethod)]+ , iAdts :: ADTs+ , iAliases :: Aliases+ , iFixities :: [(Decl.Assoc, Int, String)]+ , iPorts :: [String]+ }++toInterface :: CanonicalModule -> Interface+toInterface modul =+ let body' = body modul in+ Interface+ { iVersion = Compiler.version+ , iExports = exports modul+ , iTypes = types body'+ , iImports = imports modul+ , iAdts = datatypes body'+ , iAliases = aliases body'+ , iFixities = fixities body'+ , iPorts = ports body'+ }++instance Binary Interface where+ get = Interface <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get+ put modul = do+ put (iVersion modul)+ put (iExports modul)+ put (iTypes modul)+ put (iImports modul)+ put (iAdts modul)+ put (iAliases modul)+ put (iFixities modul)+ put (iPorts modul)+++-- IMPORT METHOD++data ImportMethod+ = As !String+ | Open !(Var.Listing Var.Value)++open :: ImportMethod+open = Open (Var.openListing)++importing :: [Var.Value] -> ImportMethod+importing xs = Open (Var.Listing xs False)++instance Binary ImportMethod where+ put method =+ case method of+ As alias -> putWord8 0 >> put alias+ Open listing -> putWord8 1 >> put listing++ get = do tag <- getWord8+ case tag of+ 0 -> As <$> get+ 1 -> Open <$> get+ _ -> error "Error reading valid ImportMethod type from serialized string"+++-- PRETTY PRINTING++instance (Pretty exs, Pretty body) => Pretty (Module exs body) where+ pretty (Module names _ exs ims body) =+ P.vcat [modul, P.text "", prettyImports, P.text "", pretty body]+ where + modul = P.text "module" <+> name <+> pretty exs <+> P.text "where"+ name = P.text (List.intercalate "." names)++ prettyImports =+ P.vcat $ map prettyMethod ims+++prettyMethod :: (Name, ImportMethod) -> Doc+prettyMethod import' =+ case import' of+ ([name], As alias)+ | name == alias -> P.empty++ (_, As alias) -> P.text "as" <+> P.text alias++ (_, Open listing) -> pretty listing
+ src/AST/Pattern.hs view
@@ -0,0 +1,79 @@+{-# OPTIONS_GHC -Wall #-}+module AST.Pattern where++import qualified AST.Helpers as Help+import AST.PrettyPrint+import Text.PrettyPrint as PP+import qualified Data.Set as Set++import qualified AST.Variable as Var+import AST.Literal as Literal++data Pattern var+ = Data var [Pattern var]+ | Record [String]+ | Alias String (Pattern var)+ | Var String+ | Anything+ | Literal Literal.Literal+ deriving (Eq, Ord, Show)++type RawPattern = Pattern Var.Raw+type CanonicalPattern = Pattern Var.Canonical++cons :: RawPattern -> RawPattern -> RawPattern+cons h t = Data (Var.Raw "::") [h,t]++nil :: RawPattern+nil = Data (Var.Raw "[]") []++list :: [RawPattern] -> RawPattern+list = foldr cons nil++tuple :: [RawPattern] -> RawPattern+tuple es = Data (Var.Raw ("_Tuple" ++ show (length es))) es++boundVarList :: Pattern var -> [String]+boundVarList = Set.toList . boundVars++boundVars :: Pattern var -> Set.Set String+boundVars pattern =+ case pattern of+ Var x -> Set.singleton x+ Alias x p -> Set.insert x (boundVars p)+ Data _ ps -> Set.unions (map boundVars ps)+ Record fields -> Set.fromList fields+ Anything -> Set.empty+ Literal _ -> Set.empty+++instance Var.ToString var => Pretty (Pattern var) where+ pretty pattern =+ case pattern of+ Var x -> variable x+ Literal lit -> pretty lit+ Record fs -> PP.braces (commaCat $ map variable fs)+ Alias x p -> prettyParens p <+> PP.text "as" <+> variable x+ Anything -> PP.text "_"+ Data name [hd,tl] | Var.toString name == "::" ->+ parensIf isCons (pretty hd) <+> PP.text "::" <+> pretty tl+ where+ isCons = case hd of+ Data ctor _ -> Var.toString ctor == "::"+ _ -> False++ Data name ps+ | Help.isTuple name' -> PP.parens . commaCat $ map pretty ps+ | otherwise -> hsep (PP.text name' : map prettyParens ps)+ where+ name' = Var.toString name++prettyParens :: Var.ToString var => Pattern var -> Doc+prettyParens pattern = + parensIf needsThem (pretty pattern)+ where+ needsThem =+ case pattern of+ Data name (_:_) | not (Help.isTuple (Var.toString name)) -> True+ Alias _ _ -> True+ _ -> False
+ src/AST/PrettyPrint.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE FlexibleInstances #-}+module AST.PrettyPrint where++import Control.Monad.Trans.Error+import Text.PrettyPrint as P+import qualified AST.Helpers as Help++class Pretty a where+ pretty :: a -> Doc++instance Pretty Doc where+ pretty doc = doc++instance Pretty String where+ pretty = P.text++renderPretty :: (Pretty a) => a -> String+renderPretty e = render (pretty e)++commaCat docs = cat (punctuate comma docs)+commaSep docs = sep (punctuate comma docs)++parensIf :: Bool -> Doc -> Doc+parensIf bool doc = if bool then parens doc else doc++variable :: String -> Doc+variable x =+ if Help.isOp x then parens (text x) else text x++eightyCharLines :: Int -> String -> String+eightyCharLines indent message = answer+ where+ (answer,_,_) = foldl step (spaces, indent-1, "") chunks++ chunks = map (\w -> (w, length w)) (words message)+ spaces = replicate indent ' '+ step (sentence, slen, space) (word, wlen)+ | slen + wlen > 79 = (sentence ++ "\n" ++ spaces ++ word, indent + wlen, " ")+ | otherwise = (sentence ++ space ++ word, slen + wlen + length space, " ")++instance ErrorList Doc where+ listMsg str = [ P.text str ]
+ src/AST/Type.hs view
@@ -0,0 +1,148 @@+module AST.Type where++import Control.Applicative ((<$>), (<*>))+import Data.Binary+import qualified Data.Map as Map++import qualified AST.Variable as Var+import AST.PrettyPrint+import qualified AST.Helpers as Help+import Text.PrettyPrint as P++data Type var+ = Lambda (Type var) (Type var)+ | Var String+ | Type var+ | App (Type var) [Type var]+ | Record [(String, Type var)] (Maybe (Type var))+ | Aliased Var.Canonical (Type var)+ deriving (Eq,Show)++type RawType = Type Var.Raw+type CanonicalType = Type Var.Canonical++fieldMap :: [(String,a)] -> Map.Map String [a]+fieldMap fields =+ foldl (\r (x,t) -> Map.insertWith (++) x [t] r) Map.empty fields++recordOf :: [(String, Type var)] -> Type var+recordOf fields = Record fields Nothing++listOf :: RawType -> RawType+listOf t = App (Type (Var.Raw "List")) [t]++tupleOf :: [RawType] -> RawType+tupleOf ts = App (Type t) ts+ where+ t = Var.Raw ("_Tuple" ++ show (length ts))++instance (Var.ToString var, Pretty var) => Pretty (Type var) where+ pretty tipe =+ case tipe of+ Lambda _ _ -> P.sep [ t, P.sep (map (P.text "->" <+>) ts) ]+ where+ t:ts = map prettyLambda (collectLambdas tipe)+ prettyLambda t = case t of+ Lambda _ _ -> P.parens (pretty t)+ _ -> pretty t++ Var x -> P.text x++ Type var ->+ let v = Var.toString var in+ P.text (if v == "_Tuple0" then "()" else v)++ App f args ->+ case (f,args) of+ (Type name, _)+ | Help.isTuple (Var.toString name) ->+ P.parens . P.sep . P.punctuate P.comma $ map pretty args++ _ -> P.hang (pretty f) 2 (P.sep $ map prettyParens args)++ Record _ _ ->+ case flattenRecord tipe of+ ([], Nothing) ->+ P.text "{}"++ (fields, Nothing) ->+ P.sep+ [ P.cat (zipWith (<+>) (P.lbrace : repeat P.comma) (map prettyField fields))+ , P.rbrace+ ]++ (fields, Just x) ->+ P.hang+ (P.lbrace <+> P.text x <+> P.text "|")+ 4+ (P.sep+ [ P.cat (zipWith (<+>) (P.space : repeat P.comma) (map prettyField fields))+ , P.rbrace+ ])+ where+ prettyField (field, tipe) =+ P.text field <+> P.text ":" <+> pretty tipe++ Aliased name t ->+ let t' = pretty t in+ if show t' `elem` ["Int", "Float", "String", "Char", "Bool"]+ then t'+ else pretty name+++collectLambdas :: Type var -> [Type var]+collectLambdas tipe =+ case tipe of+ Lambda arg body -> arg : collectLambdas body+ _ -> [tipe]++prettyParens :: (Var.ToString var, Pretty var) => Type var -> Doc+prettyParens tipe = parensIf (needed tipe) (pretty tipe)+ where+ needed t =+ case t of+ Aliased _ t' -> needed t'++ Lambda _ _ -> True++ App (Type name) _ | Help.isTuple (Var.toString name) -> False+ App t' [] -> needed t'+ App _ _ -> True++ _ -> False++flattenRecord :: Type var -> ( [(String, Type var)], Maybe String )+flattenRecord tipe =+ case tipe of+ Var x -> ([], Just x)++ Record fields Nothing -> (fields, Nothing)++ Record fields (Just ext) ->+ let (fields',ext') = flattenRecord ext+ in (fields' ++ fields, ext')++ Aliased _ tipe' -> flattenRecord tipe'++ _ -> error "Trying to flatten ill-formed record."++instance Binary var => Binary (Type var) where+ put tipe =+ case tipe of+ Lambda t1 t2 -> putWord8 0 >> put t1 >> put t2+ Var x -> putWord8 1 >> put x+ Type name -> putWord8 2 >> put name+ App t1 t2 -> putWord8 3 >> put t1 >> put t2+ Record fs ext -> putWord8 4 >> put fs >> put ext+ Aliased var t -> putWord8 5 >> put var >> put t++ get = do+ n <- getWord8+ case n of+ 0 -> Lambda <$> get <*> get+ 1 -> Var <$> get+ 2 -> Type <$> get+ 3 -> App <$> get <*> get+ 4 -> Record <$> get <*> get+ 5 -> Aliased <$> get <*> get+ _ -> error "Error reading a valid type from serialized string"
+ src/AST/Variable.hs view
@@ -0,0 +1,240 @@+module AST.Variable where++import Control.Applicative ((<$>), (<*>))+import Data.Binary+import qualified Data.List as List+import qualified Data.Maybe as Maybe+import Text.PrettyPrint as P+import qualified AST.Helpers as Help+import AST.PrettyPrint+++-- VARIABLES++newtype Raw = Raw String+ deriving (Eq,Ord,Show)+++data Home+ = BuiltIn+ | Module [String]+ | Local+ deriving (Eq,Ord,Show)+++data Canonical = Canonical+ { home :: !Home+ , name :: !String+ }+ deriving (Eq,Ord,Show)+++local :: String -> Canonical+local x = Canonical Local x+++builtin :: String -> Canonical+builtin x = Canonical BuiltIn x+++-- VARIABLE RECOGNIZERS++is :: [String] -> String -> Canonical -> Bool+is home name var =+ var == Canonical (Module home) name+++isJson :: Canonical -> Bool+isJson =+ is ["Json", "Encode"] "Value"+++isMaybe :: Canonical -> Bool+isMaybe =+ is ["Maybe"] "Maybe"++isArray :: Canonical -> Bool+isArray =+ is ["Array"] "Array"+++isSignal :: Canonical -> Bool+isSignal =+ is ["Signal"] "Signal"+++isList :: Canonical -> Bool+isList v =+ v == Canonical BuiltIn "List"+++isTuple :: Canonical -> Bool+isTuple v =+ case v of+ Canonical BuiltIn name -> Help.isTuple name+ _ -> False+++isPrimitive :: Canonical -> Bool+isPrimitive v =+ case v of+ Canonical BuiltIn name -> name `elem` ["Int","Float","String","Bool"]+ _ -> False+++isPrim :: String -> Canonical -> Bool+isPrim prim v =+ case v of+ Canonical BuiltIn name -> name == prim+ _ -> False+++isText :: Canonical -> Bool+isText =+ is ["Text"] "Text"+++-- VARIABLE TO STRING++class ToString a where+ toString :: a -> String+++instance ToString Raw where+ toString (Raw x) = x+++instance ToString Canonical where+ toString (Canonical home name) =+ case home of+ BuiltIn -> name+ Module path -> List.intercalate "." (path ++ [name])+ Local -> name+++-- LISTINGS++-- | A listing of values. Something like (a,b,c) or (..) or (a,b,..)+data Listing a = Listing+ { _explicits :: [a]+ , _open :: Bool+ } deriving (Eq,Ord,Show)+++openListing :: Listing a+openListing =+ Listing [] True+++-- | A value that can be imported or exported+data Value+ = Value !String+ | Alias !String+ | Union !String !(Listing String)+ deriving (Eq,Ord,Show)+++-- CATEGORIZING VALUES++getValues :: [Value] -> [String]+getValues values =+ Maybe.mapMaybe getValue values+++getValue :: Value -> Maybe String+getValue value =+ case value of+ Value name -> Just name+ Alias _ -> Nothing+ Union _ _ -> Nothing+++getAliases :: [Value] -> [String]+getAliases values =+ Maybe.mapMaybe getAlias values+++getAlias :: Value -> Maybe String+getAlias value =+ case value of+ Value _-> Nothing+ Alias name -> Just name+ Union _ _ -> Nothing+++getUnions :: [Value] -> [(String, Listing String)]+getUnions values =+ Maybe.mapMaybe getUnion values+++getUnion :: Value -> Maybe (String, Listing String)+getUnion value =+ case value of+ Value _ -> Nothing+ Alias _ -> Nothing+ Union name ctors -> Just (name, ctors)+++-- PRETTY VARIABLES++instance Pretty Raw where+ pretty (Raw var) = variable var+++instance Pretty Canonical where+ pretty var = P.text (toString var)+++instance Pretty a => Pretty (Listing a) where+ pretty (Listing explicits open) =+ P.parens (commaCat (map pretty explicits ++ dots))+ where+ dots = [if open then P.text ".." else P.empty]+++instance Pretty Value where+ pretty portable =+ case portable of+ Value name -> P.text name+ Alias name -> P.text name+ Union name ctors ->+ P.text name <> pretty (ctors { _explicits = map P.text (_explicits ctors) })+++-- BINARY SERIALIZATION++instance Binary Canonical where+ put (Canonical home name) =+ case home of+ BuiltIn -> putWord8 0 >> put name+ Module path -> putWord8 1 >> put path >> put name+ Local -> putWord8 2 >> put name++ get = do tag <- getWord8+ case tag of+ 0 -> Canonical BuiltIn <$> get+ 1 -> Canonical . Module <$> get <*> get+ 2 -> Canonical Local <$> get+ _ -> error "Unexpected tag when deserializing canonical variable"+++instance Binary Value where+ put portable =+ case portable of+ Value name -> putWord8 0 >> put name+ Alias name -> putWord8 1 >> put name+ Union name ctors -> putWord8 2 >> put name >> put ctors++ get = do tag <- getWord8+ case tag of+ 0 -> Value <$> get+ 1 -> Alias <$> get+ 2 -> Union <$> get <*> get+ _ -> error "Error reading valid import/export information from serialized string"+++instance (Binary a) => Binary (Listing a) where+ put (Listing explicits open) =+ put explicits >> put open++ get = Listing <$> get <*> get
+ src/Compile.hs view
@@ -0,0 +1,60 @@+module Compile (compile) where++import qualified Data.Map as Map+import Text.PrettyPrint (Doc)++import qualified AST.Module as Module+import qualified Parse.Helpers as Parse+import qualified Parse.Parse as Parse+import qualified Transform.AddDefaultImports as DefaultImports+import qualified Transform.Check as Check+import qualified Type.Inference as TI+import qualified Transform.Canonicalize as Canonical+import Elm.Utils ((|>))+++compile+ :: String+ -> String+ -> Module.Interfaces+ -> String+ -> Either [Doc] Module.CanonicalModule++compile user projectName interfaces source =+ do + -- Parse the source code+ parsedModule <-+ Parse.program (getOpTable interfaces) source++ -- determine if default imports should be added+ -- only elm-lang/core is exempt+ let needsDefaults =+ not (user == "elm-lang" && projectName == "core")++ -- add default imports if necessary+ let rawModule =+ DefaultImports.add needsDefaults parsedModule++ -- validate module (e.g. all variables exist)+ case Check.mistakes (Module.body rawModule) of+ [] -> return ()+ ms -> Left ms++ -- Canonicalize all variables, pinning down where they came from.+ canonicalModule <- Canonical.module' interfaces rawModule++ -- Run type inference on the program.+ types <- TI.infer interfaces canonicalModule++ -- Add the real list of tyes+ let body = (Module.body canonicalModule) { Module.types = types }++ return $ canonicalModule { Module.body = body }+++getOpTable :: Module.Interfaces -> Parse.OpTable+getOpTable interfaces =+ Map.elems interfaces+ |> concatMap Module.iFixities+ |> map (\(assoc,lvl,op) -> (op,(lvl,assoc)))+ |> Map.fromList
+ src/Docs.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}+module Main where++import System.Console.CmdArgs+import System.Directory+import System.FilePath+import System.Exit+import System.IO++import Control.Applicative ((<$>))+import Control.Arrow (second)+import qualified Data.Aeson.Encode.Pretty as Json+import qualified Data.ByteString.Lazy.Char8 as BS+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set++import qualified AST.Declaration as Decl+import qualified AST.Expression.Source as Source+import qualified AST.Variable as Var++import Text.Parsec hiding (newline, spaces)+import qualified Parse.Declaration as Parse (typeDecl, infixDecl)+import qualified Parse.Expression as Parse (typeAnnotation)+import qualified Parse.Helpers as Parse+import qualified Parse.Module as Parse (header)+import qualified Elm.Compiler.Module as Module+import qualified Elm.Compiler.Type.Extract as Type+import qualified Elm.Docs as Docs+++-- FLAGS++data Flags = Flags+ { input :: FilePath+ , output :: Maybe FilePath+ }+ deriving (Data,Typeable,Show,Eq)+++defaultFlags :: Flags+defaultFlags = Flags+ { input = def &= args &= typ "FILE"++ , output = Nothing &= typFile+ &= help "file name for generated JSON documentation"++ } &= help "Generate documentation for Elm"+ &= summary "Generate documentation for Elm, (c) Evan Czaplicki"+++-- GENERATE DOCUMENTATION++main :: IO ()+main =+ do flags <- cmdArgs defaultFlags+ source <- readFile (input flags)+ case Parse.iParse documentation source of+ Right docs ->+ let json = Json.encodePretty' config docs in+ case output flags of+ Nothing -> BS.putStrLn json+ Just docPath ->+ do createDirectoryIfMissing True (dropFileName docPath)+ BS.writeFile docPath json++ Left err ->+ do hPutStrLn stderr (show err)+ exitFailure+++config :: Json.Config+config =+ Json.Config+ { Json.confIndent = 2+ , Json.confCompare = Json.keyOrder keys+ }+ where+ keys =+ [ "tag", "name", "comment", "aliases", "types"+ , "values", "func", "args", "type", "cases"+ ]+++-- PARSE DOCUMENTATION++documentation :: Parse.IParser Docs.Documentation+documentation =+ do optional Parse.freshLine+ (names, exports) <- Parse.header++ manyTill (string " " <|> Parse.newline <?> "more whitespace")+ (lookAhead (string "{-|") <?> "module documentation comment")++ overview <- docComment++ decls <- allDeclarations++ let (aliases, unions, values) = categorizeDeclarations decls++ return $+ Docs.Documentation+ (Module.Name names)+ overview+ (filterAliases exports aliases)+ (filterUnions exports unions)+ (filterValues exports values)+++docComment :: Parse.IParser String+docComment =+ do try (string "{-|")+ contents <- Parse.closeComment++ let reversed =+ dropWhile (`elem` " \n\r") . drop 2 $ reverse contents++ return $ dropWhile (==' ') (reverse reversed)+++allDeclarations :: Parse.IParser [(String, Decl.SourceDecl)]+allDeclarations =+ Parse.onFreshLines (:) [] declaration+++declaration :: Parse.IParser (String, Decl.SourceDecl)+declaration =+ uncommentable <|> commented <|> uncommented ""+ where+ uncommentable =+ (,) "" <$> Parse.infixDecl++ commented =+ do comment <- docComment+ Parse.freshLine+ uncommented comment++ uncommented comment =+ (,) comment+ <$> choice [ Parse.typeDecl, Decl.Definition <$> Parse.typeAnnotation ]+++-- CATEGORIZE DECLARATIONS++data CategoryInfo = CategoryInfo+ { aliases :: [Docs.Alias]+ , unions :: [Docs.Union]+ , values :: Map.Map String Docs.Value+ , infixes :: Map.Map String (String, Int)+ }+++emptyInfo :: CategoryInfo+emptyInfo =+ CategoryInfo [] [] Map.empty Map.empty+++categorizeDeclarations :: [(String, Decl.SourceDecl)] -> ([Docs.Alias], [Docs.Union], [Docs.Value])+categorizeDeclarations decls =+ (aliases, unions, Map.elems values)+ where+ (CategoryInfo aliases unions rawValues infixes) =+ foldr collectInfo emptyInfo decls++ values =+ Map.union+ (Map.intersectionWith addInfixInfo rawValues infixes)+ rawValues+++addInfixInfo :: Docs.Value -> (String, Int) -> Docs.Value+addInfixInfo value infixInfo =+ value { Docs.valueAssocPrec = Just infixInfo }+++collectInfo :: (String, Decl.SourceDecl) -> CategoryInfo -> CategoryInfo+collectInfo (comment, decl) info =+ case decl of+ Decl.Definition def ->+ case def of+ Source.Definition _ _ -> error errorMessage+ Source.TypeAnnotation name tipe ->+ let value = Docs.Value name comment (Type.fromInternalType tipe) Nothing+ in+ info { values = Map.insert name value (values info) }++ Decl.Datatype name args cases ->+ let cases' = map (second (map Type.fromInternalType)) cases+ union = Docs.Union name comment args cases'+ in+ info { unions = union : unions info }++ Decl.TypeAlias name args tipe ->+ let alias = Docs.Alias name comment args (Type.fromInternalType tipe)+ in+ info { aliases = alias : aliases info }++ Decl.Fixity assoc prec name ->+ let infixInfo = (Decl.assocToString assoc, prec)+ in+ info { infixes = Map.insert name infixInfo (infixes info) }++ Decl.Port _ ->+ error errorMessage+++errorMessage :: String+errorMessage =+ "there appears to be a bug in this tool.\n" +++ "Please report it to <https://github.com/elm-lang/Elm/issues>"+++-- FILTER OUT UNEXPORTED VALUES, ALIASES, and UNIONS++filterValues :: Var.Listing Var.Value -> [Docs.Value] -> [Docs.Value]+filterValues (Var.Listing exports open) values =+ case open of+ True -> values+ False ->+ let names =+ Set.fromList (Var.getValues exports)+ in+ filter (\value -> Set.member (Docs.valueName value) names) values+++filterAliases :: Var.Listing Var.Value -> [Docs.Alias] -> [Docs.Alias]+filterAliases (Var.Listing exports open) aliases =+ case open of+ True -> aliases+ False ->+ let names =+ Set.fromList (Var.getAliases exports)+ in+ filter (\value -> Set.member (Docs.aliasName value) names) aliases+++filterUnions :: Var.Listing Var.Value -> [Docs.Union] -> [Docs.Union]+filterUnions (Var.Listing exports open) unions =+ case open of+ True -> unions+ False ->+ Maybe.mapMaybe (filterUnion exportedUnions) unions+ where+ exportedUnions =+ Map.fromList (Var.getUnions exports)+++filterUnion :: Map.Map String (Var.Listing String) -> Docs.Union -> Maybe Docs.Union+filterUnion exportedUnions union =+ case Map.lookup (Docs.unionName union) exportedUnions of+ Nothing ->+ Nothing++ Just (Var.Listing tags unionOpen)+ | unionOpen ->+ Just union+ | otherwise ->+ let publicTags =+ filter (\(tag, _) -> tag `elem` tags) (Docs.unionCases union)+ in+ Just (union { Docs.unionCases = publicTags })+
+ src/Elm/Compiler.hs view
@@ -0,0 +1,81 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE FlexibleContexts #-}+module Elm.Compiler+ ( version+ , parseDependencies, compile+ , runtimeDebugPath+ ) where++import Control.Monad.Error (MonadError, throwError)+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Text.PrettyPrint as P++import qualified AST.Module as Module (HeaderAndImports(HeaderAndImports), toInterface)+import qualified Compile+import qualified Elm.Compiler.Module as PublicModule+import qualified Elm.Compiler.Version as Version+import Elm.Utils ((|>))+import qualified Elm.Utils as Utils+import qualified Generate.JavaScript as JS+import qualified Parse.Helpers as Help+import qualified Parse.Module as Parse+import qualified Paths_elm_compiler as Paths+++-- VERSION++version :: String+version =+ Version.version+++-- DEPENDENCIES++parseDependencies+ :: (MonadError String m)+ => String+ -> m (PublicModule.Name, [PublicModule.Name])+parseDependencies src =+ case Help.iParse Parse.headerAndImports src of+ Left msg ->+ throwError (show msg)++ Right (Module.HeaderAndImports names _exports imports) ->+ return+ ( PublicModule.Name names+ , map (PublicModule.Name . fst) imports+ )+++-- COMPILATION++{-| Compiles Elm source code to JavaScript. -}+compile+ :: String+ -> String+ -> String+ -> Map.Map PublicModule.Name PublicModule.Interface+ -> Either String (PublicModule.Interface, String)+compile user packageName source interfaces =+ let unwrappedInterfaces =+ Map.mapKeysMonotonic (\(PublicModule.Name name) -> name) interfaces+ in+ case Compile.compile user packageName unwrappedInterfaces source of+ Right modul ->+ Right (Module.toInterface modul, JS.generate modul)++ Left docs ->+ map P.render docs+ |> List.intersperse ""+ |> unlines+ |> Left+++-- DATA FILES++{-| Path to the debugger runtime.+-}+runtimeDebugPath :: IO FilePath+runtimeDebugPath =+ Utils.getAsset "compiler" Paths.getDataFileName "runtime/debug.js"
+ src/Elm/Compiler/Module.hs view
@@ -0,0 +1,112 @@+module Elm.Compiler.Module+ ( Interface, Name(Name)+ , nameToPath+ , nameToString, nameFromString+ , hyphenate, dehyphenate+ , defaultImports+ , interfacePorts+ , interfaceTypes+ )+ where++import Control.Monad (mzero)+import qualified Data.Aeson as Json+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Text as Text+import System.FilePath ((</>))++import qualified AST.Module as Module+import qualified Transform.AddDefaultImports as Defaults+import qualified Elm.Compiler.Type as Type+import qualified Elm.Compiler.Type.Extract as Extract+++-- EXPOSED TYPES++type Interface = Module.Interface+++newtype Name = Name [String]+ deriving (Eq, Ord)+++defaultImports :: [Name]+defaultImports =+ map Name (Map.keys Defaults.defaultImports)+++-- POKING AROUND INTERFACES++interfacePorts :: Interface -> [String]+interfacePorts interface =+ Module.iPorts interface+++interfaceTypes :: Interface -> Map.Map String Type.Type+interfaceTypes interface =+ Map.map Extract.fromInternalType (Module.iTypes interface)+++-- STRING CONVERSIONS for NAMES++nameToPath :: Name -> FilePath+nameToPath (Name names) =+ List.foldl1 (</>) names+++nameToString :: Name -> String+nameToString (Name names) =+ List.intercalate "." names+++nameFromString :: String -> Maybe Name+nameFromString =+ fromString '.'+++hyphenate :: Name -> String+hyphenate (Name names) =+ List.intercalate "-" names+++dehyphenate :: String -> Maybe Name+dehyphenate =+ fromString '-'+++fromString :: Char -> String -> Maybe Name+fromString sep raw =+ Name `fmap` mapM isLegit names+ where+ names =+ filter (/= [sep]) (List.groupBy (\a b -> a /= sep && b /= sep) raw)++ isLegit name =+ case name of+ [] -> Nothing+ char:rest ->+ if Char.isUpper char && all legitChar rest+ then Just name+ else Nothing++ legitChar char =+ Char.isAlphaNum char || char `elem` "_'"+++-- JSON for NAME++instance Json.ToJSON Name where+ toJSON name =+ Json.toJSON (nameToString name)+++instance Json.FromJSON Name where+ parseJSON (Json.String text) =+ let rawName = Text.unpack text in+ case nameFromString rawName of+ Nothing -> fail (rawName ++ " is not a valid module name")+ Just name -> return name++ parseJSON _ = mzero
+ src/Elm/Compiler/Type.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE OverloadedStrings #-}+module Elm.Compiler.Type+ ( Type(..)+ , toString+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Arrow (second)+import Data.Aeson ((.:), (.=))+import qualified Data.Aeson as Json+import qualified Data.ByteString.Lazy.Char8 as BS+import qualified Data.Text as Text+import Text.PrettyPrint as P++import qualified AST.Helpers as Help+++data Type+ = Lambda Type Type+ | Var String+ | Type String+ | App Type [Type]+ | Record [(String, Type)] (Maybe Type)+++-- TO STRING++data Context = None | ADT | Function+++toString :: Type -> String+toString tipe =+ P.render (toDoc None tipe)+++toDoc :: Context -> Type -> P.Doc+toDoc context tipe =+ case tipe of+ Lambda _ _ ->+ let t:ts =+ map (toDoc Function) (collectLambdas tipe)++ lambda =+ P.sep [ t, P.sep (map (P.text "->" <+>) ts) ]+ in+ case context of+ None -> lambda+ _ -> P.parens lambda++ Var name ->+ P.text name++ Type name ->+ P.text (if name == "_Tuple0" then "()" else name)++ App (Type name) args+ | Help.isTuple name ->+ P.sep+ [ P.cat (zipWith (<+>) (P.lparen : repeat P.comma) (map (toDoc None) args))+ , P.rparen+ ]++ | otherwise ->+ let adt = P.hang (P.text name) 2 (P.sep $ map (toDoc ADT) args)+ in+ case (context, args) of+ (ADT, _ : _) -> P.parens adt+ _ -> adt++ Record _ _ ->+ case flattenRecord tipe of+ ([], Nothing) ->+ P.text "{}"++ (fields, Nothing) ->+ P.sep+ [ P.cat (zipWith (<+>) (P.lbrace : repeat P.comma) (map prettyField fields))+ , P.rbrace+ ]++ (fields, Just x) ->+ P.hang+ (P.lbrace <+> P.text x <+> P.text "|")+ 4+ (P.sep+ [ P.cat (zipWith (<+>) (P.space : repeat P.comma) (map prettyField fields))+ , P.rbrace+ ])+ where+ prettyField (field, tipe) =+ P.text field <+> P.text ":" <+> toDoc None tipe+++collectLambdas :: Type -> [Type]+collectLambdas tipe =+ case tipe of+ Lambda arg body -> arg : collectLambdas body+ _ -> [tipe]+++flattenRecord :: Type -> ( [(String, Type)], Maybe String )+flattenRecord tipe =+ case tipe of+ Var x -> ([], Just x)++ Record fields Nothing -> (fields, Nothing)++ Record fields (Just ext) ->+ let (fields',ext') = flattenRecord ext+ in+ (fields' ++ fields, ext')++ _ -> error "Trying to flatten ill-formed record."+++-- JSON for TYPE++instance Json.ToJSON Type where+ toJSON tipe =+ Json.object (getFields tipe)+ where+ getFields tipe =+ case tipe of+ Lambda t1 t2 ->+ [ "tag" .= ("lambda" :: Text.Text)+ , "in" .= Json.toJSON t1+ , "out" .= Json.toJSON t2+ ]++ Var x ->+ [ "tag" .= ("var" :: Text.Text)+ , "name" .= Json.toJSON x+ ]++ Type name ->+ [ "tag" .= ("type" :: Text.Text)+ , "name" .= Json.toJSON name+ ]++ App t ts -> + [ "tag" .= ("app" :: Text.Text)+ , "func" .= Json.toJSON t+ , "args" .= Json.toJSON ts+ ]++ Record fields ext ->+ [ "tag" .= ("record" :: Text.Text)+ , "fields" .= Json.toJSON (map (Json.toJSON . second Json.toJSON) fields)+ , "extension" .= Json.toJSON ext+ ]+++instance Json.FromJSON Type where+ parseJSON (Json.Object obj) =+ do tag <- obj .: "tag"+ case (tag :: String) of+ "lambda" ->+ Lambda <$> obj .: "in" <*> obj .: "out"++ "var" ->+ Var <$> obj .: "name"++ "type" ->+ Type <$> obj .: "name"++ "app" ->+ App <$> obj .: "func" <*> obj .: "args"++ "record" ->+ Record <$> obj .: "fields" <*> obj .: "extension"++ _ ->+ fail $ "Error when decoding type with tag: " ++ tag+++ parseJSON value =+ fail $ "Cannot decode Value from: " ++ BS.unpack (Json.encode value)
+ src/Elm/Compiler/Type/Extract.hs view
@@ -0,0 +1,30 @@+module Elm.Compiler.Type.Extract where++import Control.Arrow (second)+import qualified AST.Type as Type+import qualified AST.Variable as Var+import qualified Elm.Compiler.Type as T+++-- INTERNAL TYPE to PUBLIC TYPE++fromInternalType :: Var.ToString a => Type.Type a -> T.Type+fromInternalType astType =+ case astType of+ Type.Lambda t1 t2 ->+ T.Lambda (fromInternalType t1) (fromInternalType t2)++ Type.Var x ->+ T.Var x++ Type.Type var ->+ T.Type (Var.toString var)++ Type.App t ts ->+ T.App (fromInternalType t) (map fromInternalType ts)++ Type.Record fields ext ->+ T.Record (map (second fromInternalType) fields) (fmap fromInternalType ext)++ Type.Aliased _ t ->+ fromInternalType t
+ src/Elm/Compiler/Version.hs view
@@ -0,0 +1,10 @@+{-# OPTIONS_GHC -Wall #-}+module Elm.Compiler.Version (version) where++import qualified Data.Version as Version+import qualified Paths_elm_compiler as This+++version :: String+version =+ Version.showVersion This.version
+ src/Elm/Docs.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE OverloadedStrings #-}+module Elm.Docs where++import Control.Applicative ((<$>),(<*>))+import Control.Monad+import Data.Aeson ((.:), (.:?), (.=))+import qualified Data.Aeson as Json+import qualified Data.ByteString.Lazy.Char8 as BS++import qualified Elm.Compiler.Module as Module+import qualified Elm.Compiler.Type as Type+++data Documentation = Documentation+ { moduleName :: Module.Name+ , comment :: String+ , aliases :: [Alias]+ , types :: [Union]+ , values :: [Value]+ }+++data Alias = Alias+ { aliasName :: String+ , aliasComment :: String+ , aliasArgs :: [String]+ , aliasType :: Type.Type+ }+++data Union = Union+ { unionName :: String+ , unionComment :: String+ , unionArgs :: [String]+ , unionCases :: [(String, [Type.Type])]+ }+++data Value = Value+ { valueName :: String+ , valueComment :: String+ , valueType :: Type.Type+ , valueAssocPrec :: Maybe (String,Int)+ }+++-- JSON for DOCUMENTATION++instance Json.ToJSON Documentation where+ toJSON (Documentation name comment aliases types values) =+ Json.object+ [ "name" .= name+ , "comment" .= comment+ , "aliases" .= aliases+ , "types" .= types+ , "values" .= values+ ]++instance Json.FromJSON Documentation where+ parseJSON (Json.Object obj) =+ Documentation+ <$> obj .: "name"+ <*> obj .: "comment"+ <*> obj .: "aliases"+ <*> obj .: "types"+ <*> obj .: "values"++ parseJSON value =+ fail $ "Cannot decode Documentation from: " ++ BS.unpack (Json.encode value)+++-- JSON for ALIAS++instance Json.ToJSON Alias where+ toJSON (Alias name comment args tipe) =+ Json.object+ [ "name" .= name+ , "comment" .= comment+ , "args" .= args+ , "type" .= tipe+ ]++instance Json.FromJSON Alias where+ parseJSON (Json.Object obj) =+ Alias+ <$> obj .: "name"+ <*> obj .: "comment"+ <*> obj .: "args"+ <*> obj .: "type"++ parseJSON value =+ fail $ "Cannot decode Alias from: " ++ BS.unpack (Json.encode value)+++-- JSON for UNION++instance Json.ToJSON Union where+ toJSON (Union name comment args cases) =+ Json.object+ [ "name" .= name+ , "comment" .= comment+ , "args" .= args+ , "cases" .= cases+ ]++instance Json.FromJSON Union where+ parseJSON (Json.Object obj) =+ Union+ <$> obj .: "name"+ <*> obj .: "comment"+ <*> obj .: "args"+ <*> obj .: "cases"++ parseJSON value =+ fail $ "Cannot decode Union from: " ++ BS.unpack (Json.encode value)+++-- JSON for VALUE++instance Json.ToJSON Value where+ toJSON (Value name comment tipe assocPrec) =+ Json.object (fields ++ possibleFields)+ where+ fields =+ [ "name" .= name+ , "comment" .= comment+ , "type" .= tipe+ ]++ possibleFields =+ case assocPrec of+ Nothing -> []+ Just (assoc, prec) ->+ [ "associativity" .= assoc+ , "precedence" .= prec+ ]+++instance Json.FromJSON Value where+ parseJSON (Json.Object obj) =+ Value+ <$> obj .: "name"+ <*> obj .: "comment"+ <*> obj .: "type"+ <*> (liftM2 (,) <$> obj .:? "associativity" <*> obj .:? "precedence")++ parseJSON value =+ fail $ "Cannot decode Value from: " ++ BS.unpack (Json.encode value)+
+ src/Elm/Utils.hs view
@@ -0,0 +1,121 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE FlexibleContexts #-}+module Elm.Utils ((|>), (<|), getAsset, run, unwrappedRun, CommandError(..)) where++import Control.Monad.Error (MonadError, MonadIO, liftIO, throwError)+import System.Directory (doesFileExist)+import System.Environment (getEnv)+import System.Exit (ExitCode(ExitSuccess, ExitFailure))+import System.FilePath ((</>))+import System.IO.Error (tryIOError)+import System.Process (readProcessWithExitCode)++import qualified Elm.Compiler.Version as Version+++{-| Forward function application `x |> f == f x`. This function is useful+for avoiding parenthesis and writing code in a more natural way.+-}+(|>) :: a -> (a -> b) -> b+x |> f = f x+++{-| Backward function application `f <| x == f x`. This function is useful for+avoiding parenthesis.+-}+(<|) :: (a -> b) -> a -> b+f <| x = f x+++infixr 0 <|+infixl 0 |>+++-- RUN EXECUTABLES++data CommandError+ = MissingExe String+ | CommandFailed String String+++{-| Run a command, throw an error if the command is not found or if something+goes wrong.+-}+run :: (MonadError String m, MonadIO m) => String -> [String] -> m String+run command args =+ do result <- liftIO (unwrappedRun command args)+ case result of+ Right out -> return out+ Left err ->+ throwError (context (message err))+ where+ context msg =+ "failure when running:" ++ concatMap (' ':) (command:args) ++ "\n" ++ msg+ + message err =+ case err of+ CommandFailed stderr stdout ->+ stdout ++ stderr+ MissingExe msg -> + msg+++unwrappedRun :: String -> [String] -> IO (Either CommandError String)+unwrappedRun command args =+ do (exitCode, stdout, stderr) <- readProcessWithExitCode command args ""+ return $+ case exitCode of+ ExitSuccess -> Right stdout+ ExitFailure code+ | code == 127 -> Left (missingExe command) -- UNIX+ | code == 9009 -> Left (missingExe command) -- Windows+ | otherwise -> Left (CommandFailed stdout stderr)+++missingExe :: String -> CommandError+missingExe command =+ MissingExe $+ "Could not find command '" ++ command ++ "'. Do you have it installed?\n\+ \ Can it be run from anywhere? Is it on your PATH?"++++-- GET STATIC ASSETS++{-| Get the absolute path to a data file. If you install with cabal it will look+-}+getAsset :: String -> (FilePath -> IO FilePath) -> FilePath -> IO FilePath+getAsset project getDataFileName name =+ do path <- getDataFileName name+ exists <- doesFileExist path+ if exists+ then return path+ else do+ environment <- tryIOError (getEnv "ELM_HOME")+ case environment of+ Right env ->+ return (env </> project </> name)++ Left _ ->+ fail (errorNotFound name)+++errorNotFound :: FilePath -> String+errorNotFound name =+ unlines+ [ "Unable to find the ELM_HOME environment variable when searching"+ , "for the " ++ name ++ " file."+ , ""+ , "If you installed Elm Platform with the Mac or Windows installer, it looks like"+ , "ELM_HOME was not set automatically. Look up how to set environment variables"+ , "on your platform and set ELM_HOME to the directory that contains Elm's static"+ , "files:"+ , ""+ , " * On Mac it is /usr/local/share/elm"+ , " * On Windows it is one of the following:"+ , " C:/Program Files/Elm Platform/" ++ Version.version ++ "/share"+ , " C:/Program Files (x86)/Elm Platform/" ++ Version.version ++ "/share"+ , ""+ , "If it seems like a more complex issue, please report it here:"+ , " <https://github.com/elm-lang/elm-platform/issues>"+ ]
+ src/Generate/Cases.hs view
@@ -0,0 +1,210 @@+module Generate.Cases (toMatch, Match (..), Clause (..), matchSubst, newVar) where++import Control.Applicative ((<$>))+import Control.Arrow (first)+import Control.Monad.State (State)+import qualified Control.Monad.State as State+import Data.List (groupBy,sortBy)+import Data.Maybe (fromMaybe)++import qualified AST.Annotation as A+import qualified AST.Expression.General as Expr+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Literal as Literal+import qualified AST.Pattern as P+import qualified AST.Variable as Var+import Transform.Substitute (subst)++toMatch :: [(P.CanonicalPattern, Canonical.Expr)] -> State Int (String, Match)+toMatch patterns = do+ v <- newVar+ (,) v <$> match [v] (map (first (:[])) patterns) Fail+++newVar :: State Int String+newVar =+ do n <- State.get+ State.modify (+1)+ return $ "_v" ++ show n+++data Match+ = Match String [Clause] Match+ | Break+ | Fail+ | Other Canonical.Expr+ | Seq [Match]+ deriving Show+++data Clause =+ Clause (Either Var.Canonical Literal.Literal) [String] Match+ deriving Show+++type Case =+ ([P.CanonicalPattern], Canonical.Expr)+++matchSubst :: [(String,String)] -> Match -> Match+matchSubst pairs match =+ case match of+ Break -> Break++ Fail -> Fail+ + Seq ms ->+ Seq (map (matchSubst pairs) ms)+ + Other (A.A a e) ->+ Other . A.A a $ foldr ($) e $ map (\(x,y) -> subst x (Expr.localVar y)) pairs+ + Match n cs m ->+ Match (varSubst n) (map clauseSubst cs) (matchSubst pairs m)+ where+ varSubst v = fromMaybe v (lookup v pairs)+ clauseSubst (Clause c vs m) =+ Clause c (map varSubst vs) (matchSubst pairs m)+++isCon :: ([P.Pattern var], expr) -> Bool+isCon ([] , _) = noMatch "isCon"+isCon (p:_, _) =+ case p of+ P.Data _ _ -> True+ P.Literal _ -> True+ _ -> False+++isVar :: ([P.Pattern var], expr) -> Bool+isVar p = not (isCon p)+++match :: [String] -> [Case] -> Match -> State Int Match+match variables cases match =+ case (variables, cases, match) of+ ([], [], _) ->+ return match++ ([], [([], expr)], Fail) ->+ return $ Other expr++ ([], [([], expr)], Break) ->+ return $ Other expr++ ([], _, _) ->+ return $ Seq (map (Other . snd) cases ++ [match])++ (var:_, _, _)+ | all isVar cases' -> matchVar variables cases' match+ | all isCon cases' -> matchCon variables cases' match+ | otherwise -> matchMix variables cases' match+ where+ cases' = map (dealias var) cases+++dealias :: String -> Case -> Case+dealias _ ([], _) = noMatch "dealias"+dealias v c@(p:ps, A.A a e) =+ case p of+ P.Alias x pattern -> (pattern:ps, A.A a $ subst x (Expr.localVar v) e)+ _ -> c+++matchVar :: [String] -> [Case] -> Match -> State Int Match+matchVar [] _ _ = noMatch "matchVar"+matchVar (v:vs) cs def =+ match vs (map subVar cs) def+ where+ subVar ([], _) = noMatch "matchVar.subVar"+ subVar (p:ps, (A.A a expr)) =+ (ps, A.A a $ subOnePattern p expr)+ where+ subOnePattern pattern expr =+ case pattern of+ P.Anything -> expr++ P.Var x ->+ subst x (Expr.localVar v) expr++ P.Record fs ->+ foldr (\x -> subst x (Expr.Access (A.A a (Expr.localVar v)) x)) expr fs++ _ -> noMatch "matchVar.subVar"+++matchCon :: [String] -> [Case] -> Match -> State Int Match+matchCon variables cases match =+ case variables of+ [] -> noMatch "matchCon"++ var : _vars ->+ flip (Match var) match <$> mapM toClause caseGroups+ where+ caseGroups :: [[Case]]+ caseGroups =+ groupBy eq (sortBy cmp cases)++ eq :: Case -> Case -> Bool+ eq (patterns1, _) (patterns2, _) =+ case (patterns1, patterns2) of+ (pattern1:_, pattern2:_) ->+ case (pattern1, pattern2) of+ (P.Data name1 _, P.Data name2 _) -> name1 == name2+ (_, _) -> pattern1 == pattern2+ (_, _) -> noMatch "matchCon.eq"++ cmp :: Case -> Case -> Ordering+ cmp (patterns1, _) (patterns2, _) =+ case (patterns1, patterns2) of+ (pattern1:_, pattern2:_) ->+ case (pattern1, pattern2) of+ (P.Data name1 _, P.Data name2 _) -> compare name1 name2+ (_, _) -> compare pattern1 pattern2+ (_, _) -> noMatch "matchCon.cmp"++ toClause :: [Case] -> State Int Clause+ toClause cases =+ case head cases of+ (P.Data name _ : _, _) ->+ matchClause (Left name) variables cases Break++ (P.Literal lit : _, _) ->+ matchClause (Right lit) variables cases Break++ _ -> noMatch "matchCon"+++matchClause :: Either Var.Canonical Literal.Literal -> [String] -> [Case] -> Match+ -> State Int Clause+matchClause _ [] _ _ = noMatch "matchClause"+matchClause c (_:vs) cs mtch =+ do vs' <- getVars+ Clause c vs' <$> match (vs' ++ vs) (map flatten cs) mtch+ where+ flatten ([], _) = noMatch "matchClause.flatten"+ flatten (p:ps, e) =+ case p of+ P.Data _ ps' -> (ps' ++ ps, e)+ P.Literal _ -> (ps, e)+ _ -> noMatch "matchClause.flatten"++ getVars :: State Int [String]+ getVars =+ case head cs of+ (P.Data _ ps : _, _) -> State.forM ps (const newVar)+ (P.Literal _ : _, _) -> return []+ _ -> noMatch "matchClause.getVars"+++matchMix :: [String] -> [Case] -> Match -> State Int Match+matchMix vs cs def =+ State.foldM (flip $ match vs) def (reverse css)+ where+ css = groupBy (\p1 p2 -> isCon p1 == isCon p2) cs+++noMatch :: String -> a+noMatch name =+ error $ "unexpected pattern in '" ++ name +++ "' function. Report a compiler issue to <https://github.com/elm-lang/Elm>."
+ src/Generate/JavaScript.hs view
@@ -0,0 +1,454 @@+module Generate.JavaScript (generate) where++import Control.Applicative ((<$>),(<*>))+import Control.Arrow (first,(***))+import Control.Monad.State+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Set as Set+import Language.ECMAScript3.PrettyPrint+import Language.ECMAScript3.Syntax++import Generate.JavaScript.Helpers as Help+import qualified Generate.Cases as Case+import qualified Generate.JavaScript.Ports as Port+import qualified Generate.JavaScript.Variable as Var++import AST.Annotation+import AST.Module+import AST.Expression.General+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Module as Module+import qualified AST.Helpers as Help+import AST.Literal+import qualified AST.Pattern as P+import qualified AST.Variable as Var+++internalImports :: Module.Name -> [VarDecl ()]+internalImports name =+ [ varDecl "_N" (obj ["Elm","Native"])+ , include "_U" "Utils"+ , include "_L" "List"+ , include "_P" "Ports"+ , varDecl Help.localModuleName (string (Module.nameToString name))+ ]+ where+ include :: String -> String -> VarDecl ()+ include alias modul =+ varDecl alias (Help.make ["_N", modul])+++_Utils :: String -> Expression ()+_Utils x =+ obj ["_U", x]+++_List :: String -> Expression ()+_List x =+ obj ["_L", x]+++literal :: Literal -> Expression ()+literal lit =+ case lit of+ Chr c -> _Utils "chr" <| string [c]+ Str s -> string s+ IntNum n -> IntLit () n+ FloatNum n -> NumLit () n+ Boolean b -> BoolLit () b+++expression :: Canonical.Expr -> State Int (Expression ())+expression (A region expr) =+ case expr of+ Var var -> return $ Var.canonical var++ Literal lit -> return $ literal lit++ Range lo hi ->+ do lo' <- expression lo+ hi' <- expression hi+ return $ _List "range" `call` [lo',hi']++ Access e x ->+ do e' <- expression e+ return $ DotRef () e' (var x)++ Remove e x ->+ do e' <- expression e+ return $ _Utils "remove" `call` [string x, e']++ Insert e x v ->+ do v' <- expression v+ e' <- expression e+ return $ _Utils "insert" `call` [string x, v', e']++ Modify e fs ->+ do e' <- expression e+ fs' <- forM fs $ \(f,v) -> do+ v' <- expression v+ return $ ArrayLit () [string f, v']+ return $ _Utils "replace" `call` [ArrayLit () fs', e']++ Record fields ->+ do fields' <- forM fields $ \(f,e) -> do+ (,) f <$> expression e+ let fieldMap = List.foldl' combine Map.empty fields'+ return $ ObjectLit () $ (prop "_", hidden fieldMap) : visible fieldMap+ where+ combine r (k,v) = Map.insertWith (++) k [v] r+ hidden fs = ObjectLit () . map (prop *** ArrayLit ()) .+ Map.toList . Map.filter (not . null) $ Map.map tail fs+ visible fs = map (first prop) . Map.toList $ Map.map head fs++ Binop op e1 e2 -> binop region op e1 e2++ Lambda p e@(A ann _) ->+ do (args, body) <- foldM depattern ([], innerBody) (reverse patterns)+ body' <- expression body+ return $ case length args < 2 || length args > 9 of+ True -> foldr (==>) body' (map (:[]) args)+ False -> ref ("F" ++ show (length args)) <| (args ==> body')+ where+ depattern (args, body) pattern =+ case pattern of+ P.Var x -> return (args ++ [ Var.varName x ], body)+ _ -> do arg <- Case.newVar+ return ( args ++ [arg]+ , A ann (Case (A ann (localVar arg)) [(pattern, body)]))++ (patterns, innerBody) = collect [p] e++ collect patterns lexpr@(A _ expr) =+ case expr of+ Lambda p e -> collect (p:patterns) e+ _ -> (patterns, lexpr)++ App e1 e2 ->+ do func' <- expression func+ args' <- mapM expression args+ return $ case args' of+ [arg] -> func' <| arg+ _ | length args' <= 9 -> ref aN `call` (func':args')+ | otherwise -> foldl1 (<|) (func':args')+ where+ aN = "A" ++ show (length args)+ (func, args) = getArgs e1 [e2]+ getArgs func args =+ case func of+ (A _ (App f arg)) -> getArgs f (arg : args)+ _ -> (func, args)++ Let defs e ->+ do let (defs',e') = flattenLets defs e + stmts <- concat <$> mapM definition defs'+ exp <- expression e'+ return $ function [] (stmts ++ [ ret exp ]) `call` []++ MultiIf branches ->+ do branches' <- forM branches $ \(b,e) -> (,) <$> expression b <*> expression e+ return $ case last branches of+ (A _ (Var (Var.Canonical (Var.Module ["Basics"]) "otherwise")), _) ->+ safeIfs branches'+ (A _ (Literal (Boolean True)), _) -> safeIfs branches'+ _ -> ifs branches' (throw "badIf" region)+ where+ safeIfs branches = ifs (init branches) (snd (last branches))+ ifs branches finally = foldr iff finally branches+ iff (if', then') else' = CondExpr () if' then' else'++ Case e cases ->+ do (tempVar,initialMatch) <- Case.toMatch cases+ (revisedMatch, stmt) <-+ case e of+ A _ (Var (Var.Canonical Var.Local x)) ->+ return (Case.matchSubst [(tempVar, Var.varName x)] initialMatch, [])+ _ ->+ do e' <- expression e+ return (initialMatch, [VarDeclStmt () [varDecl tempVar e']])+ match' <- match region revisedMatch+ return (function [] (stmt ++ match') `call` [])++ ExplicitList es ->+ do es' <- mapM expression es+ return $ _List "fromArray" <| ArrayLit () es'++ Data name es ->+ do es' <- mapM expression es+ return $ ObjectLit () (ctor : fields es')+ where+ ctor = (prop "ctor", string name)+ fields = zipWith (\n e -> (prop ("_" ++ show n), e)) [0..]++ GLShader _uid src _tipe ->+ return $ ObjectLit () [(PropString () "src", literal (Str src))]+ + PortIn name tipe ->+ return $ obj ["_P","portIn"] `call`+ [ string name, Port.incoming tipe ]++ PortOut name tipe value ->+ do value' <- expression value+ return $ obj ["_P","portOut"] `call`+ [ string name, Port.outgoing tipe, value' ]+++definition :: Canonical.Def -> State Int [Statement ()]+definition (Canonical.Definition pattern expr@(A region _) _) = do+ expr' <- expression expr+ let assign x = varDecl x expr'+ case pattern of+ P.Var x+ | Help.isOp x ->+ let op = LBracket () (ref "_op") (string x) in+ return [ ExprStmt () $ AssignExpr () OpAssign op expr' ]+ | otherwise ->+ return [ VarDeclStmt () [ assign (Var.varName x) ] ]++ P.Record fields ->+ let setField f = varDecl f (obj ["$",f]) in+ return [ VarDeclStmt () (assign "$" : map setField fields) ]++ P.Data (Var.Canonical _ name) patterns | vars /= Nothing ->+ return [ VarDeclStmt () (setup (zipWith decl (maybe [] id vars) [0..])) ]+ where+ vars = getVars patterns+ getVars patterns =+ case patterns of+ P.Var x : rest -> (Var.varName x :) `fmap` getVars rest+ [] -> Just []+ _ -> Nothing++ decl x n = varDecl x (obj ["$","_" ++ show n])+ setup vars+ | Help.isTuple name = assign "$" : vars+ | otherwise = assign "_raw" : safeAssign : vars++ safeAssign = varDecl "$" (CondExpr () if' (ref "_raw") exception)+ if' = InfixExpr () OpStrictEq (obj ["_raw","ctor"]) (string name)+ exception = Help.throw "badCase" region++ _ ->+ do defs' <- concat <$> mapM toDef vars+ return (VarDeclStmt () [assign "_"] : defs')+ where+ vars = P.boundVarList pattern+ mkVar = A region . localVar+ toDef y =+ let expr = A region $ Case (mkVar "_") [(pattern, mkVar y)]+ in+ definition $ Canonical.Definition (P.Var y) expr Nothing+++match :: Region -> Case.Match -> State Int [Statement ()]+match region mtch =+ case mtch of+ Case.Match name clauses mtch' ->+ do (isChars, clauses') <- unzip <$> mapM (clause region name) clauses+ mtch'' <- match region mtch'+ return (SwitchStmt () (format isChars (access name)) clauses' : mtch'')+ where+ isLiteral p = case p of+ Case.Clause (Right _) _ _ -> True+ _ -> False++ access name+ | any isLiteral clauses = obj [name]+ | otherwise = obj (Help.splitDots name ++ ["ctor"])++ format isChars e+ | or isChars = InfixExpr () OpAdd e (string "")+ | otherwise = e++ Case.Fail ->+ return [ ExprStmt () (Help.throw "badCase" region) ]++ Case.Break -> return [BreakStmt () Nothing]++ Case.Other e ->+ do e' <- expression e+ return [ ret e' ]++ Case.Seq ms -> concat <$> mapM (match region) (dropEnd [] ms)+ where+ dropEnd acc [] = acc+ dropEnd acc (m:ms) =+ case m of+ Case.Other _ -> acc ++ [m]+ _ -> dropEnd (acc ++ [m]) ms+++clause :: Region -> String -> Case.Clause -> State Int (Bool, CaseClause ())+clause region variable (Case.Clause value vars mtch) =+ (,) isChar . CaseClause () pattern <$> match region (Case.matchSubst (zip vars vars') mtch)+ where+ vars' = map (\n -> variable ++ "._" ++ show n) [0..]+ (isChar, pattern) =+ case value of+ Right (Chr c) -> (True, string [c])+ _ ->+ (,) False $+ case value of+ Right (Boolean b) -> BoolLit () b+ Right lit -> literal lit+ Left (Var.Canonical _ name) ->+ string name+++flattenLets :: [Canonical.Def] -> Canonical.Expr -> ([Canonical.Def], Canonical.Expr)+flattenLets defs lexpr@(A _ expr) =+ case expr of+ Let ds body -> flattenLets (defs ++ ds) body+ _ -> (defs, lexpr)+++generate :: CanonicalModule -> String +generate modul =+ show . prettyPrint $ setup "Elm" (names ++ ["make"]) +++ [ assign ("Elm" : names ++ ["make"]) (function [localRuntime] programStmts) ]+ where+ names :: [String]+ names = Module.names modul++ thisModule :: Expression ()+ thisModule = obj (localRuntime : names ++ ["values"])++ programStmts :: [Statement ()]+ programStmts =+ concat+ [ [ ExprStmt () (string "use strict") ]+ , setup localRuntime (names ++ ["values"])+ , [ IfSingleStmt () thisModule (ret thisModule) ]+ , [ VarDeclStmt () localVars ]+ , body+ , [ jsExports ]+ , [ ret thisModule ]+ ]++ localVars :: [VarDecl ()]+ localVars = varDecl "_op" (ObjectLit () []) :+ internalImports (Module.names modul) +++ explicitImports+ where+ explicitImports :: [VarDecl ()]+ explicitImports =+ map jsImport . Set.toList . Set.fromList . map fst $ Module.imports modul++ jsImport :: Module.Name -> VarDecl ()+ jsImport name =+ varDecl (Var.moduleName name) $+ obj ("Elm" : name ++ ["make"]) <| ref localRuntime++ body :: [Statement ()]+ body = concat $ evalState defs 0+ where+ defs = mapM definition . fst . flattenLets [] $ Module.program (Module.body modul)++ setup namespace path = map create paths+ where+ create name = assign name (InfixExpr () OpLOr (obj name) (ObjectLit () []))+ paths = drop 2 . init . List.inits $ namespace : path++ jsExports = assign (localRuntime : names ++ ["values"]) (ObjectLit () exs)+ where+ exs = map entry $ "_op" : concatMap extract (exports modul)+ entry x = (prop x, ref x)+ extract value =+ case value of+ Var.Alias _ -> []++ Var.Value x+ | Help.isOp x -> []+ | otherwise -> [Var.varName x]++ Var.Union _ (Var.Listing ctors _) ->+ map Var.varName ctors+ + assign path expr =+ case path of+ [x] -> VarDeclStmt () [ varDecl x expr ]+ _ ->+ ExprStmt () $+ AssignExpr () OpAssign (LDot () (obj (init path)) (last path)) expr+++binop+ :: Region+ -> Var.Canonical+ -> Canonical.Expr+ -> Canonical.Expr+ -> State Int (Expression ())+binop region func@(Var.Canonical home op) e1 e2 =+ case (home, op) of+ (Var.Module ["Basics"], ">>") ->+ do es <- mapM expression (collectLeftAssoc [e2] e1)+ return $ ["$"] ==> List.foldl' (\e f -> f <| e) (ref "$") es++ (Var.Module ["Basics"], "<<") ->+ do es <- mapM expression (e1 : collectRightAssoc [] e2)+ return $ ["$"] ==> foldr (<|) (ref "$") es++ (Var.Module ["Basics"], "<|") ->+ do e2' <- expression e2+ es <- mapM expression (collectRightAssoc [] e1)+ return $ foldr (<|) e2' es++ (Var.BuiltIn, "::") ->+ expression (A region (Data "::" [e1,e2]))++ (Var.Module ["Basics"], _) ->+ do e1' <- expression e1+ e2' <- expression e2+ case Map.lookup op basicOps of+ Just f ->+ return (f e1' e2')++ Nothing ->+ return (ref "A2" `call` [ Var.canonical func, e1', e2' ])++ _ ->+ do e1' <- expression e1+ e2' <- expression e2+ return (ref "A2" `call` [ Var.canonical func, e1', e2' ])++ where+ collectRightAssoc es e =+ case e of+ A _ (Binop (Var.Canonical (Var.Module ["Basics"]) "<<") e1 e2) ->+ collectRightAssoc (es ++ [e1]) e2+ _ -> es ++ [e]++ collectLeftAssoc es e =+ case e of+ A _ (Binop (Var.Canonical (Var.Module ["Basics"]) ">>") e1 e2) ->+ collectLeftAssoc (e2 : es) e1+ _ -> e : es++ basicOps =+ Map.fromList (infixOps ++ specialOps)++ infixOps =+ let infixOp str op = (str, InfixExpr () op) in+ [ infixOp "+" OpAdd+ , infixOp "-" OpSub+ , infixOp "*" OpMul+ , infixOp "/" OpDiv+ , infixOp "&&" OpLAnd+ , infixOp "||" OpLOr+ ]++ specialOps =+ [ (,) "^" $ \a b -> obj ["Math","pow"] `call` [a,b]+ , (,) "|>" $ flip (<|)+ , (,) "==" $ \a b -> _Utils "eq" `call` [a,b]+ , (,) "/=" $ \a b -> PrefixExpr () PrefixLNot (_Utils "eq" `call` [a,b])+ , (,) "<" $ cmp OpLT 0+ , (,) ">" $ cmp OpGT 0+ , (,) "<=" $ cmp OpLT 1+ , (,) ">=" $ cmp OpGT (-1)+ , (,) "//" $ \a b -> InfixExpr () OpBOr (InfixExpr () OpDiv a b) (IntLit () 0)+ ]++ cmp op n a b =+ InfixExpr () op (_Utils "cmp" `call` [a,b]) (IntLit () n)
+ src/Generate/JavaScript/Helpers.hs view
@@ -0,0 +1,118 @@+module Generate.JavaScript.Helpers where++import AST.Annotation (Region)+import qualified AST.PrettyPrint as PP+import Language.ECMAScript3.Syntax+++localRuntime :: String+localRuntime =+ "_elm"+++varDecl :: String -> Expression () -> VarDecl ()+varDecl x expr =+ VarDecl () (var x) (Just expr)+++make :: [String] -> Expression ()+make moduleName =+ obj (moduleName ++ ["make"]) <| ref localRuntime+++useLazy :: [String] -> String -> Expression ()+useLazy moduleName functionName =+ DotRef () (make moduleName) (var functionName)+++-- Creating Variables++var :: String -> Id ()+var name =+ Id () name+++ref :: String -> Expression ()+ref name =+ VarRef () (var name)+++prop :: String -> Prop ()+prop name =+ PropId () (var name)+++string = StringLit ()+++obj :: [String] -> Expression ()+obj vars =+ case vars of+ x:xs -> foldl (DotRef ()) (ref x) (map var xs)+ [] -> error "dotSep must be called on a non-empty list of variables"+++-- Specific Utilities++localModuleName :: String+localModuleName =+ "$moduleName"+++throw :: String -> Region -> Expression ()+throw kind region =+ let args =+ [ ref localModuleName+ , string (PP.renderPretty region)+ ]+ in+ obj ["_U",kind] `call` args+++-- Function Calls++(<|) :: Expression () -> Expression () -> Expression ()+(<|) f x =+ CallExpr () f [x]+++ret :: Expression () -> Statement ()+ret e =+ ReturnStmt () (Just e)+++(==>) :: [String] -> Expression () -> Expression ()+(==>) args e =+ FuncExpr () Nothing (map var args) [ ret e ]+++function :: [String] -> [Statement ()] -> Expression ()+function args stmts =+ FuncExpr () Nothing (map var args) stmts+++call :: Expression () -> [Expression ()] -> Expression ()+call =+ CallExpr ()+++-- Checks++equal :: Expression () -> Expression () -> Expression ()+equal a b =+ InfixExpr () OpStrictEq a b+++instanceof :: String -> Expression () -> Expression ()+instanceof tipe x =+ InfixExpr () OpLAnd (typeof "object" x) (InfixExpr () OpInstanceof x (ref tipe))+++typeof :: String -> Expression () -> Expression ()+typeof tipe x =+ equal (PrefixExpr () PrefixTypeof x) (string tipe)+++member :: String -> Expression () -> Expression ()+member field x =+ InfixExpr () OpIn (string field) x
+ src/Generate/JavaScript/Ports.hs view
@@ -0,0 +1,217 @@+module Generate.JavaScript.Ports (incoming, outgoing) where++import qualified Data.List as List+import Generate.JavaScript.Helpers+import AST.Type as T+import qualified AST.Variable as Var+import Language.ECMAScript3.Syntax+++data JSType+ = JSNumber+ | JSBoolean+ | JSString+ | JSArray+ | JSObject [String]+++typeToString :: JSType -> String+typeToString tipe =+ case tipe of+ JSNumber -> "a number"+ JSBoolean -> "a boolean (true or false)"+ JSString -> "a string"+ JSArray -> "an array"+ JSObject fields ->+ "an object with fields '" ++ List.intercalate "', '" fields ++ "'"+++_Array :: String -> Expression ()+_Array functionName =+ useLazy ["Elm","Native","Array"] functionName+++_List :: String -> Expression ()+_List functionName =+ useLazy ["Elm","Native","List"] functionName+++_Maybe :: String -> Expression ()+_Maybe functionName =+ useLazy ["Elm","Maybe"] functionName+++check :: Expression () -> JSType -> Expression () -> Expression ()+check x jsType continue =+ CondExpr () (jsFold OpLOr checks x) continue throw+ where+ jsFold op checks value =+ foldl1 (InfixExpr () op) (map ($ value) checks)++ throw =+ obj ["_U","badPort"] `call` [ string (typeToString jsType), x ]++ checks =+ case jsType of+ JSNumber -> [typeof "number"]+ JSBoolean -> [typeof "boolean"]+ JSString -> [typeof "string", instanceof "String"]+ JSArray -> [instanceof "Array"]+ JSObject fields -> [jsFold OpLAnd (typeof "object" : map member fields)]+++incoming :: CanonicalType -> Expression ()+incoming tipe =+ case tipe of+ Aliased _ t -> incoming t++ App (Type v) [t]+ | Var.isSignal v -> obj ["_P","incomingSignal"] <| incoming t++ _ -> ["v"] ==> inc tipe (ref "v")+++inc :: CanonicalType -> Expression () -> Expression ()+inc tipe x =+ case tipe of+ Lambda _ _ -> error "functions should not be allowed through input ports"+ Var _ -> error "type variables should not be allowed through input ports"+ Aliased _ t ->+ inc t x++ Type (Var.Canonical Var.BuiltIn name)+ | name == "Int" -> from JSNumber+ | name == "Float" -> from JSNumber+ | name == "Bool" -> from JSBoolean+ | name == "String" -> from JSString+ where+ from checks = check x checks x++ Type name+ | Var.isJson name ->+ x++ | Var.isTuple name ->+ incomingTuple [] x++ | otherwise ->+ error "bad type got to incoming port generation code"++ App f args ->+ case f : args of+ Type name : [t]+ | Var.isMaybe name ->+ CondExpr ()+ (equal x (NullLit ()))+ (_Maybe "Nothing")+ (_Maybe "Just" <| inc t x)++ | Var.isList name ->+ check x JSArray (_List "fromArray" <| array)++ | Var.isArray name ->+ check x JSArray (_Array "fromJSArray" <| array)+ where+ array = DotRef () x (var "map") <| incoming t++ Type name : ts+ | Var.isTuple name -> incomingTuple ts x++ _ -> error "bad ADT got to incoming port generation code"++ Record _ (Just _) ->+ error "bad record got to incoming port generation code"++ Record fields Nothing ->+ check x (JSObject (map fst fields)) object+ where+ object = ObjectLit () $ (prop "_", ObjectLit () []) : keys+ keys = map convert fields+ convert (f,t) = (prop f, inc t (DotRef () x (var f)))+++incomingTuple :: [CanonicalType] -> Expression () -> Expression ()+incomingTuple types x =+ check x JSArray (ObjectLit () fields)+ where+ fields = (prop "ctor", ctor) : zipWith convert [0..] types++ ctor = string ("_Tuple" ++ show (length types))++ convert n t =+ ( prop ('_':show n)+ , inc t (BracketRef () x (IntLit () n))+ )+++outgoing :: CanonicalType -> Expression ()+outgoing tipe =+ case tipe of+ Aliased _ t -> outgoing t++ App (Type v) [t]+ | Var.isSignal v -> obj ["_P","outgoingSignal"] <| outgoing t++ _ -> ["v"] ==> out tipe (ref "v")+++out :: CanonicalType -> Expression () -> Expression ()+out tipe x =+ case tipe of+ Aliased _ t -> out t x++ Lambda _ _+ | numArgs > 1 && numArgs < 10 ->+ func (ref ('A':show numArgs) `call` (x:values))+ | otherwise -> func (foldl (<|) x values)+ where+ ts = T.collectLambdas tipe+ numArgs = length ts - 1+ args = map (\n -> '_' : show n) [0..]+ values = zipWith inc (init ts) (map ref args)+ func body =+ function (take numArgs args)+ [ VarDeclStmt () [VarDecl () (var "_r") (Just body)]+ , ret (out (last ts) (ref "_r"))+ ]++ Var _ -> error "type variables should not be allowed through input ports"++ Type (Var.Canonical Var.BuiltIn name)+ | name `elem` ["Int","Float","Bool","String"] -> x++ Type name+ | Var.isJson name -> x+ | Var.isTuple name -> ArrayLit () []+ | otherwise -> error "bad type got to outgoing port generation code"++ App f args ->+ case f : args of+ Type name : [t]+ | Var.isMaybe name ->+ CondExpr ()+ (equal (DotRef () x (var "ctor")) (string "Nothing"))+ (NullLit ())+ (out t (DotRef () x (var "_0")))++ | Var.isArray name ->+ DotRef () (_Array "toJSArray" <| x) (var "map") <| outgoing t++ | Var.isList name ->+ DotRef () (_List "toArray" <| x) (var "map") <| outgoing t++ Type name : ts+ | Var.isTuple name ->+ let convert n t = out t $ DotRef () x $ var ('_':show n)+ in ArrayLit () $ zipWith convert [0..] ts++ _ -> error "bad ADT got to outgoing port generation code"++ Record _ (Just _) ->+ error "bad record got to outgoing port generation code"++ Record fields Nothing ->+ ObjectLit () keys+ where+ keys = map convert fields+ convert (f,t) = (PropId () (var f), out t (DotRef () x (var f)))
+ src/Generate/JavaScript/Variable.hs view
@@ -0,0 +1,62 @@+module Generate.JavaScript.Variable where++import qualified AST.Helpers as Help+import qualified AST.Module as Module+import qualified AST.Variable as Var+import qualified Data.List as List+import qualified Data.Set as Set+import qualified Generate.JavaScript.Helpers as JS+import qualified Language.ECMAScript3.Syntax as JS+++swap :: Char -> Char -> Char -> Char+swap from to c =+ if c == from then to else c+++canonical :: Var.Canonical -> JS.Expression ()+canonical (Var.Canonical home name) =+ case Help.isOp name of+ True -> JS.BracketRef () (JS.obj (home' ++ ["_op"])) (JS.string name)+ False -> JS.obj (home' ++ [ varName name ])+ where+ home' =+ case home of+ Var.Local -> []+ Var.BuiltIn -> []+ Var.Module path -> [ moduleName path ]+++moduleName :: Module.Name -> String+moduleName name =+ '$' : List.intercalate "$" name+++varName :: String -> String+varName x = map (swap '\'' '$') x'+ where+ x' = if Set.member x jsReserveds then '$' : x else x+++value :: Module.Name -> String -> JS.Expression ()+value home name =+ canonical (Var.Canonical (Var.Module home) name)+++jsReserveds :: Set.Set String+jsReserveds = Set.fromList+ [ "null", "undefined", "Nan", "Infinity", "true", "false", "eval"+ , "arguments", "int", "byte", "char", "goto", "long", "final", "float"+ , "short", "double", "native", "throws", "boolean", "abstract", "volatile"+ , "transient", "synchronized", "function", "break", "case", "catch"+ , "continue", "debugger", "default", "delete", "do", "else", "finally"+ , "for", "function", "if", "in", "instanceof", "new", "return", "switch"+ , "this", "throw", "try", "typeof", "var", "void", "while", "with", "class"+ , "const", "enum", "export", "extends", "import", "super", "implements"+ , "interface", "let", "package", "private", "protected", "public"+ , "static", "yield"+ -- reserved by the Elm runtime system+ , "Elm", "ElmRuntime"+ , "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"+ , "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"+ ]
+ src/Parse/Binop.hs view
@@ -0,0 +1,85 @@+module Parse.Binop (binops, OpTable) where++import Control.Applicative ((<$>))+import qualified Data.List as List+import qualified Data.Map as Map+import Text.Parsec ((<|>), choice, getState, try)++import AST.Annotation (merge)+import AST.Declaration (Assoc(L, N, R))+import AST.Expression.General (Expr'(Binop))+import qualified AST.Expression.Source as Source+import qualified AST.Variable as Var+import Parse.Helpers (IParser, OpTable, commitIf, failure, whitespace)++opLevel :: OpTable -> String -> Int+opLevel table op = fst $ Map.findWithDefault (9,L) op table++opAssoc :: OpTable -> String -> Assoc+opAssoc table op = snd $ Map.findWithDefault (9,L) op table++hasLevel :: OpTable -> Int -> (String, Source.Expr) -> Bool+hasLevel table n (op,_) = opLevel table op == n++binops :: IParser Source.Expr+ -> IParser Source.Expr+ -> IParser String+ -> IParser Source.Expr+binops term last anyOp =+ do e <- term+ table <- getState+ split table 0 e =<< nextOps+ where+ nextOps = choice [ commitIf (whitespace >> anyOp) $ do+ whitespace ; op <- anyOp ; whitespace+ expr <- Left <$> try term <|> Right <$> last+ case expr of+ Left t -> (:) (op,t) <$> nextOps+ Right e -> return [(op,e)]+ , return [] ]++split :: OpTable+ -> Int+ -> Source.Expr+ -> [(String, Source.Expr)]+ -> IParser Source.Expr+split _ _ e [] = return e+split table n e eops = do+ assoc <- getAssoc table n eops+ es <- sequence (splitLevel table n e eops)+ let ops = map fst (filter (hasLevel table n) eops)+ case assoc of R -> joinR es ops+ _ -> joinL es ops++splitLevel :: OpTable -> Int -> Source.Expr -> [(String, Source.Expr)]+ -> [IParser Source.Expr]+splitLevel table n e eops =+ case break (hasLevel table n) eops of+ (lops, (_op,e'):rops) ->+ split table (n+1) e lops : splitLevel table n e' rops+ (lops, []) -> [ split table (n+1) e lops ]++joinL :: [Source.Expr] -> [String] -> IParser Source.Expr+joinL [e] [] = return e+joinL (a:b:es) (op:ops) = joinL (merge a b (Binop (Var.Raw op) a b) : es) ops+joinL _ _ = failure "Ill-formed binary expression. Report a compiler bug."++joinR :: [Source.Expr] -> [String] -> IParser Source.Expr+joinR [e] [] = return e+joinR (a:b:es) (op:ops) = do e <- joinR (b:es) ops+ return (merge a e (Binop (Var.Raw op) a e))+joinR _ _ = failure "Ill-formed binary expression. Report a compiler bug."++getAssoc :: OpTable -> Int -> [(String,Source.Expr)] -> IParser Assoc+getAssoc table n eops+ | all (==L) assocs = return L+ | all (==R) assocs = return R + | all (==N) assocs = case assocs of [_] -> return N+ _ -> failure (msg "precedence")+ | otherwise = failure (msg "associativity")+ where levelOps = filter (hasLevel table n) eops+ assocs = map (opAssoc table . fst) levelOps+ msg problem =+ concat [ "Conflicting " ++ problem ++ " for binary operators ("+ , List.intercalate ", " (map fst eops), "). "+ , "Consider adding parentheses to disambiguate." ]
+ src/Parse/Declaration.hs view
@@ -0,0 +1,61 @@+{-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}+module Parse.Declaration where++import Control.Applicative ((<$>))+import Text.Parsec ((<|>), (<?>), choice, digit, optionMaybe, string, try)++import qualified AST.Declaration as D+import qualified Parse.Expression as Expr+import Parse.Helpers+import qualified Parse.Type as Type+++declaration :: IParser D.SourceDecl+declaration =+ typeDecl <|> infixDecl <|> port <|> definition+++definition :: IParser D.SourceDecl+definition = D.Definition <$> Expr.def+++typeDecl :: IParser D.SourceDecl+typeDecl =+ do reserved "type" <?> "type declaration"+ forcedWS+ isAlias <- optionMaybe (string "alias" >> forcedWS)++ name <- capVar+ args <- spacePrefix lowVar+ padded equals++ case isAlias of+ Just _ ->+ do tipe <- Type.expr <?> "a type"+ return (D.TypeAlias name args tipe)++ Nothing ->+ do tcs <- pipeSep1 Type.constructor <?> "a constructor for a union type"+ return $ D.Datatype name args tcs+++infixDecl :: IParser D.SourceDecl+infixDecl = do+ assoc <- choice [ reserved "infixl" >> return D.L+ , reserved "infix" >> return D.N+ , reserved "infixr" >> return D.R ]+ forcedWS+ n <- digit+ forcedWS+ D.Fixity assoc (read [n]) <$> anyOp+++port :: IParser D.SourceDecl+port =+ do try (reserved "port")+ whitespace+ name <- lowVar+ whitespace+ let port' op ctor expr = do { try op ; whitespace ; ctor name <$> expr }+ D.Port <$> choice [ port' hasType D.PPAnnotation Type.expr+ , port' equals D.PPDef Expr.expr ]
+ src/Parse/Expression.hs view
@@ -0,0 +1,239 @@+module Parse.Expression (def,term,typeAnnotation,expr) where++import Control.Applicative ((<$>), (<*>))+import qualified Data.List as List+import Text.Parsec hiding (newline, spaces)+import Text.Parsec.Indent (block, withPos)++import qualified Parse.Binop as Binop+import Parse.Helpers+import qualified Parse.Helpers as Help+import qualified Parse.Literal as Literal+import qualified Parse.Pattern as Pattern+import qualified Parse.Type as Type++import qualified AST.Annotation as Annotation+import qualified AST.Expression.General as E+import qualified AST.Expression.Source as Source+import qualified AST.Literal as L+import qualified AST.Pattern as P+import qualified AST.Variable as Var+++-------- Basic Terms --------++varTerm :: IParser Source.Expr'+varTerm = toVar <$> var <?> "variable"++toVar :: String -> Source.Expr'+toVar v =+ case v of+ "True" -> E.Literal (L.Boolean True)+ "False" -> E.Literal (L.Boolean False)+ _ -> E.rawVar v++accessor :: IParser Source.Expr'+accessor = do+ (start, lbl, end) <- located (try (string "." >> rLabel))+ let loc e = Annotation.at start end e+ return (E.Lambda (P.Var "_") (loc $ E.Access (loc $ E.rawVar "_") lbl))++negative :: IParser Source.Expr'+negative = do+ (start, nTerm, end) <-+ located (try (char '-' >> notFollowedBy (char '.' <|> char '-')) >> term)+ let loc e = Annotation.at start end e+ return (E.Binop (Var.Raw "-") (loc $ E.Literal (L.IntNum 0)) nTerm)+++-------- Complex Terms --------++listTerm :: IParser Source.Expr'+listTerm = shader' <|> braces (try range <|> E.ExplicitList <$> commaSep expr)+ where+ range = do+ lo <- expr+ padded (string "..")+ E.Range lo <$> expr++ shader' = do+ pos <- getPosition+ let uid = show (sourceLine pos) ++ ":" ++ show (sourceColumn pos)+ (rawSrc, tipe) <- Help.shader+ return $ E.GLShader uid (filter (/='\r') rawSrc) tipe+++parensTerm :: IParser Source.Expr+parensTerm = try (parens opFn) <|> parens (tupleFn <|> parened)+ where+ opFn = do+ (start, op, end) <- located anyOp+ let loc = Annotation.at start end+ return . loc . E.Lambda (P.Var "x") . loc . E.Lambda (P.Var "y") . loc $+ E.Binop (Var.Raw op) (loc (E.rawVar "x")) (loc (E.rawVar "y"))++ tupleFn = do+ let comma = char ',' <?> "comma ','"+ (start, commas, end) <- located (comma >> many (whitespace >> comma))+ let vars = map (('v':) . show) [ 0 .. length commas + 1 ]+ loc = Annotation.at start end+ return $ foldr (\x e -> loc $ E.Lambda x e)+ (loc . E.tuple $ map (loc . E.rawVar) vars) (map P.Var vars)+ + parened = do+ (start, es, end) <- located (commaSep expr)+ return $ case es of+ [e] -> e+ _ -> Annotation.at start end (E.tuple es)++recordTerm :: IParser Source.Expr+recordTerm = brackets $ choice [ misc, addLocation record ]+ where+ field = do+ label <- rLabel+ patterns <- spacePrefix Pattern.term+ padded equals+ body <- expr+ return (label, makeFunction patterns body)+ + record = E.Record <$> commaSep field++ change = do+ lbl <- rLabel+ padded (string "<-")+ (,) lbl <$> expr++ remove r = addLocation (string "-" >> whitespace >> E.Remove r <$> rLabel)++ insert r = addLocation $ do+ string "|" >> whitespace+ E.Insert r <$> rLabel <*> (padded equals >> expr)++ modify r =+ addLocation (string "|" >> whitespace >> E.Modify r <$> commaSep1 change)++ misc = try $ do+ record <- addLocation (E.rawVar <$> rLabel)+ opt <- padded (optionMaybe (remove record))+ case opt of+ Just e -> try (insert e) <|> return e+ Nothing -> try (insert record) <|> try (modify record)+ ++term :: IParser Source.Expr+term = addLocation (choice [ E.Literal <$> Literal.literal, listTerm, accessor, negative ])+ <|> accessible (addLocation varTerm <|> parensTerm <|> recordTerm)+ <?> "basic term (4, x, 'c', etc.)"++-------- Applications --------++appExpr :: IParser Source.Expr+appExpr = do+ t <- term+ ts <- constrainedSpacePrefix term $ \str ->+ if null str then notFollowedBy (char '-') else return ()+ return $ case ts of+ [] -> t+ _ -> List.foldl' (\f x -> Annotation.merge f x $ E.App f x) t ts++-------- Normal Expressions --------++binaryExpr :: IParser Source.Expr+binaryExpr = Binop.binops appExpr lastExpr anyOp+ where lastExpr = addLocation (choice [ ifExpr, letExpr, caseExpr ])+ <|> lambdaExpr++ifExpr :: IParser Source.Expr'+ifExpr = reserved "if" >> whitespace >> (normal <|> multiIf)+ where+ normal = do+ bool <- expr+ padded (reserved "then")+ thenBranch <- expr+ whitespace <?> "an 'else' branch" ; reserved "else" <?> "an 'else' branch" ; whitespace+ elseBranch <- expr+ return $ E.MultiIf+ [ (bool, thenBranch)+ , (Annotation.sameAs elseBranch (E.Literal . L.Boolean $ True), elseBranch)+ ]++ multiIf = E.MultiIf <$> spaceSep1 iff+ where iff = do string "|" ; whitespace+ b <- expr ; padded arrow+ (,) b <$> expr++lambdaExpr :: IParser Source.Expr+lambdaExpr = do char '\\' <|> char '\x03BB' <?> "anonymous function"+ whitespace+ args <- spaceSep1 Pattern.term+ padded arrow+ body <- expr+ return (makeFunction args body)++defSet :: IParser [Source.Def]+defSet = block (do d <- def ; whitespace ; return d)++letExpr :: IParser Source.Expr'+letExpr = do+ reserved "let" ; whitespace+ defs <- defSet+ padded (reserved "in")+ E.Let defs <$> expr++caseExpr :: IParser Source.Expr'+caseExpr = do+ reserved "case"; e <- padded expr; reserved "of"; whitespace+ E.Case e <$> (with <|> without)+ where case_ = do p <- Pattern.expr+ padded arrow+ (,) p <$> expr+ with = brackets (semiSep1 (case_ <?> "cases { x -> ... }"))+ without = block (do c <- case_ ; whitespace ; return c)++expr :: IParser Source.Expr+expr = addLocation (choice [ ifExpr, letExpr, caseExpr ])+ <|> lambdaExpr+ <|> binaryExpr + <?> "an expression"++defStart :: IParser [P.RawPattern]+defStart =+ choice [ do p1 <- try Pattern.term+ infics p1 <|> func p1+ , func =<< (P.Var <$> parens symOp)+ , (:[]) <$> Pattern.expr+ ] <?> "the definition of a variable (x = ...)"+ where+ func pattern =+ case pattern of+ P.Var _ -> (pattern:) <$> spacePrefix Pattern.term+ _ -> do try (lookAhead (whitespace >> string "="))+ return [pattern]++ infics p1 = do+ o:p <- try (whitespace >> anyOp)+ p2 <- (whitespace >> Pattern.term)+ return $ if o == '`' then [ P.Var $ takeWhile (/='`') p, p1, p2 ]+ else [ P.Var (o:p), p1, p2 ]++makeFunction :: [P.RawPattern] -> Source.Expr -> Source.Expr+makeFunction args body@(Annotation.A ann _) =+ foldr (\arg body' -> Annotation.A ann $ E.Lambda arg body') body args++definition :: IParser Source.Def+definition = withPos $ do+ (name:args) <- defStart+ padded equals+ body <- expr+ return . Source.Definition name $ makeFunction args body++typeAnnotation :: IParser Source.Def+typeAnnotation = Source.TypeAnnotation <$> try start <*> Type.expr+ where+ start = do+ v <- lowVar <|> parens symOp+ padded hasType+ return v++def :: IParser Source.Def+def = typeAnnotation <|> definition
+ src/Parse/Helpers.hs view
@@ -0,0 +1,577 @@+module Parse.Helpers where++import Prelude hiding (until)+import Control.Applicative ((<$>),(<*>))+import Control.Monad (guard, join)+import Control.Monad.State (State)+import Data.Char (isUpper)+import qualified Data.Map as Map+import qualified Language.GLSL.Parser as GLP+import qualified Language.GLSL.Syntax as GLS+import Text.Parsec hiding (newline,spaces,State)+import Text.Parsec.Indent (indented, runIndent)+import qualified Text.Parsec.Token as T++import qualified AST.Annotation as Annotation+import qualified AST.Declaration as Decl+import qualified AST.Expression.General as E+import qualified AST.Expression.Source as Source+import qualified AST.Helpers as Help+import qualified AST.Literal as L+import qualified AST.PrettyPrint as P+import qualified AST.Variable as Variable+import Elm.Utils ((|>))++reserveds :: [String]+reserveds =+ [ "if", "then", "else"+ , "case", "of"+ , "let", "in"+ , "type"+ , "module", "where"+ , "import", "as", "hiding", "open"+ , "export", "foreign"+ , "deriving", "port"+ ]+++expecting = flip (<?>)+++type OpTable = Map.Map String (Int, Decl.Assoc)+type SourceM = State SourcePos+type IParser a = ParsecT String OpTable SourceM a+++iParse :: IParser a -> String -> Either ParseError a+iParse parser source =+ iParseWithTable "" Map.empty parser source+++iParseWithTable :: SourceName -> OpTable -> IParser a -> String -> Either ParseError a+iParseWithTable sourceName table aParser input =+ runIndent sourceName $ runParserT aParser table sourceName input+++-- VARIABLES++var :: IParser String+var =+ makeVar (letter <|> char '_' <?> "variable")+++lowVar :: IParser String+lowVar =+ makeVar (lower <?> "lower case variable")+++capVar :: IParser String+capVar =+ makeVar (upper <?> "upper case variable")+++qualifiedVar :: IParser String+qualifiedVar =+ do vars <- many ((++) <$> capVar <*> string ".")+ (++) (concat vars) <$> lowVar+++rLabel :: IParser String+rLabel = lowVar+++innerVarChar :: IParser Char+innerVarChar =+ alphaNum <|> char '_' <|> char '\'' <?> "" +++makeVar :: IParser Char -> IParser String+makeVar p =+ do v <- (:) <$> p <*> many innerVarChar+ if v `elem` reserveds+ then fail $ "unexpected keyword '" ++ v ++ "', variables cannot be keywords"+ else return v+++reserved :: String -> IParser String+reserved word =+ (try (string word >> notFollowedBy innerVarChar) >> return word)+ <?> "reserved word '" ++ word ++ "'"+++-- INFIX OPERATORS++anyOp :: IParser String+anyOp =+ betwixt '`' '`' qualifiedVar+ <|> symOp+ <?> "infix operator"+++symOp :: IParser String+symOp =+ do op <- many1 (satisfy Help.isSymbol)+ guard (op `notElem` [ "=", "..", "->", "--", "|", "\8594", ":" ])+ case op of+ "." -> notFollowedBy lower >> return op+ "\8728" -> return "."+ _ -> return op+++-- COMMON SYMBOLS++equals :: IParser String+equals = string "="++arrow :: IParser String+arrow = string "->" <|> string "\8594" <?> "arrow (->)"++hasType :: IParser String+hasType = string ":" <?> "':' (a type annotation)'"+++commitIf check p =+ commit <|> try p+ where+ commit =+ try (lookAhead check) >> p+++-- SEPARATORS++spaceySepBy1 :: IParser b -> IParser a -> IParser [a]+spaceySepBy1 sep p =+ do a <- p+ (a:) <$> many (commitIf (whitespace >> sep) (padded sep >> p))+++commaSep1 :: IParser a -> IParser [a]+commaSep1 =+ spaceySepBy1 (char ',' <?> "comma ','")+++commaSep :: IParser a -> IParser [a]+commaSep =+ option [] . commaSep1+++semiSep1 :: IParser a -> IParser [a]+semiSep1 =+ spaceySepBy1 (char ';' <?> "semicolon ';'")+++pipeSep1 :: IParser a -> IParser [a]+pipeSep1 =+ spaceySepBy1 (char '|' <?> "type divider '|'")+++consSep1 :: IParser a -> IParser [a]+consSep1 =+ spaceySepBy1 (string "::" <?> "cons operator '::'")+++dotSep1 :: IParser a -> IParser [a]+dotSep1 p =+ (:) <$> p <*> many (try (char '.') >> p)+++spaceSep1 :: IParser a -> IParser [a]+spaceSep1 p =+ (:) <$> p <*> spacePrefix p+++spacePrefix p =+ constrainedSpacePrefix p (\_ -> return ())+++constrainedSpacePrefix p constraint =+ many $ choice+ [ try (spacing >> lookAhead (oneOf "[({")) >> p+ , try (spacing >> p)+ ]+ where+ spacing = do+ n <- whitespace+ constraint n+ indented+++-- SURROUNDED BY++followedBy a b =+ do x <- a+ b+ return x+++betwixt a b c =+ do char a+ out <- c+ char b <?> "closing '" ++ [b] ++ "'"+ return out+++surround a z name p = do+ char a+ v <- padded p+ char z <?> unwords ["closing", name, show z]+ return v+++braces :: IParser a -> IParser a+braces =+ surround '[' ']' "brace"+++parens :: IParser a -> IParser a+parens =+ surround '(' ')' "paren"+++brackets :: IParser a -> IParser a+brackets =+ surround '{' '}' "bracket"+++-- HELPERS FOR EXPRESSIONS++addLocation :: (P.Pretty a) => IParser a -> IParser (Annotation.Located a)+addLocation expr =+ do (start, e, end) <- located expr+ return (Annotation.at start end e)+++located :: IParser a -> IParser (SourcePos, a, SourcePos)+located p =+ do start <- getPosition+ e <- p+ end <- getPosition+ return (start, e, end)+++accessible :: IParser Source.Expr -> IParser Source.Expr+accessible expr =+ do start <- getPosition+ ce@(Annotation.A _ e) <- expr+ let rest f = do+ let dot = char '.' >> notFollowedBy (char '.')+ access <- optionMaybe (try dot <?> "field access (e.g. List.map)")+ case access of+ Nothing -> return ce+ Just _ -> accessible $ do+ v <- var <?> "field access (e.g. List.map)"+ end <- getPosition+ return (Annotation.at start end (f v))+ case e of+ E.Var (Variable.Raw (c:cs))+ | isUpper c -> rest (\v -> E.rawVar (c:cs ++ '.':v))+ | otherwise -> rest (E.Access ce)+ _ -> rest (E.Access ce)+++-- WHITESPACE++padded :: IParser a -> IParser a+padded p =+ do whitespace+ out <- p+ whitespace+ return out+++spaces :: IParser String+spaces =+ let space = string " " <|> multiComment <?> "whitespace"+ in+ concat <$> many1 space+++forcedWS :: IParser String+forcedWS =+ choice+ [ (++) <$> spaces <*> (concat <$> many nl_space)+ , concat <$> many1 nl_space+ ]+ where+ nl_space =+ try ((++) <$> (concat <$> many1 newline) <*> spaces)+++-- Just eats whitespace until the next meaningful character.+dumbWhitespace :: IParser String+dumbWhitespace =+ concat <$> many (spaces <|> newline)+++whitespace :: IParser String+whitespace =+ option "" forcedWS+++freshLine :: IParser [[String]]+freshLine =+ try (many1 newline >> many space_nl) <|> try (many1 space_nl) <?> ""+ where+ space_nl = try $ spaces >> many1 newline+++newline :: IParser String+newline =+ simpleNewline <|> lineComment <?> "a newline"+++simpleNewline :: IParser String+simpleNewline =+ try (string "\r\n") <|> string "\n"+++lineComment :: IParser String+lineComment =+ do try (string "--")+ comment <- anyUntil $ simpleNewline <|> (eof >> return "\n")+ return ("--" ++ comment)+++multiComment :: IParser String+multiComment =+ (++) <$> try (string "{-") <*> closeComment+++closeComment :: IParser String+closeComment =+ anyUntil $+ choice $+ [ try (string "-}") <?> "close comment"+ , concat <$> sequence [ try (string "{-"), closeComment, closeComment ]+ ]+++-- ODD COMBINATORS++failure msg = do+ inp <- getInput+ setInput ('x':inp)+ anyToken+ fail msg+++until :: IParser a -> IParser b -> IParser b+until p end =+ go+ where+ go = end <|> (p >> go)+++anyUntil :: IParser String -> IParser String+anyUntil end =+ go+ where+ go =+ end <|> (:) <$> anyChar <*> go+++ignoreUntil :: IParser a -> IParser (Maybe a)+ignoreUntil end =+ go+ where+ ignore p =+ const () <$> p++ filler =+ choice+ [ try (ignore chr) <|> ignore str+ , ignore multiComment+ , ignore anyChar+ ]++ go =+ choice+ [ Just <$> end+ , filler `until` choice [ const Nothing <$> eof, newline >> go ]+ ]+++onFreshLines :: (a -> b -> b) -> b -> IParser a -> IParser b+onFreshLines insert init thing =+ go init+ where+ go values =+ do optionValue <- ignoreUntil thing+ case optionValue of+ Nothing -> return values+ Just v -> go (insert v values)+++withSource :: IParser a -> IParser (String, a)+withSource p =+ do start <- getParserState+ result <- p+ endPos <- getPosition+ setParserState start+ raw <- anyUntilPos endPos+ return (raw, result)+++anyUntilPos :: SourcePos -> IParser String+anyUntilPos pos =+ go+ where+ go = do currentPos <- getPosition+ case currentPos == pos of+ True -> return []+ False -> (:) <$> anyChar <*> go+++-- BASIC LANGUAGE LITERALS++shader :: IParser (String, L.GLShaderTipe)+shader =+ do try (string "[glsl|")+ rawSrc <- closeShader id+ case glSource rawSrc of+ Left err -> parserFail . show $ err+ Right tipe -> return (rawSrc, tipe)+ where+ closeShader builder =+ choice+ [ do try (string "|]")+ return (builder "")+ , do c <- anyChar+ closeShader (builder . (c:))+ ]+++glSource :: String -> Either ParseError L.GLShaderTipe+glSource src =+ case GLP.parse src of+ Left e -> Left e+ Right (GLS.TranslationUnit decls) ->+ map extractGLinputs decls+ |> join + |> foldr addGLinput emptyDecls+ |> Right+ where + emptyDecls = L.GLShaderTipe Map.empty Map.empty Map.empty++ addGLinput (qual,tipe,name) glDecls =+ case qual of+ GLS.Attribute ->+ glDecls { L.attribute = Map.insert name tipe $ L.attribute glDecls }++ GLS.Uniform ->+ glDecls { L.uniform = Map.insert name tipe $ L.uniform glDecls }++ GLS.Varying ->+ glDecls { L.varying = Map.insert name tipe $ L.varying glDecls }++ _ -> error "Should never happen due to below filter"++ extractGLinputs decl =+ case decl of+ GLS.Declaration+ (GLS.InitDeclaration+ (GLS.TypeDeclarator+ (GLS.FullType+ (Just (GLS.TypeQualSto qual))+ (GLS.TypeSpec _prec (GLS.TypeSpecNoPrecision tipe _mexpr1))))+ [GLS.InitDecl name _mexpr2 _mexpr3]+ ) ->+ case elem qual [GLS.Attribute, GLS.Varying, GLS.Uniform] of+ False -> []+ True ->+ case tipe of + GLS.Int -> return (qual, L.Int,name)+ GLS.Float -> return (qual, L.Float,name)+ GLS.Vec2 -> return (qual, L.V2,name)+ GLS.Vec3 -> return (qual, L.V3,name) + GLS.Vec4 -> return (qual, L.V4,name)+ GLS.Mat4 -> return (qual, L.M4,name)+ GLS.Sampler2D -> return (qual, L.Texture,name)+ _ -> []+ _ -> []+++str :: IParser String+str = expecting "String" $ do+ s <- choice [ multiStr, singleStr ]+ processAs T.stringLiteral . sandwich '\"' $ concat s+ where+ rawString quote insides =+ quote >> manyTill insides quote++ multiStr = rawString (try (string "\"\"\"")) multilineStringChar+ singleStr = rawString (char '"') stringChar++ stringChar :: IParser String+ stringChar = choice [ newlineChar, escaped '\"', (:[]) <$> satisfy (/= '\"') ]++ multilineStringChar :: IParser String+ multilineStringChar =+ do noEnd+ choice [ newlineChar, escaped '\"', expandQuote <$> anyChar ]+ where+ noEnd = notFollowedBy (string "\"\"\"")+ expandQuote c = if c == '\"' then "\\\"" else [c]++ newlineChar :: IParser String+ newlineChar =+ choice [ char '\n' >> return "\\n"+ , char '\r' >> return "\\r" ]+++sandwich :: Char -> String -> String+sandwich delim s =+ delim : s ++ [delim]+++escaped :: Char -> IParser String+escaped delim =+ try $ do+ char '\\'+ c <- char '\\' <|> char delim+ return ['\\', c]+++chr :: IParser Char+chr =+ betwixt '\'' '\'' character <?> "character"+ where+ nonQuote = satisfy (/='\'')++ character =+ do c <- choice+ [ escaped '\''+ , (:) <$> char '\\' <*> many1 nonQuote+ , (:[]) <$> nonQuote+ ]++ processAs T.charLiteral $ sandwich '\'' c+++processAs :: (T.GenTokenParser String u SourceM -> IParser a) -> String -> IParser a+processAs processor s =+ calloutParser s (processor lexer)+ where+ calloutParser :: String -> IParser a -> IParser a+ calloutParser inp p =+ either (fail . show) return (iParse p inp)++ lexer :: T.GenTokenParser String u SourceM+ lexer = T.makeTokenParser elmDef++ -- I don't know how many of these are necessary for charLiteral/stringLiteral+ elmDef :: T.GenLanguageDef String u SourceM+ elmDef =+ T.LanguageDef+ { T.commentStart = "{-"+ , T.commentEnd = "-}"+ , T.commentLine = "--"+ , T.nestedComments = True+ , T.identStart = undefined+ , T.identLetter = undefined+ , T.opStart = undefined+ , T.opLetter = undefined+ , T.reservedNames = reserveds+ , T.reservedOpNames = [":", "->", "<-", "|"]+ , T.caseSensitive = True+ }
+ src/Parse/Literal.hs view
@@ -0,0 +1,72 @@+module Parse.Literal (literal) where++import Prelude hiding (exponent)+import Control.Applicative ((<$>))+import Text.Parsec ((<|>), (<?>), digit, hexDigit, lookAhead, many1, option, string, try)+import Parse.Helpers (IParser, chr, str)+import qualified AST.Literal as L+++literal :: IParser L.Literal+literal =+ num <|> (L.Str <$> str) <|> (L.Chr <$> chr) <?> "literal"+++num :: IParser L.Literal+num =+ toLiteral <$> (rawNumber <?> "number")+++toLiteral :: String -> L.Literal+toLiteral n+ | 'x' `elem` n = L.IntNum (read n) + | any (`elem` ".eE") n = L.FloatNum (read n)+ | otherwise = L.IntNum (read n) +++rawNumber :: IParser String+rawNumber =+ concat <$> sequence+ [ option "" minus+ , base16 <|> base10+ ]+++base16 :: IParser String+base16 =+ do try (string "0x")+ digits <- many1 hexDigit+ return ("0x" ++ digits)+++base10 :: IParser String+base10 =+ concat <$> sequence+ [ many1 digit+ , option "" decimals+ , option "" exponent+ ]+++minus :: IParser String+minus =+ try $ do+ string "-"+ lookAhead digit+ return "-"+++decimals :: IParser String+decimals =+ do try $ lookAhead (string "." >> digit)+ string "."+ n <- many1 digit+ return ('.' : n)+++exponent :: IParser String+exponent =+ do string "e" <|> string "E"+ op <- option "" (string "+" <|> string "-")+ n <- many1 digit+ return ('e' : op ++ n)
+ src/Parse/Module.hs view
@@ -0,0 +1,88 @@+module Parse.Module (header, headerAndImports, getModuleName) where++import Control.Applicative ((<$>), (<*>))+import Data.List (intercalate)+import Text.Parsec hiding (newline, spaces)++import Parse.Helpers+import qualified AST.Module as Module+import qualified AST.Variable as Var++getModuleName :: String -> Maybe String+getModuleName source =+ case iParse getModuleName source of+ Right name -> Just name+ Left _ -> Nothing+ where+ getModuleName = do+ optional freshLine+ (names, _) <- header+ return (intercalate "." names)++headerAndImports :: IParser Module.HeaderAndImports+headerAndImports =+ do optional freshLine+ (names, exports) <-+ option (["Main"], Var.openListing) (header `followedBy` freshLine)+ imports' <- commitToImports <|> return []+ return $ Module.HeaderAndImports names exports imports'+ where+ commitToImports =+ do try (lookAhead $ reserved "import")+ imports `followedBy` freshLine++header :: IParser ([String], Var.Listing Var.Value)+header = do+ try (reserved "module")+ whitespace+ names <- dotSep1 capVar <?> "name of module"+ whitespace+ exports <- option Var.openListing (listing value)+ whitespace <?> "reserved word 'where'"+ reserved "where"+ return (names, exports)++imports :: IParser [(Module.Name, Module.ImportMethod)]+imports = option [] ((:) <$> import' <*> many (try (freshLine >> import')))++import' :: IParser (Module.Name, Module.ImportMethod)+import' =+ do reserved "import"+ whitespace+ names <- dotSep1 capVar+ (,) names <$> option (Module.As (intercalate "." names)) method+ where+ method :: IParser Module.ImportMethod+ method = as' <|> importing'++ as' :: IParser Module.ImportMethod+ as' = do+ try (whitespace >> reserved "as")+ whitespace+ Module.As <$> capVar <?> "alias for module"++ importing' :: IParser Module.ImportMethod+ importing' = Module.Open <$> listing value++listing :: IParser a -> IParser (Var.Listing a)+listing item =+ do try (whitespace >> char '(')+ whitespace+ listing <- choice [ const Var.openListing <$> string ".."+ , Var.Listing <$> commaSep1 item <*> return False+ ] <?> "listing of values (x,y,z)"+ whitespace+ char ')'+ return listing++value :: IParser Var.Value+value = val <|> tipe+ where+ val = Var.Value <$> (lowVar <|> parens symOp)++ tipe = do+ name <- capVar+ maybeCtors <- optionMaybe (listing capVar)+ case maybeCtors of+ Nothing -> return (Var.Alias name)+ Just ctors -> return (Var.Union name ctors)
+ src/Parse/Parse.hs view
@@ -0,0 +1,64 @@+module Parse.Parse (program) where++import Control.Applicative ((<$>))+import qualified Data.List as List+import qualified Data.Map as Map+import Text.Parsec hiding (newline, spaces)+import qualified Text.PrettyPrint as P++import qualified AST.Declaration as D+import qualified AST.Module as M+import Parse.Helpers+import Parse.Declaration (infixDecl)+import qualified Parse.Module as Module+import qualified Parse.Declaration as Decl+import Transform.Declaration (combineAnnotations)++freshDef = commitIf (freshLine >> (letter <|> char '_')) $ do+ freshLine+ Decl.declaration <?> "another datatype or variable definition"++decls = do d <- Decl.declaration <?> "at least one datatype or variable definition"+ (d:) <$> many freshDef++program :: OpTable -> String -> Either [P.Doc] M.ValidModule+program table src =+ do (M.Module names filePath exs ims sourceDecls) <-+ setupParserWithTable table programParser src++ decls <-+ either (\err -> Left [P.text err]) Right (combineAnnotations sourceDecls)+ return $ M.Module names filePath exs ims decls++programParser :: IParser M.SourceModule+programParser =+ do (M.HeaderAndImports names exports imports) <- Module.headerAndImports+ declarations <- decls+ optional freshLine ; optional spaces ; eof+ return $ M.Module names "" exports imports declarations++setupParserWithTable :: OpTable -> IParser a -> String -> Either [P.Doc] a+setupParserWithTable table p source =+ do localTable <- setupParser parseFixities source+ case Map.intersection table localTable of+ overlap | not (Map.null overlap) -> Left [ msg overlap ]+ | otherwise -> + flip setupParser source $ do+ putState (Map.union table localTable)+ p+ where+ msg overlap =+ P.vcat [ P.text "Parse error:"+ , P.text $ "Overlapping definitions for infix operators: " +++ List.intercalate " " (Map.keys overlap)+ ]++parseFixities = do+ decls <- onFreshLines (:) [] infixDecl+ return $ Map.fromList [ (op,(lvl,assoc)) | D.Fixity assoc lvl op <- decls ]+ +setupParser :: IParser a -> String -> Either [P.Doc] a+setupParser p source =+ case iParse p source of+ Right result -> Right result+ Left err -> Left [ P.text $ "Parse error at " ++ show err ]
+ src/Parse/Pattern.hs view
@@ -0,0 +1,63 @@+module Parse.Pattern (term, expr) where++import Control.Applicative ((<$>))+import Data.Char (isUpper)+import qualified Data.List as List+import Text.Parsec ((<|>), (<?>), char, choice, optionMaybe, try)++import Parse.Helpers+import qualified Parse.Literal as Literal+import qualified AST.Literal as L+import qualified AST.Pattern as P+import qualified AST.Variable as Var++basic :: IParser P.RawPattern+basic = choice+ [ char '_' >> return P.Anything+ , stringToPattern <$> var+ , P.Literal <$> Literal.literal+ ]+ where+ stringToPattern v =+ case v of+ "True" -> P.Literal (L.Boolean True)+ "False" -> P.Literal (L.Boolean False)+ c:_ | isUpper c -> P.Data (Var.Raw v) []+ _ -> P.Var v++asPattern :: P.RawPattern -> IParser P.RawPattern+asPattern pattern = do+ var <- optionMaybe (try (whitespace >> reserved "as" >> whitespace >> lowVar))+ return $ case var of+ Just v -> P.Alias v pattern+ Nothing -> pattern++record :: IParser P.RawPattern+record = P.Record <$> brackets (commaSep1 lowVar)++tuple :: IParser P.RawPattern+tuple = do+ ps <- parens (commaSep expr)+ return $ case ps of+ [p] -> p+ _ -> P.tuple ps++list :: IParser P.RawPattern+list = P.list <$> braces (commaSep expr)++term :: IParser P.RawPattern+term =+ (choice [ record, tuple, list, basic ]) <?> "pattern"++patternConstructor :: IParser P.RawPattern+patternConstructor = do+ v <- List.intercalate "." <$> dotSep1 capVar+ case v of+ "True" -> return $ P.Literal (L.Boolean True)+ "False" -> return $ P.Literal (L.Boolean False)+ _ -> P.Data (Var.Raw v) <$> spacePrefix term++expr :: IParser P.RawPattern+expr = do+ patterns <- consSep1 (patternConstructor <|> term)+ asPattern (foldr1 P.cons patterns) <?> "pattern"
+ src/Parse/Type.hs view
@@ -0,0 +1,91 @@+{-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}+module Parse.Type where++import Control.Applicative ((<$>),(<*>),(<*))+import Data.List (intercalate)+import Text.Parsec ((<|>), (<?>), char, many, optionMaybe, string, try)++import qualified AST.Type as T+import qualified AST.Variable as Var+import Parse.Helpers+++tvar :: IParser T.RawType+tvar =+ T.Var <$> lowVar <?> "type variable"+++tuple :: IParser T.RawType+tuple =+ do ts <- parens (commaSep expr)+ case ts of+ [t] -> return t+ _ -> return (T.tupleOf ts)+++record :: IParser T.RawType+record =+ do char '{'+ whitespace+ rcrd <- extended <|> normal+ dumbWhitespace+ char '}'+ return rcrd+ where+ normal = flip T.Record Nothing <$> commaSep field++ -- extended record types require at least one field+ extended = do+ ext <- try (lowVar <* (whitespace >> string "|"))+ whitespace+ flip T.Record (Just (T.Var ext)) <$> commaSep1 field++ field = do+ lbl <- rLabel+ whitespace >> hasType >> whitespace+ (,) lbl <$> expr+++capTypeVar :: IParser String+capTypeVar =+ intercalate "." <$> dotSep1 capVar+++constructor0 :: IParser T.RawType+constructor0 =+ do name <- capTypeVar+ return (T.Type (Var.Raw name))+++term :: IParser T.RawType+term =+ tuple <|> record <|> tvar <|> constructor0+++app :: IParser T.RawType+app =+ do f <- constructor0 <|> try tupleCtor <?> "type constructor"+ args <- spacePrefix term+ case args of+ [] -> return f+ _ -> return (T.App f args)+ where+ tupleCtor = do+ n <- length <$> parens (many (char ','))+ let ctor = "_Tuple" ++ show (if n == 0 then 0 else n+1)+ return (T.Type (Var.Raw ctor))+++expr :: IParser T.RawType+expr =+ do t1 <- app <|> term+ arr <- optionMaybe $ try (whitespace >> arrow)+ case arr of+ Just _ -> T.Lambda t1 <$> (whitespace >> expr)+ Nothing -> return t1+++constructor :: IParser (String, [T.RawType])+constructor =+ (,) <$> (capTypeVar <?> "another type constructor")+ <*> spacePrefix term
+ src/Transform/AddDefaultImports.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE FlexibleContexts #-}+module Transform.AddDefaultImports (add, defaultImports) where++import Prelude hiding (maybe)+import qualified Data.Map as Map++import qualified AST.Module as Module+import qualified AST.Variable as Var+++type ImportDict =+ Map.Map Module.Name ([String], Var.Listing Var.Value)+++-- DESCRIPTION OF DEFAULT IMPORTS++(==>) :: a -> b -> (a,b)+(==>) = (,)+++defaultImports :: ImportDict+defaultImports =+ Map.fromList+ [ ["Basics"] ==> ([], Var.openListing)+ , ["Maybe"] ==> ([], Var.Listing [maybe] False)+ , ["Result"] ==> ([], Var.Listing [result] False)+ , ["Signal"] ==> ([], Var.Listing [signal] False)+ ]+++maybe :: Var.Value+maybe =+ Var.Union "Maybe" Var.openListing+++result :: Var.Value+result =+ Var.Union "Result" Var.openListing+++signal :: Var.Value+signal =+ Var.Union "Signal" (Var.Listing [] False)+++-- ADDING DEFAULT IMPORTS TO A MODULE++add :: Bool -> Module.Module exs body -> Module.Module exs body+add needsDefaults (Module.Module moduleName path exports imports decls) =+ Module.Module moduleName path exports ammendedImports decls+ where+ ammendedImports =+ importDictToList $+ foldr addImport (if needsDefaults then defaultImports else Map.empty) imports+++importDictToList :: ImportDict -> [(Module.Name, Module.ImportMethod)]+importDictToList dict =+ concatMap toImports (Map.toList dict)+ where+ toImports (name, (qualifiers, listing@(Var.Listing explicits open))) =+ map (\qualifier -> (name, Module.As qualifier)) qualifiers+ +++ if open || not (null explicits)+ then [(name, Module.Open listing)]+ else []+++addImport :: (Module.Name, Module.ImportMethod) -> ImportDict -> ImportDict+addImport (name, method) importDict =+ Map.alter mergeMethods name importDict+ where+ mergeMethods :: Maybe ([String], Var.Listing Var.Value)+ -> Maybe ([String], Var.Listing Var.Value)+ mergeMethods entry =+ let (qualifiers, listing) =+ case entry of+ Nothing -> ([], Var.Listing [] False)+ Just v -> v+ in+ case method of+ Module.As qualifier ->+ Just (qualifier : qualifiers, listing)++ Module.Open newListing ->+ Just (qualifiers, newListing)
+ src/Transform/Canonicalize.hs view
@@ -0,0 +1,305 @@+module Transform.Canonicalize (module') where++import Control.Applicative ((<$>),(<*>))+import Control.Monad.Error (runErrorT, throwError)+import Control.Monad.State (runState)+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Traversable as T++import AST.Expression.General (Expr'(..), dummyLet)+import qualified AST.Expression.Valid as Valid+import qualified AST.Expression.Canonical as Canonical++import AST.Module (CanonicalBody(..))+import qualified AST.Module as Module+import qualified AST.Type as Type+import qualified AST.Variable as Var+import qualified AST.Annotation as A+import qualified AST.Declaration as D+import AST.PrettyPrint (pretty, commaSep)+import qualified AST.Pattern as P+import Text.PrettyPrint as P++import Transform.Canonicalize.Environment as Env+import qualified Transform.Canonicalize.Setup as Setup+import qualified Transform.Canonicalize.Type as Canonicalize+import qualified Transform.Canonicalize.Variable as Canonicalize+import qualified Transform.SortDefinitions as Transform+import qualified Transform.Declaration as Transform+++module' :: Module.Interfaces -> Module.ValidModule -> Either [Doc] Module.CanonicalModule+module' interfaces modul =+ filterImports <$> modul'+ where+ (modul', usedModules) =+ runState (runErrorT (moduleHelp interfaces modul)) Set.empty++ filterImports m =+ let used (name,_) = Set.member name usedModules+ in m { Module.imports = filter used (Module.imports m) }+++moduleHelp :: Module.Interfaces -> Module.ValidModule+ -> Canonicalizer [Doc] Module.CanonicalModule+moduleHelp interfaces modul@(Module.Module _ _ exports _ decls) =+ do env <- Setup.environment interfaces modul+ canonicalDecls <- mapM (declaration env) decls+ exports' <- resolveExports locals exports+ return $ modul+ { Module.exports = exports'+ , Module.body = body canonicalDecls+ }+ where+ locals :: [Var.Value]+ locals = concatMap declToValue decls++ body :: [D.CanonicalDecl] -> Module.CanonicalBody+ body decls =+ Module.CanonicalBody+ { program =+ let expr = Transform.toExpr (Module.names modul) decls+ in Transform.sortDefs (dummyLet expr)+ , types = Map.empty+ , datatypes =+ Map.fromList [ (name,(vars,ctors)) | D.Datatype name vars ctors <- decls ]+ , fixities =+ [ (assoc,level,op) | D.Fixity assoc level op <- decls ]+ , aliases =+ Map.fromList [ (name,(tvs,alias)) | D.TypeAlias name tvs alias <- decls ]+ , ports =+ [ D.portName port | D.Port port <- decls ]+ }+++resolveExports :: [Var.Value] -> Var.Listing Var.Value -> Canonicalizer [Doc] [Var.Value]+resolveExports fullList (Var.Listing partialList open) =+ if open+ then return fullList+ else do+ valueExports <- getValueExports+ aliasExports <- concat `fmap` mapM getAliasExport aliases+ adtExports <- mapM getAdtExport adts+ return $ valueExports ++ aliasExports ++ adtExports+ where+ (allValues, allAliases, allAdts) = splitValues fullList++ (values, aliases, adts) = splitValues partialList++ getValueExports =+ case Set.toList (Set.difference values' allValues') of+ [] -> return (map Var.Value values)+ xs -> notFound xs+ where+ allValues' = Set.fromList allValues+ values' = Set.fromList values++ getAliasExport alias+ | alias `elem` allAliases =+ let recordConstructor =+ if alias `elem` allValues then [Var.Value alias] else []+ in+ return $ Var.Alias alias : recordConstructor++ | otherwise =+ case List.find (\(name, _ctors) -> name == alias) allAdts of+ Nothing -> notFound [alias]+ Just (name, _ctor) ->+ return [Var.Union name (Var.Listing [] False)]++ getAdtExport (name, Var.Listing ctors open) =+ case lookup name allAdts of+ Nothing -> notFound [name]+ Just (Var.Listing allCtors _)+ | open -> return $ Var.Union name (Var.Listing allCtors False)+ | otherwise ->+ case filter (`notElem` allCtors) ctors of+ [] -> return $ Var.Union name (Var.Listing ctors False)+ unfoundCtors -> notFound unfoundCtors++ notFound xs =+ throwError [ P.text "Export Error: trying to export non-existent values:" <+>+ commaSep (map pretty xs)+ ]+++{-| Split a list of values into categories so we can work with them+independently.+-}+splitValues :: [Var.Value] -> ([String], [String], [(String, Var.Listing String)])+splitValues mixedValues =+ case mixedValues of+ [] -> ([], [], [])+ x:xs ->+ let (values, aliases, adts) = splitValues xs in+ case x of+ Var.Value name ->+ (name : values, aliases, adts)++ Var.Alias name ->+ (values, name : aliases, adts)++ Var.Union name listing ->+ (values, aliases, (name, listing) : adts)++++declToValue :: D.ValidDecl -> [Var.Value]+declToValue decl =+ case decl of+ D.Definition (Valid.Definition pattern _ _) ->+ map Var.Value (P.boundVarList pattern)++ D.Datatype name _tvs ctors ->+ [ Var.Union name (Var.Listing (map fst ctors) False) ]++ D.TypeAlias name _ tipe ->+ case tipe of+ Type.Record _ _ ->+ [ Var.Alias name, Var.Value name ]+ _ -> [ Var.Alias name ]++ _ -> []+++declaration :: Environment -> D.ValidDecl -> Canonicalizer [Doc] D.CanonicalDecl+declaration env decl =+ let canonicalize kind context pattern env v =+ let ctx = P.text ("Error in " ++ context) <+> pretty pattern <> P.colon+ throw err = P.vcat [ ctx, P.text err ]+ in + Env.onError throw (kind env v)+ in+ case decl of+ D.Definition (Valid.Definition p e t) ->+ do p' <- canonicalize pattern "definition" p env p+ e' <- expression env e+ t' <- T.traverse (canonicalize Canonicalize.tipe "definition" p env) t+ return $ D.Definition (Canonical.Definition p' e' t')++ D.Datatype name tvars ctors ->+ D.Datatype name tvars <$> mapM canonicalize' ctors+ where+ canonicalize' (ctor,args) =+ (,) ctor <$> mapM (canonicalize Canonicalize.tipe "datatype" name env) args++ D.TypeAlias name tvars expanded ->+ do expanded' <- canonicalize Canonicalize.tipe "type alias" name env expanded+ return (D.TypeAlias name tvars expanded')++ D.Port port ->+ do Env.uses ["Native","Ports"]+ Env.uses ["Native","Json"]+ D.Port <$>+ case port of+ D.In name t ->+ do t' <- canonicalize Canonicalize.tipe "port" name env t+ return (D.In name t')+ D.Out name e t ->+ do e' <- expression env e+ t' <- canonicalize Canonicalize.tipe "port" name env t+ return (D.Out name e' t')++ D.Fixity assoc prec op ->+ return $ D.Fixity assoc prec op+++expression :: Environment -> Valid.Expr -> Canonicalizer [Doc] Canonical.Expr+expression env (A.A ann expr) =+ let go = expression env+ tipe' environ = format . Canonicalize.tipe environ+ throw err =+ P.vcat+ [ P.text "Error" <+> pretty ann <> P.colon+ , P.text err+ ]+ format = Env.onError throw+ in+ A.A ann <$>+ case expr of+ Literal lit ->+ return (Literal lit)++ Range e1 e2 ->+ Range <$> go e1 <*> go e2++ Access e x ->+ Access <$> go e <*> return x++ Remove e x ->+ flip Remove x <$> go e++ Insert e x v ->+ flip Insert x <$> go e <*> go v++ Modify e fs ->+ Modify <$> go e <*> mapM (\(k,v) -> (,) k <$> go v) fs++ Record fs ->+ Record <$> mapM (\(k,v) -> (,) k <$> go v) fs++ Binop (Var.Raw op) e1 e2 ->+ do op' <- format (Canonicalize.variable env op)+ Binop op' <$> go e1 <*> go e2++ Lambda p e ->+ let env' = update p env in+ Lambda <$> format (pattern env' p) <*> expression env' e++ App e1 e2 ->+ App <$> go e1 <*> go e2++ MultiIf ps ->+ MultiIf <$> mapM go' ps+ where+ go' (b,e) = (,) <$> go b <*> go e++ Let defs e ->+ Let <$> mapM rename' defs <*> expression env' e+ where+ env' = foldr update env $ map (\(Valid.Definition p _ _) -> p) defs+ rename' (Valid.Definition p body mtipe) =+ Canonical.Definition+ <$> format (pattern env' p)+ <*> expression env' body+ <*> T.traverse (tipe' env') mtipe++ Var (Var.Raw x) ->+ Var <$> format (Canonicalize.variable env x)++ Data name es ->+ Data name <$> mapM go es++ ExplicitList es ->+ ExplicitList <$> mapM go es++ Case e cases ->+ Case <$> go e <*> mapM branch cases+ where+ branch (p,b) =+ (,) <$> format (pattern env p)+ <*> expression (update p env) b++ PortIn name st ->+ PortIn name <$> tipe' env st++ PortOut name st signal ->+ PortOut name <$> tipe' env st <*> go signal++ GLShader uid src tipe ->+ return (GLShader uid src tipe)+++pattern :: Environment -> P.RawPattern -> Canonicalizer String P.CanonicalPattern+pattern env ptrn =+ case ptrn of+ P.Var x -> return $ P.Var x+ P.Literal lit -> return $ P.Literal lit+ P.Record fs -> return $ P.Record fs+ P.Anything -> return P.Anything+ P.Alias x p -> P.Alias x <$> pattern env p+ P.Data (Var.Raw name) ps ->+ P.Data <$> Canonicalize.pvar env name+ <*> mapM (pattern env) ps
+ src/Transform/Canonicalize/Environment.hs view
@@ -0,0 +1,91 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Canonicalize.Environment where++import Control.Arrow (second)+import qualified Control.Monad.Error as Error+import qualified Control.Monad.State as State+import qualified Data.Map as Map+import qualified Data.Set as Set++import AST.Expression.General (saveEnvName)+import qualified AST.Module as Module+import qualified AST.Pattern as P+import qualified AST.Type as Type+import qualified AST.Variable as Var++import Text.PrettyPrint (Doc)+++type Dict a = Map.Map String [a]++dict :: [(String,a)] -> Dict a+dict pairs =+ Map.fromList $ map (second (:[])) pairs++insert :: String -> a -> Dict a -> Dict a+insert key value =+ Map.insertWith (++) key [value]+++type Canonicalizer err a =+ Error.ErrorT err (State.State (Set.Set Module.Name)) a++uses :: (Error.Error e) => Module.Name -> Canonicalizer e ()+uses home =+ Error.lift (State.modify (Set.insert home))++using :: Var.Canonical -> Canonicalizer String Var.Canonical+using var@(Var.Canonical home _) =+ do case home of+ Var.BuiltIn -> return ()+ Var.Module path -> uses path+ Var.Local -> return ()+ return var++onError :: (String -> Doc) -> Canonicalizer String a -> Canonicalizer [Doc] a+onError handler canonicalizer =+ do usedModules <- Error.lift State.get+ let (result, usedModules') =+ State.runState (Error.runErrorT canonicalizer) usedModules+ Error.lift (State.put usedModules')+ case result of+ Left err -> Error.throwError [handler err]+ Right x -> return x+++data Environment = Env+ { _home :: Module.Name+ , _values :: Dict Var.Canonical+ , _adts :: Dict Var.Canonical+ , _aliases :: Dict (Var.Canonical, [String], Type.CanonicalType)+ , _patterns :: Dict Var.Canonical+ }++builtIns :: Module.Name -> Environment+builtIns home =+ Env { _home = home+ , _values = builtIn (tuples ++ [saveEnvName])+ , _adts = builtIn (tuples ++ ["List","Int","Float","Char","Bool","String"])+ , _aliases = dict []+ , _patterns = builtIn (tuples ++ ["::","[]"])+ }+ where+ builtIn xs = dict $ map (\x -> (x, Var.Canonical Var.BuiltIn x)) xs+ tuples = map (\n -> "_Tuple" ++ show (n :: Int)) [0..9]++update :: P.Pattern var -> Environment -> Environment+update ptrn env =+ env { _values = foldr put (_values env) (P.boundVarList ptrn) }+ where+ put x = Map.insert x [Var.local x]++merge :: Environment -> Environment -> Environment+merge (Env n1 v1 t1 a1 p1) (Env n2 v2 t2 a2 p2)+ | n1 /= n2 = error "trying to merge incompatable environments"+ | otherwise =+ Env { _home = n1+ , _values = Map.unionWith (++) v1 v2+ , _adts = Map.unionWith (++) t1 t2+ , _aliases = Map.unionWith (++) a1 a2+ , _patterns = Map.unionWith (++) p1 p2+ }
+ src/Transform/Canonicalize/Setup.hs view
@@ -0,0 +1,269 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Canonicalize.Setup+ ( environment+ , typeAliasErrorSegue+ , typeAliasErrorExplanation+ ) where++import Control.Arrow (first)+import Control.Monad (foldM)+import Control.Monad.Error (throwError)+import qualified Data.Graph as Graph+import qualified Data.List as List+import qualified Data.Map as Map++import qualified AST.Expression.Valid as Valid++import AST.Module (Interface(iAdts, iTypes, iAliases))+import qualified AST.Module as Module+import qualified AST.Type as Type+import qualified AST.Variable as Var+import qualified AST.Declaration as D+import AST.PrettyPrint (pretty, eightyCharLines)+import qualified AST.Pattern as P+import Text.PrettyPrint as P++import Transform.Canonicalize.Environment as Env+import qualified Transform.Canonicalize.Type as Canonicalize+import qualified Transform.Interface as Interface+++environment :: Module.Interfaces -> Module.ValidModule -> Canonicalizer [Doc] Environment+environment interfaces modul@(Module.Module _ _ _ imports decls) =+ do () <- allImportsAvailable+ let moduleName = Module.names modul+ nonLocalEnv <- foldM (addImports moduleName interfaces) (builtIns moduleName) imports+ let (aliases, env) = List.foldl' (addDecl moduleName) ([], nonLocalEnv) decls+ addTypeAliases moduleName aliases env+ where+ allImportsAvailable :: Canonicalizer [Doc] ()+ allImportsAvailable =+ case filter (not . found) modules of+ [] -> return ()+ missings -> throwError [ P.text (missingModuleError missings) ]+ where+ modules = map fst imports++ found m = Map.member m interfaces || Module.nameIsNative m++ missingModuleError missings =+ concat [ "The following imports were not found:\n "+ , List.intercalate ", " (map Module.nameToString missings)+ ]+++addImports :: Module.Name -> Module.Interfaces -> Environment -> (Module.Name, Module.ImportMethod)+ -> Canonicalizer [Doc] Environment+addImports moduleName interfaces environ (name, method)+ | Module.nameIsNative name = return environ+ | otherwise =+ case method of+ Module.As name' ->+ return (updateEnviron (name' ++ "."))++ Module.Open (Var.Listing vs open)+ | open -> return (updateEnviron "")+ | otherwise -> foldM (addValue name interface) environ vs+ where+ interface = Interface.filterExports ((Map.!) interfaces name)++ updateEnviron prefix =+ let dict' = dict . map (first (prefix++)) in+ merge environ $+ Env { _home = moduleName+ , _values = dict' $ map pair (Map.keys (iTypes interface)) ++ ctors+ , _adts = dict' $ map pair (Map.keys (iAdts interface))+ , _aliases = dict' $ map alias (Map.toList (iAliases interface))+ , _patterns = dict' $ ctors+ }++ canonical :: String -> Var.Canonical+ canonical = Var.Canonical (Var.Module name)++ pair :: String -> (String, Var.Canonical)+ pair key = (key, canonical key)++ alias (x,(tvars,tipe)) = (x, (canonical x, tvars, tipe))++ ctors = concatMap (map (pair . fst) . snd . snd) (Map.toList (iAdts interface))++addValue :: Module.Name -> Module.Interface -> Environment -> Var.Value+ -> Canonicalizer [Doc] Environment+addValue moduleName interface env value =+ let name = Module.nameToString moduleName+ insert' x = insert x (Var.Canonical (Var.Module moduleName) x)+ msg x = "Import Error: Could not import value '" ++ name ++ "." ++ x +++ "'.\n It is not exported by module " ++ name ++ "."+ notFound x = throwError [ P.text (msg x) ]+ in+ case value of+ Var.Value x+ | Map.notMember x (iTypes interface) -> notFound x+ | otherwise ->+ return $ env { _values = insert' x (_values env) }++ Var.Alias x ->+ case Map.lookup x (iAliases interface) of+ Just (tvars, t) ->+ return $ env+ { _aliases = insert x v (_aliases env)+ , _values = updatedValues+ }+ where+ v = (Var.Canonical (Var.Module moduleName) x, tvars, t)+ updatedValues =+ if Map.member x (iTypes interface)+ then insert' x (_values env)+ else _values env++ Nothing ->+ case Map.lookup x (iAdts interface) of+ Nothing -> notFound x+ Just (_,_) ->+ return $ env { _adts = insert' x (_adts env) }++ Var.Union x (Var.Listing xs open) ->+ case Map.lookup x (iAdts interface) of+ Nothing -> notFound x+ Just (_tvars, ctors) ->+ do ctors' <- filterNames (map fst ctors)+ return $ env { _adts = insert' x (_adts env)+ , _values = foldr insert' (_values env) ctors'+ , _patterns = foldr insert' (_patterns env) ctors'+ }+ where+ filterNames names+ | open = return names+ | otherwise =+ case filter (`notElem` names) xs of+ [] -> return names+ c:_ -> notFound c++type Node = ((String, [String], Type.RawType), String, [String])++node :: String -> [String] -> Type.RawType -> Node+node name tvars alias = ((name, tvars, alias), name, edges alias)+ where+ edges tipe =+ case tipe of+ Type.Lambda t1 t2 -> edges t1 ++ edges t2+ Type.Var _ -> []+ Type.Type (Var.Raw x) -> [x]+ Type.App t ts -> edges t ++ concatMap edges ts+ Type.Record fs ext -> maybe [] edges ext ++ concatMap (edges . snd) fs+ Type.Aliased _ t -> edges t+++addTypeAliases+ :: Module.Name+ -> [Node]+ -> Environment+ -> Canonicalizer [Doc] Environment+addTypeAliases moduleName nodes environ =+ foldM (addTypeAlias moduleName) environ (Graph.stronglyConnComp nodes)+++addTypeAlias+ :: Module.Name+ -> Environment+ -> Graph.SCC (String, [String], Type.RawType)+ -> Canonicalizer [Doc] Environment+addTypeAlias moduleName env scc =+ case Graph.flattenSCC scc of+ [(name, tvars, alias)] ->+ do alias' <- Env.onError throw (Canonicalize.tipe env alias)+ let value = (Var.Canonical (Var.Module moduleName) name, tvars, alias')+ return $ env { _aliases = insert name value (_aliases env) }+ where+ throw err =+ let msg = "Problem with type alias '" ++ name ++ "':"+ in P.vcat [ P.text msg, P.text err ]++ aliases ->+ throwError + [ P.vcat+ [ P.text (eightyCharLines 0 mutuallyRecursiveMessage)+ , indented (map typeAlias aliases)+ , P.text (eightyCharLines 0 typeAliasErrorSegue)+ , indented (map datatype aliases)+ , P.text (eightyCharLines 0 typeAliasErrorExplanation)+ ]+ ]+++typeAlias+ :: (String, [String], Type.Type var)+ -> D.Declaration' port def var+typeAlias (n,ts,t) =+ D.TypeAlias n ts t+++datatype+ :: (String, [String], Type.Type var)+ -> D.Declaration' port def var+datatype (n,ts,t) =+ D.Datatype n ts [(n,[t])]+++indented :: [D.ValidDecl] -> Doc+indented decls = P.vcat (map prty decls) <> P.text "\n"+ where+ prty decl = P.text "\n " <> pretty decl+++mutuallyRecursiveMessage :: String+mutuallyRecursiveMessage =+ "The following type aliases are mutually recursive, forming an \+ \infinite type. When you expand them, they just keep getting bigger:"+++typeAliasErrorSegue :: String+typeAliasErrorSegue =+ "Try this instead:"+++typeAliasErrorExplanation :: String+typeAliasErrorExplanation =+ "It looks very similar, but the 'type' keyword creates a brand new type, \+ \not just an alias for an existing one. This lets us avoid infinitely \+ \expanding it during type inference."+++-- When canonicalizing, all _values should be Local, but all _adts and _patterns+-- should be fully namespaced. With _adts, they may appear in types that can+-- escape the module.+addDecl :: Module.Name -> ([Node], Environment) -> D.ValidDecl -> ([Node], Environment)+addDecl moduleName info@(nodes,env) decl =+ let namespacedVar = Var.Canonical (Var.Module moduleName)+ addLocal x e = insert x (Var.local x) e+ addNamespaced x e = insert x (namespacedVar x) e+ in+ case decl of+ D.Definition (Valid.Definition pattern _ _) ->+ (,) nodes $ env+ { _values = foldr addLocal (_values env) (P.boundVarList pattern) }++ D.Datatype name _ ctors ->+ (,) nodes $ env+ { _values = addCtors addLocal (_values env)+ , _adts = addNamespaced name (_adts env)+ , _patterns = addCtors addNamespaced (_patterns env)+ }+ where+ addCtors how e = foldr how e (map fst ctors)++ D.TypeAlias name tvars alias ->+ (,) (node name tvars alias : nodes) $ env+ { _values = case alias of+ Type.Record _ _ -> addLocal name (_values env)+ _ -> _values env+ }++ D.Port port ->+ let portName = case port of+ D.Out name _ _ -> name+ D.In name _ -> name+ in+ (,) nodes $ env { _values = addLocal portName (_values env) }++ D.Fixity _ _ _ -> info
+ src/Transform/Canonicalize/Type.hs view
@@ -0,0 +1,96 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Canonicalize.Type (tipe) where++import Control.Arrow (second)+import Control.Applicative ((<$>),(<*>))+import Control.Monad.Error+import qualified Data.Map as Map+import Data.Traversable (traverse)++import qualified AST.Type as T+import qualified AST.Variable as Var++import Transform.Canonicalize.Environment+import qualified Transform.Canonicalize.Variable as Canonicalize+++tipe+ :: Environment+ -> T.RawType+ -> Canonicalizer String T.CanonicalType+tipe env typ =+ let go = tipe env in+ case typ of+ T.Var x ->+ return (T.Var x)++ T.Type _ ->+ canonicalizeApp env typ []++ T.App t ts ->+ canonicalizeApp env t ts++ T.Lambda a b ->+ T.Lambda <$> go a <*> go b++ T.Aliased name t ->+ T.Aliased name <$> go t++ T.Record fields ext ->+ let go' (f,t) = (,) f <$> go t+ in+ T.Record <$> mapM go' fields <*> traverse go ext+++canonicalizeApp+ :: Environment+ -> T.RawType+ -> [T.RawType]+ -> Canonicalizer String T.CanonicalType+canonicalizeApp env f args =+ case f of+ T.Type (Var.Raw rawName) ->+ do answer <- Canonicalize.tvar env rawName+ case answer of+ Right alias ->+ canonicalizeAlias env alias args++ Left name ->+ case args of+ [] -> return (T.Type name)+ _ ->+ T.App (T.Type name) <$> mapM (tipe env) args++ _ ->+ T.App <$> tipe env f <*> mapM (tipe env) args+++canonicalizeAlias+ :: Environment+ -> (Var.Canonical, [String], T.CanonicalType)+ -> [T.RawType]+ -> Canonicalizer String T.CanonicalType+canonicalizeAlias env (name, tvars, dealiasedTipe) tipes =+ do when (tipesLen /= tvarsLen) (throwError msg)+ tipes' <- mapM (tipe env) tipes+ let tipe' = replace (Map.fromList (zip tvars tipes')) dealiasedTipe+ return $ T.Aliased name tipe'+ where+ tipesLen = length tipes+ tvarsLen = length tvars++ msg :: String+ msg = "Type alias '" ++ Var.toString name ++ "' expects " ++ show tvarsLen +++ " type argument" ++ (if tvarsLen == 1 then "" else "s") +++ " but was given " ++ show tipesLen++ replace :: Map.Map String T.CanonicalType -> T.CanonicalType -> T.CanonicalType+ replace typeTable t =+ let go = replace typeTable in+ case t of+ T.Lambda a b -> T.Lambda (go a) (go b)+ T.Var x -> Map.findWithDefault t x typeTable+ T.Record fields ext -> T.Record (map (second go) fields) (fmap go ext)+ T.Aliased original t' -> T.Aliased original (go t')+ T.Type _ -> t+ T.App f args -> T.App (go f) (map go args)
+ src/Transform/Canonicalize/Variable.hs view
@@ -0,0 +1,95 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Canonicalize.Variable where++import Control.Monad.Error+import qualified Data.List as List+import qualified Data.Map as Map+import Data.Maybe (fromMaybe)++import AST.Helpers as Help+import qualified AST.Module as Module+import qualified AST.Type as Type+import qualified AST.Variable as Var+import Transform.Canonicalize.Environment as Env++variable :: Environment -> String -> Canonicalizer String Var.Canonical+variable env var =+ case modul of+ Just name+ | Module.nameIsNative name ->+ Env.using (Var.Canonical (Var.Module name) varName)++ _ ->+ case Map.lookup var (_values env) of+ Just [v] -> Env.using v+ Just vs -> preferLocals env "variable" vs var+ Nothing -> notFound "variable" (Map.keys (_values env)) var+ where+ (modul, varName) =+ case Help.splitDots var of+ [x] -> (Nothing, x)+ xs -> (Just (init xs), last xs)++tvar :: Environment -> String+ -> Canonicalizer String (Either Var.Canonical (Var.Canonical, [String], Type.CanonicalType))+tvar env var =+ case adts ++ aliases of+ [] -> notFound "type" (Map.keys (_adts env) ++ Map.keys (_aliases env)) var+ [v] -> found extract v+ vs -> preferLocals' env extract "type" vs var+ where+ adts = map Left . fromMaybe [] $ Map.lookup var (_adts env)+ aliases = map Right . fromMaybe [] $ Map.lookup var (_aliases env)++ extract value =+ case value of+ Left v -> v+ Right (v,_,_) -> v++pvar :: Environment -> String -> Canonicalizer String Var.Canonical+pvar env var =+ case Map.lookup var (_patterns env) of+ Just [v] -> Env.using v+ Just vs -> preferLocals env "pattern" vs var+ Nothing -> notFound "pattern" (Map.keys (_patterns env)) var++found :: (a -> Var.Canonical) -> a -> Canonicalizer String a+found extract v = do+ _ <- Env.using (extract v)+ return v++notFound :: String -> [String] -> String -> Canonicalizer String a+notFound kind possibilities var =+ throwError $ "Could not find " ++ kind ++ " '" ++ var ++ "'." ++ msg+ where+ matches = filter (List.isInfixOf var) possibilities+ msg = if null matches then "" else+ "\nClose matches include: " ++ List.intercalate ", " matches++preferLocals :: Environment -> String -> [Var.Canonical] -> String+ -> Canonicalizer String Var.Canonical+preferLocals env = preferLocals' env id++preferLocals' :: Environment -> (a -> Var.Canonical) -> String -> [a] -> String+ -> Canonicalizer String a+preferLocals' env extract kind possibilities var =+ case filter (isLocal . extract) possibilities of+ [] -> ambiguous possibilities+ [v] -> found extract v+ locals -> ambiguous locals+ where+ isLocal :: Var.Canonical -> Bool+ isLocal (Var.Canonical home _) =+ case home of+ Var.Local -> True+ Var.BuiltIn -> False+ Var.Module name ->+ name == Env._home env++ ambiguous possibleVars =+ throwError msg+ where+ vars = map (Var.toString . extract) possibleVars+ msg = "Ambiguous usage of " ++ kind ++ " '" ++ var ++ "'.\n" +++ " Disambiguate between: " ++ List.intercalate ", " vars+
+ src/Transform/Check.hs view
@@ -0,0 +1,177 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Check (mistakes) where++import qualified Control.Arrow as Arrow+import qualified Data.List as List+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set++import qualified AST.Expression.Valid as Valid+import qualified AST.Declaration as D+import qualified AST.Pattern as Pattern+import qualified AST.Type as T+import qualified AST.Variable as Var+import qualified Transform.Expression as Expr+import qualified Transform.Canonicalize.Setup as Setup++import AST.PrettyPrint+import Elm.Utils ((|>))+import Text.PrettyPrint as P+++mistakes :: [D.ValidDecl] -> [Doc]+mistakes decls =+ concat+ [ infiniteTypeAliases decls+ , illFormedTypes decls+ , map P.text (duplicateTypeDeclarations decls)+ , map P.text (duplicateValues decls)+ ]+++dups :: Ord a => [a] -> [a]+dups names =+ List.sort names+ |> List.group+ |> filter ((>1) . length)+ |> map head+++duplicateValues :: [D.ValidDecl] -> [String]+duplicateValues decls =+ map msg (dups (portNames ++ concatMap Pattern.boundVarList defPatterns)) +++ case mapM exprDups (portExprs ++ defExprs) of+ Left name -> [msg name]+ Right _ -> []++ where+ msg x =+ "Name Collision: There can only be one definition of '" ++ x ++ "'."++ (defPatterns, defExprs) =+ unzip [ (pat,expr) | D.Definition (Valid.Definition pat expr _) <- decls ]++ (portNames, portExprs) =+ Arrow.second concat $ unzip $ + flip map [ port | D.Port port <- decls ] $ \port ->+ case port of+ D.Out name expr _ -> (name, [expr])+ D.In name _ -> (name, [])++ exprDups :: Valid.Expr -> Either String Valid.Expr+ exprDups expr = Expr.crawlLet defsDups expr++ defsDups :: [Valid.Def] -> Either String [Valid.Def]+ defsDups defs =+ let varsIn (Valid.Definition pattern _ _) = Pattern.boundVarList pattern in+ case dups $ concatMap varsIn defs of+ [] -> Right defs+ name:_ -> Left name+++duplicateTypeDeclarations :: [D.ValidDecl] -> [String]+duplicateTypeDeclarations decls = + map dupTypeError (dups (typeNames ++ aliasNames))+ ++ map dupCtorError (dups (ctorNames ++ aliasNames))+ where+ typeNames =+ [ name | D.Datatype name _ _ <- decls ]++ aliasNames =+ [ name | D.TypeAlias name _ _ <- decls ]++ ctorNames =+ concat [ map fst patterns | D.Datatype _ _ patterns <- decls ]+++dupTypeError :: String -> String+dupTypeError name =+ "Name Collision: There can only be one type named '" ++ name ++ "'"+++dupCtorError :: String -> String+dupCtorError name =+ "Name Collision: There can only be one constructor named '" ++ name ++ "'.\n"+ ++ " Constructors are created for record type aliases and for union types, so\n"+ ++ " something should be renamed or moved to a different module."+++illFormedTypes :: [D.ValidDecl] -> [Doc]+illFormedTypes decls = map report (Maybe.mapMaybe isIllFormed (aliases ++ adts))+ where+ aliases = [ (decl, tvars, [tipe]) | decl@(D.TypeAlias _ tvars tipe) <- decls ]+ adts = [ (decl, tvars, concatMap snd ctors) | decl@(D.Datatype _ tvars ctors) <- decls ]++ freeVars tipe =+ case tipe of+ T.Lambda t1 t2 -> Set.union (freeVars t1) (freeVars t2)+ T.Var x -> Set.singleton x+ T.Type _ -> Set.empty+ T.App t ts -> Set.unions (map freeVars (t:ts))+ T.Record fields ext -> Set.unions (ext' : map (freeVars . snd) fields)+ where ext' = maybe Set.empty freeVars ext+ T.Aliased _ t -> freeVars t++ undeclared tvars tipes = Set.difference used declared+ where+ used = Set.unions (map freeVars tipes)+ declared = Set.fromList tvars++ isIllFormed (decl, tvars, tipes) =+ let unbound = undeclared tvars tipes in + if Set.null unbound then Nothing+ else Just (decl, Set.toList unbound)++ report (decl, tvars) =+ P.vcat [ P.text $ "Error: type variable" ++ listing ++ " unbound in:"+ , P.text "\n"+ , nest 4 (pretty decl) ]+ where+ listing =+ case tvars of+ [tvar] -> " " ++ quote tvar ++ " is"+ _ -> "s" ++ addCommas (map ((++) " ") (addAnd (map quote tvars))) ++ " are"++ addCommas xs+ | length xs < 3 = concat xs+ | otherwise = List.intercalate "," xs++ addAnd xs+ | length xs < 2 = xs+ | otherwise = zipWith (++) (replicate (length xs - 1) "" ++ ["and "]) xs++ quote tvar = "'" ++ tvar ++ "'"+++infiniteTypeAliases :: [D.ValidDecl] -> [Doc]+infiniteTypeAliases decls =+ [ report name tvars tipe | D.TypeAlias name tvars tipe <- decls+ , infiniteType name tipe ]+ where+ infiniteType :: String -> T.Type Var.Raw -> Bool+ infiniteType name tipe =+ let infinite = infiniteType name in+ case tipe of+ T.Lambda a b -> infinite a || infinite b+ T.Var _ -> False+ T.Type (Var.Raw name') -> name == name'+ T.App t ts -> any infinite (t:ts)+ T.Record fields _ -> any (infinite . snd) fields+ T.Aliased _ t -> infinite t++ indented :: D.ValidDecl -> Doc+ indented decl = P.text "\n " <> pretty decl <> P.text "\n"++ report name args tipe =+ P.vcat+ [ P.text $ eightyCharLines 0 msg1+ , indented $ D.TypeAlias name args tipe+ , P.text $ eightyCharLines 0 Setup.typeAliasErrorSegue+ , indented $ D.Datatype name args [(name,[tipe])]+ , P.text $ eightyCharLines 0 Setup.typeAliasErrorExplanation ++ "\n"+ ]+ where+ msg1 =+ "Type alias '" ++ name ++ "' is an infinite type. " +++ "Notice that it appears in its own definition, so when \+ \you expand it, it just keeps getting bigger:"
+ src/Transform/Declaration.hs view
@@ -0,0 +1,129 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Declaration (combineAnnotations, toExpr) where++import Control.Applicative ((<$>))++import qualified AST.Annotation as A+import qualified AST.Declaration as D+import qualified AST.Expression.General as E+import qualified AST.Expression.Source as Source+import qualified AST.Expression.Valid as Valid+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Module as Module+import qualified AST.Pattern as P+import qualified AST.Type as T+import qualified AST.Variable as Var++import qualified Transform.Expression as Expr+import qualified Transform.Definition as Def+++combineAnnotations :: [D.SourceDecl] -> Either String [D.ValidDecl]+combineAnnotations = go+ where+ msg x = "Syntax Error: The type annotation for '" ++ x +++ "' must be directly above its definition."++ exprCombineAnnotations = Expr.crawlLet Def.combineAnnotations++ go decls =+ case decls of+ -- simple cases, pass them through with no changes+ [] -> return []++ D.Datatype name tvars ctors : rest ->+ (:) (D.Datatype name tvars ctors) <$> go rest++ D.TypeAlias name tvars alias : rest ->+ (:) (D.TypeAlias name tvars alias) <$> go rest++ D.Fixity assoc prec op : rest ->+ (:) (D.Fixity assoc prec op) <$> go rest++ -- combine definitions+ D.Definition def : defRest ->+ case def of+ Source.Definition pat expr ->+ do expr' <- exprCombineAnnotations expr+ let def' = Valid.Definition pat expr' Nothing+ (:) (D.Definition def') <$> go defRest++ Source.TypeAnnotation name tipe ->+ case defRest of+ D.Definition (Source.Definition pat@(P.Var name') expr) : rest+ | name == name' ->+ do expr' <- exprCombineAnnotations expr+ let def' = Valid.Definition pat expr' (Just tipe)+ (:) (D.Definition def') <$> go rest++ _ -> Left (msg name)++ -- combine ports+ D.Port port : portRest ->+ case port of+ D.PPDef name _ -> Left (msg name)+ D.PPAnnotation name tipe ->+ case portRest of+ D.Port (D.PPDef name' expr) : rest | name == name' ->+ do expr' <- exprCombineAnnotations expr+ (:) (D.Port (D.Out name expr' tipe)) <$> go rest++ _ -> (:) (D.Port (D.In name tipe)) <$> go portRest+++toExpr :: Module.Name -> [D.CanonicalDecl] -> [Canonical.Def]+toExpr moduleName = concatMap (toDefs moduleName)++toDefs :: Module.Name -> D.CanonicalDecl -> [Canonical.Def]+toDefs moduleName decl =+ let typeVar = Var.Canonical (Var.Module moduleName) in+ case decl of+ D.Definition def -> [def]++ D.Datatype name tvars constructors -> concatMap toDefs' constructors+ where+ toDefs' (ctor, tipes) =+ let vars = take (length tipes) arguments+ tbody = T.App (T.Type (typeVar name)) (map T.Var tvars)+ body = A.none . E.Data ctor $ map (A.none . E.localVar) vars+ in [ definition ctor (buildFunction body vars) (foldr T.Lambda tbody tipes) ]++ D.TypeAlias name _ tipe@(T.Record fields ext) ->+ [ definition name (buildFunction record vars) (foldr T.Lambda result args) ]+ where+ result = T.Aliased (typeVar name) tipe++ args = map snd fields ++ maybe [] (:[]) ext++ var = A.none . E.localVar+ vars = take (length args) arguments++ efields = zip (map fst fields) (map var vars)+ record = case ext of+ Nothing -> A.none $ E.Record efields+ Just _ -> foldl (\r (f,v) -> A.none $ E.Insert r f v) (var $ last vars) efields++ -- Type aliases must be added to an extended equality dictionary,+ -- but they do not require any basic constraints.+ D.TypeAlias _ _ _ -> []++ D.Port port ->+ case port of+ D.Out name expr@(A.A s _) tipe ->+ [ definition name (A.A s $ E.PortOut name tipe expr) tipe ]+ D.In name tipe ->+ [ definition name (A.none $ E.PortIn name tipe) tipe ]++ -- no constraints are needed for fixity declarations+ D.Fixity _ _ _ -> []+++arguments :: [String]+arguments = map (:[]) ['a'..'z'] ++ map (\n -> "_" ++ show (n :: Int)) [1..]++buildFunction :: Canonical.Expr -> [String] -> Canonical.Expr+buildFunction body@(A.A s _) vars =+ foldr (\p e -> A.A s (E.Lambda p e)) body (map P.Var vars)++definition :: String -> Canonical.Expr -> T.CanonicalType -> Canonical.Def+definition name expr tipe = Canonical.Definition (P.Var name) expr (Just tipe)
+ src/Transform/Definition.hs view
@@ -0,0 +1,35 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Definition where++import Control.Applicative ((<$>))+import qualified AST.Pattern as P+import qualified AST.Expression.Source as Source+import qualified AST.Expression.Valid as Valid+import qualified Transform.Expression as Expr++combineAnnotations :: [Source.Def] -> Either String [Valid.Def]+combineAnnotations = go+ where+ msg x = "Syntax Error: The type annotation for '" ++ x +++ "' must be directly above its definition."++ exprCombineAnnotations = Expr.crawlLet combineAnnotations++ go defs =+ case defs of+ Source.TypeAnnotation name tipe : rest ->+ case rest of+ Source.Definition pat@(P.Var name') expr : rest'+ | name == name' ->+ do expr' <- exprCombineAnnotations expr+ let def = Valid.Definition pat expr' (Just tipe)+ (:) def <$> go rest'++ _ -> Left (msg name)++ Source.Definition pat expr : rest ->+ do expr' <- exprCombineAnnotations expr+ let def = Valid.Definition pat expr' Nothing+ (:) def <$> go rest++ [] -> return []
+ src/Transform/Expression.hs view
@@ -0,0 +1,100 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Expression (crawlLet, checkPorts) where++import Control.Applicative ((<$>),(<*>))+import AST.Annotation ( Annotated(A) )+import AST.Expression.General+import qualified AST.Expression.Canonical as Canonical+import AST.Type (Type, CanonicalType)++crawlLet+ :: ([def] -> Either a [def'])+ -> Expr ann def var+ -> Either a (Expr ann def' var)+crawlLet =+ crawl (\_ _ -> return ()) (\_ _ -> return ())+++checkPorts+ :: (String -> CanonicalType -> Either a ())+ -> (String -> CanonicalType -> Either a ())+ -> Canonical.Expr+ -> Either a Canonical.Expr+checkPorts inCheck outCheck expr =+ crawl inCheck outCheck (mapM checkDef) expr+ where+ checkDef def@(Canonical.Definition _ body _) =+ do _ <- checkPorts inCheck outCheck body+ return def+++crawl+ :: (String -> Type var -> Either a ())+ -> (String -> Type var -> Either a ())+ -> ([def] -> Either a [def'])+ -> Expr ann def var+ -> Either a (Expr ann def' var)+crawl portInCheck portOutCheck defsTransform =+ go+ where+ go (A srcSpan expr) =+ A srcSpan <$>+ case expr of+ Var x ->+ return (Var x)++ Lambda p e ->+ Lambda p <$> go e++ Binop op e1 e2 ->+ Binop op <$> go e1 <*> go e2++ Case e cases ->+ Case <$> go e <*> mapM (\(p,b) -> (,) p <$> go b) cases++ Data name es ->+ Data name <$> mapM go es++ Literal lit ->+ return (Literal lit)++ Range e1 e2 ->+ Range <$> go e1 <*> go e2++ ExplicitList es ->+ ExplicitList <$> mapM go es++ App e1 e2 ->+ App <$> go e1 <*> go e2++ MultiIf branches ->+ MultiIf <$> mapM (\(b,e) -> (,) <$> go b <*> go e) branches++ Access e lbl ->+ Access <$> go e <*> return lbl++ Remove e lbl ->+ Remove <$> go e <*> return lbl++ Insert e lbl v ->+ Insert <$> go e <*> return lbl <*> go v++ Modify e fields ->+ Modify <$> go e <*> mapM (\(k,v) -> (,) k <$> go v) fields++ Record fields ->+ Record <$> mapM (\(k,v) -> (,) k <$> go v) fields++ Let defs body ->+ Let <$> defsTransform defs <*> go body++ GLShader uid src gltipe ->+ return $ GLShader uid src gltipe++ PortIn name st ->+ do portInCheck name st+ return $ PortIn name st++ PortOut name st signal ->+ do portOutCheck name st+ PortOut name st <$> go signal
+ src/Transform/Interface.hs view
@@ -0,0 +1,60 @@+module Transform.Interface (filterExports) where++import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified AST.Module as Module+import qualified AST.Variable as Var+++filterExports :: Module.Interface -> Module.Interface+filterExports interface =+ interface+ { Module.iTypes =+ Map.fromList $+ Maybe.mapMaybe+ (get (Module.iTypes interface))+ (Var.getValues exports ++ ctors)++ , Module.iAliases =+ Map.fromList $+ Maybe.mapMaybe+ (get (Module.iAliases interface))+ (Var.getAliases exports)++ , Module.iAdts =+ Map.fromList+ (Maybe.mapMaybe (trimUnions interface) unions)+ }+ where+ exports :: [Var.Value]+ exports =+ Module.iExports interface++ unions :: [(String, Var.Listing String)]+ unions =+ Var.getUnions exports++ ctors :: [String]+ ctors =+ concatMap (\(_, Var.Listing ctorList _) -> ctorList) unions+++get :: Map.Map String a -> String -> Maybe (String, a)+get dict key =+ case Map.lookup key dict of+ Nothing -> Nothing+ Just value -> Just (key, value)+++trimUnions+ :: Module.Interface+ -> (String, Var.Listing String)+ -> Maybe (String, Module.AdtInfo String)+trimUnions interface (name, Var.Listing exportedCtors _) =+ case Map.lookup name (Module.iAdts interface) of+ Nothing -> Nothing+ Just (tvars, ctors) ->+ let isExported (ctor, _) =+ ctor `elem` exportedCtors+ in+ Just (name, (tvars, filter isExported ctors))
+ src/Transform/SortDefinitions.hs view
@@ -0,0 +1,170 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.SortDefinitions (sortDefs) where++import Control.Monad.State+import Control.Applicative ((<$>),(<*>))+import qualified Data.Graph as Graph+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Set as Set++import AST.Annotation+import AST.Expression.General (Expr'(..))+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Pattern as P+import qualified AST.Variable as V++ctors :: P.CanonicalPattern -> [String]+ctors pattern =+ case pattern of+ P.Var _ -> []+ P.Alias _ p -> ctors p+ P.Record _ -> []+ P.Anything -> []+ P.Literal _ -> []+ P.Data (V.Canonical home name) ps ->+ case home of+ V.Local -> name : rest+ V.BuiltIn -> rest+ V.Module _ -> rest+ where+ rest = concatMap ctors ps++free :: String -> State (Set.Set String) ()+free x = modify (Set.insert x)++freeIfLocal :: V.Canonical -> State (Set.Set String) ()+freeIfLocal (V.Canonical home name) =+ do case home of+ V.Local -> free name+ V.BuiltIn -> return ()+ V.Module _ -> return ()++bound :: Set.Set String -> State (Set.Set String) ()+bound boundVars = modify (\freeVars -> Set.difference freeVars boundVars)++sortDefs :: Canonical.Expr -> Canonical.Expr+sortDefs expr = evalState (reorder expr) Set.empty++reorder :: Canonical.Expr -> State (Set.Set String) Canonical.Expr+reorder (A ann expr) =+ A ann <$>+ case expr of+ -- Be careful adding and restricting freeVars+ Var var -> freeIfLocal var >> return expr++ Lambda p e ->+ uncurry Lambda <$> bindingReorder (p,e)++ Binop op e1 e2 ->+ do freeIfLocal op+ Binop op <$> reorder e1 <*> reorder e2++ Case e cases ->+ Case <$> reorder e <*> mapM bindingReorder cases++ Data name es ->+ do free name+ Data name <$> mapM reorder es++ -- Just pipe the reorder though+ Literal _ -> return expr++ Range e1 e2 ->+ Range <$> reorder e1 <*> reorder e2++ ExplicitList es ->+ ExplicitList <$> mapM reorder es++ App e1 e2 ->+ App <$> reorder e1 <*> reorder e2++ MultiIf branches ->+ MultiIf <$> mapM (\(e1,e2) -> (,) <$> reorder e1 <*> reorder e2) branches++ Access e lbl ->+ Access <$> reorder e <*> return lbl++ Remove e lbl ->+ Remove <$> reorder e <*> return lbl++ Insert e lbl v ->+ Insert <$> reorder e <*> return lbl <*> reorder v++ Modify e fields ->+ Modify <$> reorder e <*> mapM (\(k,v) -> (,) k <$> reorder v) fields++ Record fields ->+ Record <$> mapM (\(k,v) -> (,) k <$> reorder v) fields++ GLShader _ _ _ -> return expr++ PortOut name st signal -> PortOut name st <$> reorder signal++ PortIn name st -> return $ PortIn name st++ -- Actually do some reordering+ Let defs body ->+ do body' <- reorder body++ -- Sort defs into strongly connected components.This+ -- allows the programmer to write definitions in whatever+ -- order they please, we can still define things in order+ -- and generalize polymorphic functions when appropriate.+ sccs <- Graph.stronglyConnComp <$> buildDefDict defs+ let defss = map Graph.flattenSCC sccs+ + -- remove let-bound variables from the context+ forM_ defs $ \(Canonical.Definition pattern _ _) -> do+ bound (P.boundVars pattern)+ mapM free (ctors pattern)++ let A _ let' = foldr (\ds bod -> A ann (Let ds bod)) body' defss++ return let'++bindingReorder :: (P.CanonicalPattern, Canonical.Expr)+ -> State (Set.Set String) (P.CanonicalPattern, Canonical.Expr)+bindingReorder (pattern,expr) =+ do expr' <- reorder expr+ bound (P.boundVars pattern)+ mapM_ free (ctors pattern)+ return (pattern, expr')+++reorderAndGetDependencies :: Canonical.Def -> State (Set.Set String) (Canonical.Def, [String])+reorderAndGetDependencies (Canonical.Definition pattern expr mType) =+ do globalFrees <- get+ -- work in a fresh environment+ put Set.empty+ expr' <- reorder expr+ localFrees <- get+ -- merge with global frees+ modify (Set.union globalFrees)+ return (Canonical.Definition pattern expr' mType, Set.toList localFrees)+++-- This also reorders the all of the sub-expressions in the Def list.+buildDefDict :: [Canonical.Def] -> State (Set.Set String) [(Canonical.Def, Int, [Int])]+buildDefDict defs =+ do pdefsDeps <- mapM reorderAndGetDependencies defs+ return $ realDeps (addKey pdefsDeps)++ where+ addKey :: [(Canonical.Def, [String])] -> [(Canonical.Def, Int, [String])]+ addKey = zipWith (\n (pdef,deps) -> (pdef,n,deps)) [0..]++ variableToKey :: (Canonical.Def, Int, [String]) -> [(String, Int)]+ variableToKey (Canonical.Definition pattern _ _, key, _) =+ [ (var, key) | var <- Set.toList (P.boundVars pattern) ]++ variableToKeyMap :: [(Canonical.Def, Int, [String])] -> Map.Map String Int+ variableToKeyMap pdefsDeps =+ Map.fromList (concatMap variableToKey pdefsDeps)++ realDeps :: [(Canonical.Def, Int, [String])] -> [(Canonical.Def, Int, [Int])]+ realDeps pdefsDeps = map convert pdefsDeps+ where+ varDict = variableToKeyMap pdefsDeps+ convert (pdef, key, deps) =+ (pdef, key, Maybe.mapMaybe (flip Map.lookup varDict) deps)
+ src/Transform/Substitute.hs view
@@ -0,0 +1,88 @@+{-# OPTIONS_GHC -Wall #-}+module Transform.Substitute (subst) where++import Control.Arrow (second, (***))+import qualified Data.Set as Set++import AST.Annotation+import AST.Expression.General (Expr'(..))+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Pattern as Pattern+import qualified AST.Variable as V+++subst :: String -> Canonical.Expr' -> Canonical.Expr' -> Canonical.Expr'+subst old new expression =+ let f (A a e) = A a (subst old new e) in+ case expression of+ Range e1 e2 ->+ Range (f e1) (f e2)++ ExplicitList exprs ->+ ExplicitList (map f exprs)++ Binop op e1 e2 ->+ Binop op (f e1) (f e2)++ Lambda pattern body+ | Set.member old (Pattern.boundVars pattern) -> expression+ | otherwise -> Lambda pattern (f body)++ App e1 e2 ->+ App (f e1) (f e2)++ MultiIf branches ->+ MultiIf (map (f *** f) branches)++ Let defs body+ | anyShadow -> expression+ | otherwise -> Let (map substDef defs) (f body)+ where+ substDef (Canonical.Definition p e t) =+ Canonical.Definition p (f e) t++ anyShadow =+ any (Set.member old . Pattern.boundVars)+ [ pattern | Canonical.Definition pattern _ _ <- defs ]++ Var (V.Canonical home x) ->+ case home of+ V.Module _ -> expression+ V.BuiltIn -> expression+ V.Local -> if x == old then new else expression++ Case e cases ->+ Case (f e) (map substCase cases)+ where+ substCase (pattern, expr) =+ if Set.member old (Pattern.boundVars pattern)+ then (pattern, expr)+ else (pattern, f expr)++ Data tag values ->+ Data tag (map f values)++ Access record field ->+ Access (f record) field++ Remove record field ->+ Remove (f record) field++ Insert record field value ->+ Insert (f record) field (f value)++ Modify record fields ->+ Modify (f record) (map (second f) fields)++ Record fields ->+ Record (map (second f) fields)++ Literal _ -> expression++ GLShader _ _ _ -> expression++ PortIn name st ->+ PortIn name st++ PortOut name st signal ->+ PortOut name st (f signal)
+ src/Type/Constrain/Expression.hs view
@@ -0,0 +1,231 @@+module Type.Constrain.Expression where++import Control.Applicative ((<$>))+import qualified Control.Monad as Monad+import Control.Monad.Error+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Text.PrettyPrint as PP++import AST.Literal as Lit+import AST.Annotation as Ann+import AST.Expression.General+import qualified AST.Expression.Canonical as Canonical+import qualified AST.Pattern as P+import qualified AST.Type as ST+import qualified AST.Variable as V+import Type.Type hiding (Descriptor(..))+import Type.Fragment+import qualified Type.Environment as Env+import qualified Type.Constrain.Literal as Literal+import qualified Type.Constrain.Pattern as Pattern+++constrain+ :: Env.Environment+ -> Canonical.Expr+ -> Type+ -> ErrorT [PP.Doc] IO TypeConstraint+constrain env (A region expr) tipe =+ let list t = Env.get env Env.types "List" <| t+ and = A region . CAnd+ true = A region CTrue+ t1 === t2 = A region (CEqual t1 t2)+ x <? t = A region (CInstance x t)+ clet schemes c = A region (CLet schemes c)+ in+ case expr of+ Literal lit -> liftIO $ Literal.constrain env region lit tipe++ GLShader _uid _src gltipe -> + exists $ \attr -> + exists $ \unif -> + let + shaderTipe a u v = Env.get env Env.types "WebGL.Shader" <| a <| u <| v+ glTipe = Env.get env Env.types . Lit.glTipeName+ makeRec accessor baseRec = + let decls = accessor gltipe+ in if Map.size decls == 0+ then baseRec+ else record (Map.map (\t -> [glTipe t]) decls) baseRec+ attribute = makeRec Lit.attribute attr+ uniform = makeRec Lit.uniform unif+ varying = makeRec Lit.varying (termN EmptyRecord1)+ in return . A region $ CEqual tipe (shaderTipe attribute uniform varying)++ Var var+ | name == saveEnvName -> return (A region CSaveEnv)+ | otherwise -> return (name <? tipe)+ where+ name = V.toString var++ Range lo hi ->+ existsNumber $ \n -> do+ clo <- constrain env lo n+ chi <- constrain env hi n+ return $ and [clo, chi, list n === tipe]++ ExplicitList exprs ->+ exists $ \x -> do+ cs <- mapM (\e -> constrain env e x) exprs+ return . and $ list x === tipe : cs++ Binop op e1 e2 ->+ exists $ \t1 ->+ exists $ \t2 -> do+ c1 <- constrain env e1 t1+ c2 <- constrain env e2 t2+ return $ and [ c1, c2, V.toString op <? (t1 ==> t2 ==> tipe) ]++ Lambda p e ->+ exists $ \t1 ->+ exists $ \t2 -> do+ fragment <- try region $ Pattern.constrain env p t1+ c2 <- constrain env e t2+ let c = ex (vars fragment) (clet [monoscheme (typeEnv fragment)]+ (typeConstraint fragment /\ c2 ))+ return $ c /\ tipe === (t1 ==> t2)++ App e1 e2 ->+ exists $ \t -> do+ c1 <- constrain env e1 (t ==> tipe)+ c2 <- constrain env e2 t+ return $ c1 /\ c2++ MultiIf branches -> and <$> mapM constrain' branches+ where + bool = Env.get env Env.types "Bool"+ constrain' (b,e) = do+ cb <- constrain env b bool+ ce <- constrain env e tipe+ return (cb /\ ce)++ Case exp branches ->+ exists $ \t -> do+ ce <- constrain env exp t+ let branch (p,e) = do+ fragment <- try region $ Pattern.constrain env p t+ clet [toScheme fragment] <$> constrain env e tipe+ and . (:) ce <$> mapM branch branches++ Data name exprs ->+ do vars <- forM exprs $ \_ -> liftIO (variable Flexible)+ let pairs = zip exprs (map varN vars)+ (ctipe, cs) <- Monad.foldM step (tipe,true) (reverse pairs)+ return $ ex vars (cs /\ name <? ctipe)+ where+ step (t,c) (e,x) = do+ c' <- constrain env e x+ return (x ==> t, c /\ c')++ Access e label ->+ exists $ \t ->+ constrain env e (record (Map.singleton label [tipe]) t)++ Remove e label ->+ exists $ \t ->+ constrain env e (record (Map.singleton label [t]) tipe)++ Insert e label value ->+ exists $ \tVal ->+ exists $ \tRec -> do+ cVal <- constrain env value tVal+ cRec <- constrain env e tRec+ let c = tipe === record (Map.singleton label [tVal]) tRec+ return (and [cVal, cRec, c])++ Modify e fields ->+ exists $ \t -> do+ oldVars <- forM fields $ \_ -> liftIO (variable Flexible)+ let oldFields = ST.fieldMap (zip (map fst fields) (map varN oldVars))+ cOld <- ex oldVars <$> constrain env e (record oldFields t)++ newVars <- forM fields $ \_ -> liftIO (variable Flexible)+ let newFields = ST.fieldMap (zip (map fst fields) (map varN newVars))+ let cNew = tipe === record newFields t++ cs <- zipWithM (constrain env) (map snd fields) (map varN newVars)++ return $ cOld /\ ex newVars (and (cNew : cs))++ Record fields ->+ do vars <- forM fields $ \_ -> liftIO (variable Flexible)+ cs <- zipWithM (constrain env) (map snd fields) (map varN vars)+ let fields' = ST.fieldMap (zip (map fst fields) (map varN vars))+ recordType = record fields' (termN EmptyRecord1)+ return . ex vars . and $ tipe === recordType : cs++ Let defs body ->+ do c <- constrain env body tipe+ (schemes, rqs, fqs, header, c2, c1) <-+ Monad.foldM (constrainDef env)+ ([], [], [], Map.empty, true, true)+ (concatMap expandPattern defs)+ return $ clet schemes+ (clet [Scheme rqs fqs (clet [monoscheme header] c2) header ]+ (c1 /\ c))++ PortIn _ _ -> return true++ PortOut _ _ signal ->+ constrain env signal tipe+++constrainDef env info (Canonical.Definition pattern expr maybeTipe) =+ let qs = [] -- should come from the def, but I'm not sure what would live there...+ (schemes, rigidQuantifiers, flexibleQuantifiers, headers, c2, c1) = info+ in+ do rigidVars <- forM qs (\_ -> liftIO $ variable Rigid) -- Some mistake may be happening here.+ -- Currently, qs is always [].+ case (pattern, maybeTipe) of+ (P.Var name, Just tipe) -> do+ flexiVars <- forM qs (\_ -> liftIO $ variable Flexible)+ let inserts = zipWith (\arg typ -> Map.insert arg (varN typ)) qs flexiVars+ env' = env { Env.value = List.foldl' (\x f -> f x) (Env.value env) inserts }+ (vars, typ) <- Env.instantiateType env tipe Map.empty+ let scheme = Scheme { rigidQuantifiers = [],+ flexibleQuantifiers = flexiVars ++ vars,+ constraint = Ann.noneNoDocs CTrue,+ header = Map.singleton name typ }+ c <- constrain env' expr typ+ return ( scheme : schemes+ , rigidQuantifiers+ , flexibleQuantifiers+ , headers+ , c2+ , fl rigidVars c /\ c1 )++ (P.Var name, Nothing) -> do+ v <- liftIO $ variable Flexible+ let tipe = varN v+ inserts = zipWith (\arg typ -> Map.insert arg (varN typ)) qs rigidVars+ env' = env { Env.value = List.foldl' (\x f -> f x) (Env.value env) inserts }+ c <- constrain env' expr tipe+ return ( schemes+ , rigidVars ++ rigidQuantifiers+ , v : flexibleQuantifiers+ , Map.insert name tipe headers+ , c /\ c2+ , c1 )++ _ -> error ("problem in constrainDef with " ++ show pattern)+++expandPattern :: Canonical.Def -> [Canonical.Def]+expandPattern def@(Canonical.Definition pattern lexpr@(A r _) maybeType) =+ case pattern of+ P.Var _ -> [def]+ _ -> Canonical.Definition (P.Var x) lexpr maybeType : map toDef vars+ where+ vars = P.boundVarList pattern+ x = "$" ++ concat vars+ mkVar = A r . localVar+ toDef y = Canonical.Definition (P.Var y) (A r $ Case (mkVar x) [(pattern, mkVar y)]) Nothing+++try :: Region -> ErrorT (Region -> PP.Doc) IO a -> ErrorT [PP.Doc] IO a+try region computation =+ do result <- liftIO $ runErrorT computation+ case result of+ Left err -> throwError [err region]+ Right value -> return value
+ src/Type/Constrain/Literal.hs view
@@ -0,0 +1,21 @@+module Type.Constrain.Literal where++import AST.Annotation+import AST.Literal+import Type.Type+import Type.Environment as Env++constrain :: Environment -> Region -> Literal -> Type -> IO TypeConstraint+constrain env region literal tipe =+ do tipe' <- litType+ return . A region $ CEqual tipe tipe'+ where+ prim name = return (Env.get env Env.types name)++ litType =+ case literal of+ IntNum _ -> varN `fmap` variable (Is Number)+ FloatNum _ -> prim "Float"+ Chr _ -> prim "Char"+ Str _ -> prim "String"+ Boolean _ -> prim "Bool"
+ src/Type/Constrain/Pattern.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE FlexibleInstances #-}+module Type.Constrain.Pattern where++import Control.Arrow (second)+import Control.Applicative ((<$>))+import qualified Control.Monad as Monad+import Control.Monad.Error+import qualified Data.Map as Map+import qualified Text.PrettyPrint as PP++import qualified AST.Annotation as A+import qualified AST.Pattern as P+import qualified AST.Variable as V+import AST.PrettyPrint (pretty)+import Type.Type+import Type.Fragment+import Type.Environment as Env+import qualified Type.Constrain.Literal as Literal+++constrain :: Environment -> P.CanonicalPattern -> Type+ -> ErrorT (A.Region -> PP.Doc) IO Fragment+constrain env pattern tipe =+ let region = A.None (pretty pattern)+ t1 === t2 = A.A region (CEqual t1 t2)+ in+ case pattern of+ P.Anything -> return emptyFragment++ P.Literal lit -> do+ c <- liftIO $ Literal.constrain env region lit tipe+ return $ emptyFragment { typeConstraint = c }++ P.Var name -> do+ v <- liftIO $ variable Flexible+ return $ Fragment {+ typeEnv = Map.singleton name (varN v),+ vars = [v],+ typeConstraint = varN v === tipe+ }++ P.Alias name p -> do+ v <- liftIO $ variable Flexible+ fragment <- constrain env p tipe+ return $ fragment+ { typeEnv = Map.insert name (varN v) (typeEnv fragment)+ , vars = v : vars fragment+ , typeConstraint = varN v === tipe /\ typeConstraint fragment+ }++ P.Data name patterns -> do+ (kind, cvars, args, result) <- liftIO $ freshDataScheme env (V.toString name)+ let msg = concat [ "Constructor '", V.toString name, "' expects ", show kind+ , " argument", if kind == 1 then "" else "s"+ , " but was given ", show (length patterns), "." ]+ err span = PP.vcat [ PP.text $ "Type error " ++ show span+ , PP.text msg ]+ case length patterns == kind of+ False -> throwError err+ True -> do+ fragment <- Monad.liftM joinFragments (Monad.zipWithM (constrain env) patterns args)+ return $ fragment {+ typeConstraint = typeConstraint fragment /\ tipe === result,+ vars = cvars ++ vars fragment+ }++ P.Record fields -> do+ pairs <- liftIO $ mapM (\name -> (,) name <$> variable Flexible) fields+ let tenv = Map.fromList (map (second varN) pairs)+ c <- exists $ \t -> return (tipe === record (Map.map (:[]) tenv) t)+ return $ Fragment {+ typeEnv = tenv,+ vars = map snd pairs,+ typeConstraint = c+ }++instance Error (A.Region -> PP.Doc) where+ noMsg _ = PP.empty+ strMsg str span =+ PP.vcat [ PP.text $ "Type error " ++ show span+ , PP.text str ]
+ src/Type/Environment.hs view
@@ -0,0 +1,167 @@+module Type.Environment where++import Control.Applicative ((<$>), (<*>))+import Control.Exception (try, SomeException)+import Control.Monad+import Control.Monad.Error (ErrorT, throwError, liftIO)+import qualified Control.Monad.State as State+import qualified Data.Traversable as Traverse+import qualified Data.Map as Map+import Data.List (isPrefixOf)+import qualified Text.PrettyPrint as PP++import qualified AST.Type as T+import qualified AST.Variable as V+import AST.Module (CanonicalAdt, AdtInfo)+import Type.Type++type TypeDict = Map.Map String Type+type VarDict = Map.Map String Variable++data Environment = Environment+ { constructor :: Map.Map String (IO (Int, [Variable], [Type], Type))+ , types :: TypeDict+ , value :: TypeDict+ }++initialEnvironment :: [CanonicalAdt] -> IO Environment+initialEnvironment datatypes = do+ types <- makeTypes datatypes+ let env = Environment+ { constructor = Map.empty+ , value = Map.empty+ , types = types+ }+ return $ env { constructor = makeConstructors env datatypes }++makeTypes :: [CanonicalAdt] -> IO TypeDict+makeTypes datatypes =+ do adts <- mapM makeImported datatypes+ bs <- mapM makeBuiltin builtins+ return (Map.fromList (adts ++ bs))+ where+ makeImported :: (V.Canonical, AdtInfo V.Canonical) -> IO (String, Type)+ makeImported (var, _) = do+ tvar <- namedVar Constant var+ return (V.toString var, varN tvar)++ makeBuiltin :: (String, Int) -> IO (String, Type)+ makeBuiltin (name, _) = do+ name' <- namedVar Constant (V.builtin name)+ return (name, varN name')++ builtins :: [(String, Int)]+ builtins = concat [ map tuple [0..9]+ , kind 1 ["List"]+ , kind 0 ["Int","Float","Char","String","Bool"]+ ]+ where+ tuple n = ("_Tuple" ++ show n, n)+ kind n names = map (\name -> (name, n)) names++++makeConstructors :: Environment+ -> [CanonicalAdt]+ -> Map.Map String (IO (Int, [Variable], [Type], Type))+makeConstructors env datatypes = Map.fromList builtins+ where+ list t = (types env Map.! "List") <| t++ inst :: Int -> ([Type] -> ([Type], Type)) -> IO (Int, [Variable], [Type], Type)+ inst numTVars tipe = do+ vars <- forM [1..numTVars] $ \_ -> variable Flexible+ let (args, result) = tipe (map (varN) vars)+ return (length args, vars, args, result)++ tupleCtor n =+ let name = "_Tuple" ++ show n+ in (name, inst n $ \vs -> (vs, foldl (<|) (types env Map.! name) vs))+ + builtins :: [ (String, IO (Int, [Variable], [Type], Type)) ]+ builtins = [ ("[]", inst 1 $ \ [t] -> ([], list t))+ , ("::", inst 1 $ \ [t] -> ([t, list t], list t))+ ] ++ map tupleCtor [0..9]+ ++ concatMap (ctorToType env) datatypes+++ctorToType :: Environment+ -> (V.Canonical, AdtInfo V.Canonical)+ -> [(String, IO (Int, [Variable], [Type], Type))]+ctorToType env (name, (tvars, ctors)) =+ zip (map (V.toString . fst) ctors) (map inst ctors)+ where+ inst :: (V.Canonical, [T.CanonicalType]) -> IO (Int, [Variable], [Type], Type)+ inst ctor = do+ ((args, tipe), dict) <- State.runStateT (go ctor) Map.empty+ return (length args, Map.elems dict, args, tipe)+ ++ go :: (V.Canonical, [T.CanonicalType]) -> State.StateT VarDict IO ([Type], Type)+ go (_, args) = do+ types <- mapM (instantiator env) args+ returnType <- instantiator env (T.App (T.Type name) (map T.Var tvars))+ return (types, returnType)+++get :: Environment -> (Environment -> Map.Map String a) -> String -> a+get env subDict key = Map.findWithDefault (error msg) key (subDict env)+ where+ msg = "Could not find type constructor '" ++ key ++ "' while checking types."+++freshDataScheme :: Environment -> String -> IO (Int, [Variable], [Type], Type)+freshDataScheme env name = get env constructor name++instantiateType :: Environment -> T.CanonicalType -> VarDict -> ErrorT [PP.Doc] IO ([Variable], Type)+instantiateType env sourceType dict =+ do result <- liftIO $ try (State.runStateT (instantiator env sourceType) dict)+ case result :: Either SomeException (Type, VarDict) of+ Left someError -> throwError [ PP.text $ show someError ]+ Right (tipe, dict') -> return (Map.elems dict', tipe)++instantiator :: Environment -> T.CanonicalType -> State.StateT VarDict IO Type+instantiator env sourceType = go sourceType+ where+ go :: T.CanonicalType -> State.StateT VarDict IO Type+ go sourceType =+ case sourceType of+ T.Lambda t1 t2 -> (==>) <$> go t1 <*> go t2++ T.Var x -> do+ dict <- State.get+ case Map.lookup x dict of+ Just v -> return (varN v)+ Nothing ->+ do v <- State.liftIO $ namedVar flex (V.local x)+ State.put (Map.insert x v dict)+ return (varN v)+ where+ flex | "number" `isPrefixOf` x = Is Number+ | "comparable" `isPrefixOf` x = Is Comparable+ | "appendable" `isPrefixOf` x = Is Appendable+ | otherwise = Flexible++ T.Aliased name t -> do+ t' <- go t+ case t' of+ VarN _ v -> return (VarN (Just name) v)+ TermN _ term -> return (TermN (Just name) term)++ T.Type name ->+ case Map.lookup (V.toString name) (types env) of+ Just t -> return t+ Nothing -> error $ "Could not find type constructor '" +++ V.toString name ++ "' while checking types."++ T.App t ts -> do+ t' <- go t+ ts' <- mapM go ts+ return $ foldl (<|) t' ts'++ T.Record fields ext -> do+ fields' <- Traverse.traverse (mapM go) (T.fieldMap fields)+ ext' <- case ext of+ Nothing -> return $ termN EmptyRecord1+ Just x -> go x+ return $ termN (Record1 fields' ext')
+ src/Type/ExtraChecks.hs view
@@ -0,0 +1,155 @@+{-# OPTIONS_GHC -Wall #-}++{-| This module contains checks to be run *after* type inference has completed+successfully. At that point we still need to do occurs checks and ensure that+`main` has an acceptable type.+-}+module Type.ExtraChecks (mainType, occurs, portTypes) where++import Control.Applicative ((<$>),(<*>))+import Control.Monad.Error+import Control.Monad.State+import qualified Data.Map as Map+import qualified Data.Traversable as Traverse+import qualified Data.UnionFind.IO as UF+import Text.PrettyPrint as P++import qualified AST.Annotation as A+import qualified AST.Expression.Canonical as Canonical+import qualified AST.PrettyPrint as PP+import qualified AST.Type as ST+import qualified AST.Variable as V+import qualified Transform.Expression as Expr+import qualified Type.Type as TT+import qualified Type.State as TS++throw :: [Doc] -> Either [Doc] a+throw err = Left [ P.vcat err ]++mainType :: TS.Env -> ErrorT [P.Doc] IO (Map.Map String ST.CanonicalType)+mainType environment =+ do environment' <- liftIO $ Traverse.traverse TT.toSrcType environment+ mainCheck environment'+ where+ mainCheck :: (Monad m) => Map.Map String ST.CanonicalType+ -> ErrorT [P.Doc] m (Map.Map String ST.CanonicalType)+ mainCheck env =+ case Map.lookup "main" env of+ Nothing -> return env+ Just typeOfMain+ | tipe `elem` acceptable -> return env+ | otherwise -> throwError err+ where+ acceptable = [ "Graphics.Element.Element"+ , "Signal.Signal Graphics.Element.Element" ]++ tipe = PP.renderPretty typeOfMain+ err = [ P.text "Type Error: 'main' must have type Element or (Signal Element)."+ , P.text "Instead 'main' has type:\n"+ , P.nest 4 (PP.pretty typeOfMain)+ , P.text " " ]++data Direction = In | Out++portTypes :: (Monad m) => Canonical.Expr -> ErrorT [P.Doc] m ()+portTypes expr =+ case Expr.checkPorts (check In) (check Out) expr of+ Left err -> throwError err+ Right _ -> return ()+ where+ check = isValid True False False+ isValid isTopLevel seenFunc seenSignal direction name tipe =+ case tipe of+ ST.Aliased _ t -> valid t++ ST.Type v ->+ case any ($ v) [ V.isJson, V.isPrimitive, V.isTuple ] of+ True -> return ()+ False -> err "an unsupported type"++ ST.App t [] -> valid t++ ST.App (ST.Type v) [t]+ | V.isSignal v -> handleSignal t+ | V.isMaybe v -> valid t+ | V.isArray v -> valid t+ | V.isList v -> valid t++ ST.App (ST.Type v) ts+ | V.isTuple v -> mapM_ valid ts+ + ST.App _ _ -> err "an unsupported type"++ ST.Var _ -> err "free type variables"++ ST.Lambda _ _ ->+ case direction of+ In -> err "functions"+ Out | seenFunc -> err "higher-order functions"+ | seenSignal -> err "signals that contain functions"+ | otherwise ->+ forM_ (ST.collectLambdas tipe)+ (isValid' True seenSignal direction name)++ ST.Record _ (Just _) -> err "extended records with free type variables"++ ST.Record fields Nothing ->+ mapM_ (\(k,v) -> (,) k <$> valid v) fields++ where+ isValid' = isValid False+ valid = isValid' seenFunc seenSignal direction name++ handleSignal t+ | seenFunc = err "functions that involve signals"+ | seenSignal = err "signals-of-signals"+ | isTopLevel = isValid' seenFunc True direction name t+ | otherwise = err "a signal within a data stucture"++ dir inMsg outMsg = case direction of { In -> inMsg ; Out -> outMsg }+ txt = P.text . concat++ err kind =+ throw $+ [ txt [ "Type Error: the value ", dir "coming in" "sent out"+ , " through port '", name, "' is invalid." ]+ , txt [ "It contains ", kind, ":\n" ]+ , P.nest 4 (PP.pretty tipe) <> P.text "\n"+ , txt [ "Acceptable values for ", dir "incoming" "outgoing", " ports include:" ]+ , txt [ " Ints, Floats, Bools, Strings, Maybes, Lists, Arrays, Tuples, unit values," ]+ , txt [ " Json.Values, ", dir "" "first-order functions, ", "and concrete records." ]+ ]++occurs :: (String, TT.Variable) -> StateT TS.SolverState IO ()+occurs (name, variable) =+ do vars <- liftIO $ infiniteVars [] variable+ case vars of+ [] -> return ()+ var:_ -> do+ desc <- liftIO $ UF.descriptor var+ case TT.structure desc of+ Nothing ->+ modify $ \s -> s { TS.sErrors = P.text msg : TS.sErrors s }+ Just _ ->+ do liftIO $ UF.setDescriptor var (desc { TT.structure = Nothing })+ var' <- liftIO $ UF.fresh desc+ TS.addError (A.None (P.text name)) (Just msg) var var'+ where+ msg = "Infinite types are not allowed"++ infiniteVars :: [TT.Variable] -> TT.Variable -> IO [TT.Variable]+ infiniteVars seen var =+ let go = infiniteVars (var:seen) in+ if var `elem` seen+ then return [var]+ else do+ desc <- UF.descriptor var+ case TT.structure desc of+ Nothing -> return []+ Just struct ->+ case struct of+ TT.App1 a b -> (++) <$> go a <*> go b+ TT.Fun1 a b -> (++) <$> go a <*> go b+ TT.Var1 a -> go a+ TT.EmptyRecord1 -> return []+ TT.Record1 fields ext -> concat <$> mapM go (ext : concat (Map.elems fields))
+ src/Type/Fragment.hs view
@@ -0,0 +1,43 @@+{-# OPTIONS_GHC -Wall #-}+module Type.Fragment where++import qualified Data.List as List+import qualified Data.Map as Map++import Type.Type+import AST.Annotation (noneNoDocs)++data Fragment = Fragment+ { typeEnv :: Map.Map String Type+ , vars :: [Variable]+ , typeConstraint :: TypeConstraint+ }+++emptyFragment :: Fragment+emptyFragment =+ Fragment Map.empty [] (noneNoDocs CTrue)+++joinFragment :: Fragment -> Fragment -> Fragment+joinFragment f1 f2 =+ Fragment+ { typeEnv =+ Map.union (typeEnv f1) (typeEnv f2)++ , vars =+ vars f1 ++ vars f2++ , typeConstraint =+ typeConstraint f1 /\ typeConstraint f2+ }+++joinFragments :: [Fragment] -> Fragment+joinFragments =+ List.foldl' (flip joinFragment) emptyFragment+++toScheme :: Fragment -> Scheme Type Variable+toScheme fragment =+ Scheme [] (vars fragment) (typeConstraint fragment) (typeEnv fragment)
+ src/Type/Inference.hs view
@@ -0,0 +1,98 @@+module Type.Inference where++import qualified Data.Map as Map++import qualified Type.Type as T+import qualified Type.Environment as Env+import qualified Type.Constrain.Expression as TcExpr+import qualified Type.Solve as Solve++import qualified AST.Annotation as A+import AST.Module as Module+import AST.Type (CanonicalType)+import qualified AST.Variable as Var+import Text.PrettyPrint+import qualified Type.State as TS+import qualified Type.ExtraChecks as Check+import Control.Arrow (first, second)+import Control.Monad.State (execStateT, forM)+import Control.Monad.Error (ErrorT, runErrorT, liftIO, throwError)++import System.IO.Unsafe -- Possible to switch over to the ST monad instead of+ -- the IO monad. I don't think that'd be worthwhile.+++infer :: Interfaces -> CanonicalModule -> Either [Doc] (Map.Map String CanonicalType)+infer interfaces modul =+ unsafePerformIO $ runErrorT $+ do (header, constraint) <- genConstraints interfaces modul++ state <- liftIO $ execStateT (Solve.solve constraint) TS.initialState++ () <- case TS.sErrors state of+ errors@(_:_) -> throwError errors+ [] -> return ()++ () <- Check.portTypes (program (body modul))++ let header' = Map.delete "::" header+ types = Map.difference (TS.sSavedEnv state) header'++ Check.mainType types+++genConstraints+ :: Interfaces+ -> CanonicalModule+ -> ErrorT [Doc] IO (Env.TypeDict, T.TypeConstraint)+genConstraints interfaces modul =+ do env <- liftIO $ Env.initialEnvironment (canonicalizeAdts interfaces modul)++ ctors <- forM (Map.keys (Env.constructor env)) $ \name -> do+ (_, vars, args, result) <- liftIO $ Env.freshDataScheme env name+ return (name, (vars, foldr (T.==>) result args))++ importedVars <- mapM (canonicalizeValues env) (Map.toList interfaces)++ let allTypes = concat (ctors : importedVars)+ vars = concatMap (fst . snd) allTypes+ header = Map.map snd (Map.fromList allTypes)+ environ = A.noneNoDocs . T.CLet [ T.Scheme vars [] (A.noneNoDocs T.CTrue) header ]++ fvar <- liftIO $ T.variable T.Flexible+ c <- TcExpr.constrain env (program (body modul)) (T.varN fvar)+ return (header, environ c)+++canonicalizeValues+ :: Env.Environment+ -> (Module.Name, Interface)+ -> ErrorT [Doc] IO [(String, ([T.Variable], T.Type))]+canonicalizeValues env (moduleName, iface) =+ forM (Map.toList (iTypes iface)) $ \(name,tipe) ->+ do tipe' <- Env.instantiateType env tipe Map.empty+ return (Module.nameToString moduleName ++ "." ++ name, tipe')+++canonicalizeAdts :: Interfaces -> CanonicalModule -> [CanonicalAdt]+canonicalizeAdts interfaces modul =+ localAdts ++ importedAdts+ where+ localAdts :: [CanonicalAdt]+ localAdts = format (Module.names modul, datatypes (body modul))++ importedAdts :: [CanonicalAdt]+ importedAdts = concatMap (format . second iAdts) (Map.toList interfaces)++ format :: (Module.Name, Module.ADTs) -> [CanonicalAdt]+ format (home, adts) =+ map canonical (Map.toList adts)+ where+ canonical :: (String, AdtInfo String) -> CanonicalAdt+ canonical (name, (tvars, ctors)) =+ ( toVar name+ , (tvars, map (first toVar) ctors)+ )++ toVar :: String -> Var.Canonical+ toVar = Var.Canonical (Var.Module home)
+ src/Type/PrettyPrint.hs view
@@ -0,0 +1,20 @@+module Type.PrettyPrint where++import Text.PrettyPrint+++data ParensWhen = Fn | App | Never+++class PrettyType a where+ pretty :: ParensWhen -> a -> Doc+++commaSep :: [Doc] -> Doc+commaSep docs =+ sep (punctuate comma docs)+++parensIf :: Bool -> Doc -> Doc+parensIf bool doc =+ if bool then parens doc else doc
+ src/Type/Solve.hs view
@@ -0,0 +1,195 @@+module Type.Solve (solve) where++import Control.Monad+import Control.Monad.State+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Traversable as Traversable+import qualified Data.UnionFind.IO as UF+import Type.Type+import Type.Unify+import qualified Type.ExtraChecks as Check+import qualified Type.State as TS+import qualified AST.Annotation as A+++-- | Every variable has rank less than or equal to the maxRank of the pool.+-- This sorts variables into the young and old pools accordingly.+generalize :: TS.Pool -> StateT TS.SolverState IO ()+generalize youngPool = do+ youngMark <- TS.uniqueMark + let youngRank = TS.maxRank youngPool+ insert dict var = do+ descriptor <- liftIO $ UF.descriptor var+ liftIO $ UF.modifyDescriptor var (\desc -> desc { mark = youngMark })+ return $ Map.insertWith (++) (rank descriptor) [var] dict++ -- Sort the youngPool variables by rank.+ rankDict <- foldM insert Map.empty (TS.inhabitants youngPool)++ -- get the ranks right for each entry.+ -- start at low ranks so that we only have to pass+ -- over the information once.+ visitedMark <- TS.uniqueMark+ forM (Map.toList rankDict) $ \(poolRank, vars) ->+ mapM (adjustRank youngMark visitedMark poolRank) vars++ -- For variables that have rank lowerer than youngRank, register them in+ -- the old pool if they are not redundant.+ let registerIfNotRedundant var = do+ isRedundant <- liftIO $ UF.redundant var+ if isRedundant then return var else TS.register var++ let rankDict' = Map.delete youngRank rankDict+ Traversable.traverse (mapM registerIfNotRedundant) rankDict'++ -- For variables with rank youngRank+ -- If rank < youngRank: register in oldPool+ -- otherwise generalize+ let registerIfLowerRank var = do+ isRedundant <- liftIO $ UF.redundant var+ case isRedundant of+ True -> return ()+ False -> do+ desc <- liftIO $ UF.descriptor var+ case rank desc < youngRank of+ True -> TS.register var >> return ()+ False -> do+ let flex' = case flex desc of { Flexible -> Rigid ; other -> other }+ liftIO $ UF.setDescriptor var (desc { rank = noRank, flex = flex' })+ + mapM_ registerIfLowerRank (Map.findWithDefault [] youngRank rankDict)+++-- adjust the ranks of variables such that ranks never increase as you+-- move deeper into a variable.+adjustRank :: Int -> Int -> Int -> Variable -> StateT TS.SolverState IO Int+adjustRank youngMark visitedMark groupRank var =+ do descriptor <- liftIO $ UF.descriptor var+ case () of+ () | mark descriptor == youngMark ->+ do -- Set the variable as marked first because it may be cyclic.+ liftIO $ UF.modifyDescriptor var $ \desc ->+ desc { mark = visitedMark }+ rank' <- maybe (return groupRank) adjustTerm (structure descriptor)+ liftIO $ UF.modifyDescriptor var $ \desc ->+ desc { rank = rank' }+ return rank'++ | mark descriptor /= visitedMark ->+ do let rank' = min groupRank (rank descriptor)+ liftIO $ UF.setDescriptor var (descriptor { mark = visitedMark, rank = rank' })+ return rank'++ | otherwise -> return (rank descriptor)++ where+ adjust = adjustRank youngMark visitedMark groupRank++ adjustTerm term =+ case term of+ App1 a b -> max `liftM` adjust a `ap` adjust b+ Fun1 a b -> max `liftM` adjust a `ap` adjust b+ Var1 x -> adjust x+ EmptyRecord1 -> return outermostRank+ Record1 fields extension ->+ do ranks <- mapM adjust (concat (Map.elems fields))+ rnk <- adjust extension+ return . maximum $ rnk : ranks+++solve :: TypeConstraint -> StateT TS.SolverState IO ()+solve (A.A region constraint) =+ case constraint of+ CTrue -> return ()++ CSaveEnv -> TS.saveLocalEnv++ CEqual term1 term2 -> do+ t1 <- TS.flatten term1+ t2 <- TS.flatten term2+ unify region t1 t2++ CAnd cs -> mapM_ solve cs++ CLet [Scheme [] fqs constraint' _] (A.A _ CTrue) -> do+ oldEnv <- TS.getEnv+ mapM TS.introduce fqs+ solve constraint'+ TS.modifyEnv (\_ -> oldEnv)++ CLet schemes constraint' -> do+ oldEnv <- TS.getEnv+ headers <- Map.unions `fmap` mapM (solveScheme region) schemes+ TS.modifyEnv $ \env -> Map.union headers env+ solve constraint'+ mapM Check.occurs $ Map.toList headers+ TS.modifyEnv (\_ -> oldEnv)++ CInstance name term -> do+ env <- TS.getEnv+ freshCopy <-+ case Map.lookup name env of+ Just tipe -> TS.makeInstance tipe+ Nothing+ | List.isPrefixOf "Native." name -> liftIO (variable Flexible)+ | otherwise ->+ error ("Could not find '" ++ name ++ "' when solving type constraints.")++ t <- TS.flatten term+ unify region freshCopy t++solveScheme :: A.Region -> TypeScheme -> StateT TS.SolverState IO (Map.Map String Variable)+solveScheme region scheme =+ case scheme of+ Scheme [] [] constraint header -> do+ solve constraint+ Traversable.traverse TS.flatten header++ Scheme rigidQuantifiers flexibleQuantifiers constraint header -> do+ let quantifiers = rigidQuantifiers ++ flexibleQuantifiers+ oldPool <- TS.getPool++ -- fill in a new pool when working on this scheme's constraints+ freshPool <- TS.nextRankPool+ TS.switchToPool freshPool+ mapM TS.introduce quantifiers+ header' <- Traversable.traverse TS.flatten header+ solve constraint++ allDistinct region rigidQuantifiers+ youngPool <- TS.getPool+ TS.switchToPool oldPool+ generalize youngPool+ mapM (isGeneric region) rigidQuantifiers+ return header'+++-- Checks that all of the given variables belong to distinct equivalence classes.+-- Also checks that their structure is Nothing, so they represent a variable, not+-- a more complex term.+allDistinct :: A.Region -> [Variable] -> StateT TS.SolverState IO ()+allDistinct region vars = do+ seen <- TS.uniqueMark+ let check var = do+ desc <- liftIO $ UF.descriptor var+ case structure desc of+ Just _ -> TS.addError region (Just msg) var var+ where msg = "Cannot generalize something that is not a type variable."++ Nothing -> do+ if mark desc == seen+ then let msg = "Duplicate variable during generalization."+ in TS.addError region (Just msg) var var+ else return ()+ liftIO $ UF.setDescriptor var (desc { mark = seen })+ mapM_ check vars++-- Check that a variable has rank == noRank, meaning that it can be generalized.+isGeneric :: A.Region -> Variable -> StateT TS.SolverState IO ()+isGeneric region var = do+ desc <- liftIO $ UF.descriptor var+ if rank desc == noRank+ then return ()+ else let msg = "Unable to generalize a type variable. It is not unranked."+ in TS.addError region (Just msg) var var
+ src/Type/State.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE FlexibleContexts #-}+module Type.State where++import Control.Applicative ( (<$>), (<*>), Applicative, (<|>) )+import Control.Monad.State+import qualified Data.Map as Map+import qualified Data.Traversable as Traversable+import qualified Data.UnionFind.IO as UF++import qualified AST.Annotation as A+import AST.PrettyPrint+import Text.PrettyPrint as P+import Type.Type++-- Pool+-- Holds a bunch of variables+-- The rank of each variable is less than or equal to the pool's "maxRank"+-- The young pool exists to make it possible to identify these vars in constant time.++data Pool = Pool+ { maxRank :: Int+ , inhabitants :: [Variable]+ }+++emptyPool :: Pool+emptyPool =+ Pool+ { maxRank = outermostRank+ , inhabitants = []+ }+++type Env = Map.Map String Variable+++-- Keeps track of the environment, type variable pool, and a list of errors+data SolverState = SS+ { sEnv :: Env+ , sSavedEnv :: Env+ , sPool :: Pool+ , sMark :: Int+ , sErrors :: [P.Doc]+ }+++initialState :: SolverState+initialState =+ SS+ { sEnv = Map.empty+ , sSavedEnv = Map.empty+ , sPool = emptyPool+ , sMark = noMark + 1 -- The mark must never be equal to noMark!+ , sErrors = []+ }+++modifyEnv :: (MonadState SolverState m) => (Env -> Env) -> m ()+modifyEnv f =+ modify $ \state -> state { sEnv = f (sEnv state) }+++modifyPool :: (MonadState SolverState m) => (Pool -> Pool) -> m ()+modifyPool f =+ modify $ \state -> state { sPool = f (sPool state) }+++addError+ :: A.Region+ -> Maybe String+ -> UF.Point Descriptor+ -> UF.Point Descriptor+ -> StateT SolverState IO ()+addError region hint t1 t2 =+ do err <- liftIO makeError+ modify $ \state -> state { sErrors = err : sErrors state }+ where+ makeError = do+ t1' <- pretty <$> toSrcType t1+ t2' <- pretty <$> toSrcType t2+ return . foldr ($+$) P.empty $+ [ P.text "Type mismatch between the following types" <+> pretty region <> P.text ":"+ , P.text ""+ , P.nest 8 t1'+ , P.text ""+ , P.nest 8 t2'+ , P.text ""+ , maybe P.empty (P.nest 4 . P.text) hint+ , P.text " It is related to the following expression:"+ , P.text ""+ , P.nest 8 $ A.getRegionDocs region+ ]+++switchToPool :: (MonadState SolverState m) => Pool -> m ()+switchToPool pool =+ modifyPool (\_ -> pool)+++getPool :: StateT SolverState IO Pool+getPool =+ gets sPool+++getEnv :: StateT SolverState IO Env+getEnv =+ gets sEnv+++saveLocalEnv :: StateT SolverState IO ()+saveLocalEnv =+ do env <- sEnv <$> get+ modify $ \state -> state { sSavedEnv = env }+++uniqueMark :: StateT SolverState IO Int+uniqueMark =+ do state <- get+ let mark = sMark state+ put $ state { sMark = mark + 1 }+ return mark+++nextRankPool :: StateT SolverState IO Pool+nextRankPool =+ do pool <- getPool+ return $ Pool { maxRank = maxRank pool + 1, inhabitants = [] }+++register :: Variable -> StateT SolverState IO Variable+register variable =+ do modifyPool $ \pool -> pool { inhabitants = variable : inhabitants pool }+ return variable+++introduce :: Variable -> StateT SolverState IO Variable+introduce variable =+ do pool <- getPool+ liftIO $ UF.modifyDescriptor variable (\desc -> desc { rank = maxRank pool })+ register variable+++flatten :: Type -> StateT SolverState IO Variable+flatten term =+ case term of+ VarN maybeAlias v ->+ do liftIO $ UF.modifyDescriptor v $ \desc -> desc { alias = maybeAlias <|> alias desc }+ return v++ TermN maybeAlias t ->+ do flatStructure <- traverseTerm flatten t+ pool <- getPool+ var <- liftIO . UF.fresh $ Descriptor+ { structure = Just flatStructure+ , rank = maxRank pool+ , flex = Flexible+ , name = Nothing+ , copy = Nothing+ , mark = noMark+ , alias = maybeAlias+ }+ register var+++makeInstance :: Variable -> StateT SolverState IO Variable+makeInstance var =+ do alreadyCopied <- uniqueMark+ freshVar <- makeCopy alreadyCopied var+ restore alreadyCopied var+ return freshVar+++makeCopy :: Int -> Variable -> StateT SolverState IO Variable+makeCopy alreadyCopied variable = do+ desc <- liftIO $ UF.descriptor variable+ case () of+ () | mark desc == alreadyCopied ->+ case copy desc of+ Just v -> return v+ Nothing -> error $ "Error copying type variable. This should be impossible." +++ " Please report an error to the github repo!"++ | rank desc /= noRank || flex desc == Constant ->+ return variable++ | otherwise -> do+ pool <- getPool+ newVar <- liftIO $ UF.fresh $ Descriptor+ { structure = Nothing+ , rank = maxRank pool+ , mark = noMark+ , flex = case flex desc of+ Is s -> Is s+ _ -> Flexible+ , copy = Nothing+ , name = case flex desc of+ Rigid -> Nothing+ _ -> name desc+ , alias = Nothing+ }+ register newVar++ -- Link the original variable to the new variable. This lets us+ -- avoid making multiple copies of the variable we are instantiating.+ --+ -- Need to do this before recursively copying the structure of+ -- the variable to avoid looping on cyclic terms.+ liftIO $ UF.modifyDescriptor variable $ \desc ->+ desc { mark = alreadyCopied, copy = Just newVar }++ -- Now we recursively copy the structure of the variable.+ -- We have already marked the variable as copied, so we+ -- will not repeat this work or crawl this variable again.+ case structure desc of+ Nothing -> return newVar+ Just term -> do+ newTerm <- traverseTerm (makeCopy alreadyCopied) term+ liftIO $ UF.modifyDescriptor newVar $ \desc ->+ desc { structure = Just newTerm }+ return newVar+++restore :: Int -> Variable -> StateT SolverState IO Variable+restore alreadyCopied variable =+ do desc <- liftIO $ UF.descriptor variable+ case mark desc /= alreadyCopied of+ True -> return variable+ False ->+ do restoredStructure <-+ Traversable.traverse (traverseTerm (restore alreadyCopied)) (structure desc)+ liftIO $ UF.modifyDescriptor variable $ \desc ->+ desc { mark = noMark, rank = noRank, structure = restoredStructure }+ return variable+++traverseTerm :: (Monad f, Applicative f) => (a -> f b) -> Term1 a -> f (Term1 b)+traverseTerm f term =+ case term of+ App1 a b -> App1 <$> f a <*> f b+ Fun1 a b -> Fun1 <$> f a <*> f b+ Var1 x -> Var1 <$> f x+ EmptyRecord1 -> return EmptyRecord1+ Record1 fields ext ->+ Record1 <$> Traversable.traverse (mapM f) fields <*> f ext+
+ src/Type/Type.hs view
@@ -0,0 +1,513 @@+module Type.Type where++import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.UnionFind.IO as UF+import Type.PrettyPrint+import Text.PrettyPrint as P+import System.IO.Unsafe+import Control.Applicative ((<$>),(<*>))+import Control.Monad.State (StateT, get, put, execStateT, runStateT)+import Control.Monad.Error (ErrorT, Error, liftIO)+import Data.Traversable (traverse)+import AST.Annotation+import qualified AST.PrettyPrint as PP+import qualified AST.Type as T+import qualified AST.Variable as Var+++data Term1 a+ = App1 a a+ | Fun1 a a+ | Var1 a+ | EmptyRecord1+ | Record1 (Map.Map String [a]) a+++data TermN a+ = VarN (Maybe Var.Canonical) a+ | TermN (Maybe Var.Canonical) (Term1 (TermN a))+++varN :: a -> TermN a+varN = VarN Nothing+++termN :: (Term1 (TermN a)) -> TermN a+termN = TermN Nothing+++record :: Map.Map String [TermN a] -> TermN a -> TermN a+record fs rec = termN (Record1 fs rec)+++type Type = TermN Variable+++type Variable = UF.Point Descriptor+++type SchemeName = String+++type TypeName = Var.Canonical+++type Constraint a b = Located (BasicConstraint a b)+++data BasicConstraint a b+ = CTrue+ | CSaveEnv+ | CEqual a a+ | CAnd [Constraint a b]+ | CLet [Scheme a b] (Constraint a b)+ | CInstance SchemeName a+++data Scheme a b = Scheme+ { rigidQuantifiers :: [b]+ , flexibleQuantifiers :: [b]+ , constraint :: Constraint a b+ , header :: Map.Map String a+ }+++type TypeConstraint = Constraint Type Variable++type TypeScheme = Scheme Type Variable+++monoscheme :: Map.Map String a -> Scheme a b+monoscheme headers = Scheme [] [] (noneNoDocs CTrue) headers+++infixl 8 /\++(/\) :: Constraint a b -> Constraint a b -> Constraint a b+a@(A _ c1) /\ b@(A _ c2) =+ case (c1, c2) of+ (CTrue, _) -> b+ (_, CTrue) -> a+ _ -> mergeOldDocs a b (CAnd [a,b])+++infixr 9 ==>++(==>) :: Type -> Type -> Type+a ==> b = termN (Fun1 a b)+++(<|) :: TermN a -> TermN a -> TermN a+f <| a = termN (App1 f a)+++data Descriptor = Descriptor+ { structure :: Maybe (Term1 Variable)+ , rank :: Int+ , flex :: Flex+ , name :: Maybe TypeName+ , copy :: Maybe Variable+ , mark :: Int+ , alias :: Maybe Var.Canonical+ }+++noRank :: Int+noRank = -1+++outermostRank :: Int+outermostRank = 0+++noMark :: Int+noMark = 0+++initialMark :: Int+initialMark = 1+++data Flex+ = Rigid+ | Flexible+ | Constant+ | Is SuperType+ deriving (Eq)+++data SuperType+ = Number+ | Comparable+ | Appendable+ deriving (Eq)+++namedVar :: Flex -> Var.Canonical -> IO Variable+namedVar flex name = UF.fresh $ Descriptor+ { structure = Nothing+ , rank = noRank+ , flex = flex+ , name = Just name+ , copy = Nothing+ , mark = noMark+ , alias = Nothing+ }+++variable :: Flex -> IO Variable+variable flex = UF.fresh $ Descriptor+ { structure = Nothing+ , rank = noRank+ , flex = flex+ , name = Nothing+ , copy = Nothing+ , mark = noMark+ , alias = Nothing+ }+++-- ex qs constraint == exists qs. constraint+ex :: [Variable] -> TypeConstraint -> TypeConstraint+ex fqs constraint@(A ann _) =+ A ann $ CLet [Scheme [] fqs constraint Map.empty] (A ann CTrue)+++-- fl qs constraint == forall qs. constraint+fl :: [Variable] -> TypeConstraint -> TypeConstraint+fl rqs constraint@(A ann _) =+ A ann $ CLet [Scheme rqs [] constraint Map.empty] (A ann CTrue)+++exists :: Error e => (Type -> ErrorT e IO TypeConstraint) -> ErrorT e IO TypeConstraint+exists f =+ do v <- liftIO $ variable Flexible+ ex [v] <$> f (varN v)+++existsNumber :: Error e => (Type -> ErrorT e IO TypeConstraint) -> ErrorT e IO TypeConstraint+existsNumber f =+ do v <- liftIO $ variable (Is Number)+ ex [v] <$> f (varN v)+++-- TYPES TO PRETTY STRINGS++instance PrettyType a => PrettyType (UF.Point a) where+ pretty when point =+ unsafePerformIO $ fmap (pretty when) (UF.descriptor point)+++instance PrettyType t => PrettyType (Annotated a t) where+ pretty when (A _ e) =+ pretty when e+++instance PrettyType a => PrettyType (Term1 a) where+ pretty when term =+ let prty = pretty Never in+ case term of+ App1 f x ->+ parensIf needed (px <+> pretty App x)+ where+ px = prty f+ needed = case when of+ App -> True+ _ -> False++ Fun1 arg body ->+ parensIf needed (pretty Fn arg <+> P.text "->" <+> prty body)+ where+ needed = case when of+ Never -> False+ _ -> True++ Var1 x -> prty x++ EmptyRecord1 -> P.braces P.empty++ Record1 fields ext ->+ P.braces (extend <+> commaSep prettyFields)+ where+ prettyExt = prty ext+ extend | P.render prettyExt == "{}" = P.empty+ | otherwise = prettyExt <+> P.text "|"+ mkPretty f t = P.text f <+> P.text ":" <+> prty t+ prettyFields = concatMap (\(f,ts) -> map (mkPretty f) ts) (Map.toList fields)+++instance PrettyType a => PrettyType (TermN a) where+ pretty when term =+ case term of+ VarN alias x -> either alias (pretty when x)+ TermN alias t1 -> either alias (pretty when t1)+ where+ either maybeAlias doc =+ case maybeAlias of+ Just alias -> PP.pretty alias+ Nothing -> doc+++instance PrettyType Descriptor where+ pretty when desc = do+ case (alias desc, structure desc, name desc) of+ (Just name, _, _) -> PP.pretty name+ (_, Just term, _) -> pretty when term+ (_, _, Just name)+ | Var.isTuple name ->+ P.parens . P.text $ replicate (read (drop 6 (Var.toString name)) - 1) ','+ | otherwise ->+ P.text (Var.toString name)+ + _ -> P.text "?"+++instance (PrettyType a, PrettyType b) => PrettyType (BasicConstraint a b) where+ pretty _ constraint =+ let prty = pretty Never in+ case constraint of+ CTrue -> P.text "True"+ CSaveEnv -> P.text "SaveTheEnvironment!!!"+ CEqual a b -> prty a <+> P.text "=" <+> prty b+ CAnd [] -> P.text "True"++ CAnd cs ->+ P.parens . P.sep $ P.punctuate (P.text " and") (map (pretty Never) cs)++ CLet [Scheme [] fqs constraint header] (A _ CTrue) | Map.null header ->+ P.sep [ binder, pretty Never c ]+ where+ mergeExists vs (A _ c) =+ case c of+ CLet [Scheme [] fqs' c' _] (A _ CTrue) -> mergeExists (vs ++ fqs') c'+ _ -> (vs, c)++ (fqs', c) = mergeExists fqs constraint++ binder = if null fqs' then P.empty else+ P.text "\x2203" <+> P.hsep (map (pretty Never) fqs') <> P.text "."++ CLet schemes constraint ->+ P.fsep [ P.hang (P.text "let") 4 (P.brackets . commaSep $ map (pretty Never) schemes)+ , P.text "in", pretty Never constraint ]++ CInstance name tipe ->+ P.text name <+> P.text "<" <+> prty tipe+++instance (PrettyType a, PrettyType b) => PrettyType (Scheme a b) where+ pretty _ (Scheme rqs fqs (A _ constraint) headers) =+ P.sep [ forall, cs, headers' ]+ where+ prty = pretty Never++ forall = if null rqs && null fqs then P.empty else+ P.text "\x2200" <+> frees <+> rigids++ frees = P.hsep $ map prty fqs+ rigids = if null rqs then P.empty else P.braces . P.hsep $ map prty rqs++ cs = case constraint of+ CTrue -> P.empty+ CAnd [] -> P.empty+ _ -> P.brackets (pretty Never constraint)++ headers' = if Map.size headers > 0 then dict else P.empty+ dict = P.parens . commaSep . map prettyPair $ Map.toList headers+ prettyPair (n,t) = P.text n <+> P.text ":" <+> pretty Never t+++extraPretty :: (PrettyType t, Crawl t) => t -> IO Doc+extraPretty t = pretty Never <$> addNames t+++-- ADDING NAMES TO TYPES++addNames :: (Crawl t) => t -> IO t+addNames value = do+ (rawVars, _, _, _) <- execStateT (crawl getNames value) ([], 0, 0, 0)+ let vars = map head . List.group $ List.sort rawVars+ suffix s = map (++s) (map (:[]) ['a'..'z'])+ allVars = concatMap suffix $ ["","'","_"] ++ map show [ (0 :: Int) .. ]+ okayVars = filter (`notElem` vars) allVars+ runStateT (crawl rename value) (okayVars, 0, 0, 0)+ return value+ where+ getNames desc state@(vars, a, b, c) =+ let name' = name desc in+ case name' of+ Just (Var.Canonical _ var) -> (name', (var:vars, a, b, c))+ _ -> (name', state)++ rename desc state@(vars, a, b, c) =+ case name desc of+ Just var -> (Just var, state)+ Nothing ->+ case flex desc of+ Is Number -> (local $ "number" ++ replicate a '\'', (vars, a+1, b, c))+ Is Comparable -> (local $ "comparable" ++ replicate b '\'', (vars, a, b+1, c))+ Is Appendable -> (local $ "appendable" ++ replicate c '\'', (vars, a, b, c+1))+ _ -> (local $ head vars, (tail vars, a, b, c))+ where+ local v = Just (Var.local v)+++-- CRAWLING OVER TYPES++type CrawlState = ([String], Int, Int, Int)+++-- Code for traversing all the type data-structures and giving+-- names to the variables embedded deep in there.+class Crawl t where+ crawl :: (Descriptor -> CrawlState -> (Maybe TypeName, CrawlState))+ -> t+ -> StateT CrawlState IO t+++instance Crawl e => Crawl (Annotated a e) where+ crawl nextState (A ann e) = A ann <$> crawl nextState e+++instance (Crawl t, Crawl v) => Crawl (BasicConstraint t v) where+ crawl nextState constraint = + let rnm = crawl nextState in+ case constraint of+ CTrue -> return CTrue+ CSaveEnv -> return CSaveEnv+ CEqual a b -> CEqual <$> rnm a <*> rnm b+ CAnd cs -> CAnd <$> crawl nextState cs+ CLet schemes c -> CLet <$> crawl nextState schemes <*> crawl nextState c + CInstance name tipe -> CInstance name <$> rnm tipe+++instance Crawl a => Crawl [a] where+ crawl nextState list = mapM (crawl nextState) list+++instance (Crawl t, Crawl v) => Crawl (Scheme t v) where+ crawl nextState (Scheme rqs fqs c headers) =+ let rnm = crawl nextState+ in+ Scheme+ <$> rnm rqs+ <*> rnm fqs+ <*> crawl nextState c+ <*> return headers+++instance Crawl t => Crawl (TermN t) where+ crawl nextState tipe =+ case tipe of+ VarN a x ->+ VarN a <$> crawl nextState x++ TermN a term ->+ TermN a <$> crawl nextState term+++instance Crawl t => Crawl (Term1 t) where+ crawl nextState term =+ let rnm = crawl nextState in+ case term of+ App1 a b -> App1 <$> rnm a <*> rnm b+ Fun1 a b -> Fun1 <$> rnm a <*> rnm b+ Var1 a -> Var1 <$> rnm a+ EmptyRecord1 -> return EmptyRecord1+ Record1 fields ext ->+ Record1 <$> traverse (mapM rnm) fields <*> rnm ext+++instance Crawl a => Crawl (UF.Point a) where+ crawl nextState point =+ do desc <- liftIO $ UF.descriptor point+ desc' <- crawl nextState desc+ liftIO $ UF.setDescriptor point desc'+ return point+++instance Crawl Descriptor where+ crawl nextState desc =+ do state <- get+ let (name', state') = nextState desc state+ structure' <- traverse (crawl nextState) (structure desc)+ put state'+ return $ desc { name = name', structure = structure' }++ +toSrcType :: Variable -> IO T.CanonicalType+toSrcType var =+ go =<< addNames var+ where+ go v =+ do desc <- UF.descriptor v+ srcType <- maybe (backupSrcType desc) termToSrcType (structure desc)+ return $ maybe srcType (\name -> T.Aliased name srcType) (alias desc)++ backupSrcType desc = + case name desc of+ Just v@(Var.Canonical _ x@(c:_))+ | Char.isLower c -> return (T.Var x)+ | otherwise -> return $ T.Type v++ _ -> error $ concat+ [ "Problem converting the following type "+ , "from a type-checker type to a source-syntax type:"+ , P.render (pretty Never var) ]++ termToSrcType term =+ case term of+ App1 a b -> do+ a' <- go a+ b' <- go b+ case a' of+ T.App f args -> return $ T.App f (args ++ [b'])+ _ -> return $ T.App a' [b']++ Fun1 a b -> T.Lambda <$> go a <*> go b++ Var1 a -> go a++ EmptyRecord1 -> return $ T.Record [] Nothing++ Record1 tfields extension -> do+ fields' <- traverse (mapM go) tfields+ let fields = concat [ map ((,) name) ts | (name,ts) <- Map.toList fields' ]+ ext' <- dealias <$> go extension+ return $ case ext' of+ T.Record fs ext -> T.Record (fs ++ fields) ext+ T.Var _ -> T.Record fields (Just ext')+ _ -> error "Used toSrcType on a type that is not well-formed"++ dealias :: T.CanonicalType -> T.CanonicalType+ dealias t =+ case t of+ T.Aliased _ t' -> t'+ _ -> t+++data AppStructure = List Variable | Tuple [Variable] | Other+++collectApps :: Variable -> IO AppStructure+collectApps var = go [] var+ where+ go vars var = do+ desc <- UF.descriptor var+ case (structure desc, vars) of+ (Nothing, [v]) ->+ case name desc of+ Just n | Var.isList n -> return (List v)+ _ -> return Other++ (Nothing, vs) ->+ case name desc of+ Just n | Var.isTuple n -> return (Tuple vs)+ _ -> return Other++ (Just term, _) ->+ case term of+ App1 a b -> go (vars ++ [b]) a+ _ -> return Other
+ src/Type/Unify.hs view
@@ -0,0 +1,348 @@+module Type.Unify (unify) where++import Control.Applicative ((<|>))+import Control.Monad.State+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.UnionFind.IO as UF+import qualified AST.Annotation as A+import qualified AST.Variable as Var+import qualified Type.State as TS+import Type.Type+import Type.PrettyPrint+import Text.PrettyPrint (render)+import Elm.Utils ((|>))+++unify :: A.Region -> Variable -> Variable -> StateT TS.SolverState IO ()+unify region variable1 variable2 = do+ equivalent <- liftIO $ UF.equivalent variable1 variable2+ if equivalent+ then return ()+ else actuallyUnify region variable1 variable2+++actuallyUnify :: A.Region -> Variable -> Variable -> StateT TS.SolverState IO ()+actuallyUnify region variable1 variable2 = do+ desc1 <- liftIO $ UF.descriptor variable1+ desc2 <- liftIO $ UF.descriptor variable2+ let unify' = unify region++ (name', flex', rank', alias') = combinedDescriptors desc1 desc2++ merge1 :: StateT TS.SolverState IO ()+ merge1 = liftIO $ do+ if rank desc1 < rank desc2 then UF.union variable2 variable1+ else UF.union variable1 variable2+ UF.modifyDescriptor variable1 $ \desc ->+ desc { structure = structure desc1+ , flex = flex'+ , name = name'+ , alias = alias'+ }++ merge2 :: StateT TS.SolverState IO ()+ merge2 = liftIO $ do+ if rank desc1 < rank desc2 then UF.union variable2 variable1+ else UF.union variable1 variable2+ UF.modifyDescriptor variable2 $ \desc ->+ desc { structure = structure desc2+ , flex = flex'+ , name = name'+ , alias = alias'+ }++ merge = if rank desc1 < rank desc2 then merge1 else merge2++ fresh :: Maybe (Term1 Variable) -> StateT TS.SolverState IO Variable+ fresh structure = do+ v <- liftIO . UF.fresh $ Descriptor+ { structure = structure+ , rank = rank'+ , flex = flex'+ , name = name'+ , copy = Nothing+ , mark = noMark+ , alias = alias'+ }+ TS.register v++ flexAndUnify v = do+ liftIO $ UF.modifyDescriptor v $ \desc -> desc { flex = Flexible }+ unify' variable1 variable2++ unifyNumber svar (Var.Canonical home name) =+ case home of+ Var.BuiltIn | name `elem` ["Int","Float"] -> flexAndUnify svar+ Var.Local | List.isPrefixOf "number" name -> flexAndUnify svar+ _ ->+ let hint = "Looks like something besides an Int or Float is being used as a number."+ in+ TS.addError region (Just hint) variable1 variable2++ comparableError maybe =+ TS.addError region (Just $ Maybe.fromMaybe msg maybe) variable1 variable2+ where+ msg =+ "Looks like you want something comparable, but the only valid comparable\n\+ \types are Int, Float, Char, String, lists, or tuples."++ appendableError maybe =+ TS.addError region (Just $ Maybe.fromMaybe msg maybe) variable1 variable2+ where+ msg =+ "Looks like you want something appendable, but the only Strings, Lists,\n\+ \and Text can be appended with the (++) operator."++ unifyComparable v (Var.Canonical home name) =+ case home of+ Var.BuiltIn | name `elem` ["Int","Float","Char","String"] -> flexAndUnify v+ Var.Local | List.isPrefixOf "comparable" name -> flexAndUnify v+ _ -> comparableError Nothing++ unifyComparableStructure varSuper varFlex =+ do struct <- liftIO $ collectApps varFlex+ case struct of+ Other -> comparableError Nothing+ List v -> do flexAndUnify varSuper+ unify' v =<< liftIO (variable $ Is Comparable)+ Tuple vs+ | length vs > 6 ->+ comparableError $ Just "Cannot compare a tuple with more than 6 elements."+ | otherwise -> + do flexAndUnify varSuper+ cmpVars <- liftIO $ forM [1..length vs] $ \_ -> variable (Is Comparable)+ zipWithM_ unify' vs cmpVars++ unifyAppendable varSuper varFlex =+ do struct <- liftIO $ collectApps varFlex+ case struct of+ List _ -> flexAndUnify varSuper+ _ -> appendableError Nothing++ rigidError var =+ TS.addError region (Just hint) variable1 variable2+ where+ hint =+ "Could not unify rigid type variable '" ++ render (pretty Never var) ++ "'.\n" +++ "The problem probably relates to the type variable being shared between a\n\+ \top-level type annotation and a related let-bound type annotation."++ superUnify =+ case (flex desc1, flex desc2, name desc1, name desc2) of+ (Is super1, Is super2, _, _)+ | super1 == super2 -> merge+ (Is Number, Is Comparable, _, _) -> merge1+ (Is Comparable, Is Number, _, _) -> merge2+ + (Is Number, _, _, Just name) -> unifyNumber variable1 name+ (_, Is Number, Just name, _) -> unifyNumber variable2 name++ (Is Comparable, _, _, Just name) -> unifyComparable variable1 name+ (_, Is Comparable, Just name, _) -> unifyComparable variable2 name+ (Is Comparable, _, _, _) -> unifyComparableStructure variable1 variable2+ (_, Is Comparable, _, _) -> unifyComparableStructure variable2 variable1++ (Is Appendable, _, _, Just name)+ | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable1+ (_, Is Appendable, Just name, _)+ | Var.isText name || Var.isPrim "String" name -> flexAndUnify variable2+ (Is Appendable, _, _, _) -> unifyAppendable variable1 variable2+ (_, Is Appendable, _, _) -> unifyAppendable variable2 variable1++ (Rigid, _, _, _) -> rigidError variable1+ (_, Rigid, _, _) -> rigidError variable2+ _ -> TS.addError region Nothing variable1 variable2++ case (structure desc1, structure desc2) of+ (Nothing, Nothing) | flex desc1 == Flexible && flex desc1 == Flexible -> merge+ (Nothing, _) | flex desc1 == Flexible -> merge2+ (_, Nothing) | flex desc2 == Flexible -> merge1++ (Just (Var1 v), _) -> unify' v variable2+ (_, Just (Var1 v)) -> unify' v variable1++ (Nothing, _) -> superUnify+ (_, Nothing) -> superUnify++ (Just type1, Just type2) ->+ case (type1,type2) of+ (App1 term1 term2, App1 term1' term2') ->+ do merge+ unify' term1 term1'+ unify' term2 term2'+ (Fun1 term1 term2, Fun1 term1' term2') ->+ do merge+ unify' term1 term1'+ unify' term2 term2'++ (EmptyRecord1, EmptyRecord1) ->+ return ()++ (Record1 fields ext, EmptyRecord1) | Map.null fields -> unify' ext variable2+ (EmptyRecord1, Record1 fields ext) | Map.null fields -> unify' ext variable1++ (Record1 _ _, Record1 _ _) ->+ recordUnify region fresh variable1 variable2++ _ -> TS.addError region Nothing variable1 variable2+++-- RECORD UNIFICATION++recordUnify+ :: A.Region+ -> (Maybe (Term1 Variable) -> StateT TS.SolverState IO Variable)+ -> Variable+ -> Variable+ -> StateT TS.SolverState IO ()+recordUnify region fresh variable1 variable2 =+ do (ExpandedRecord fields1 ext1) <- liftIO (gatherFields variable1)+ (ExpandedRecord fields2 ext2) <- liftIO (gatherFields variable2)++ unifyOverlappingFields region fields1 fields2++ let freshRecord fields ext =+ fresh (Just (Record1 fields ext))++ let uniqueFields1 = diffFields fields1 fields2+ let uniqueFields2 = diffFields fields2 fields1++ let addFieldMismatchError missingFields =+ let msg = fieldMismatchError missingFields+ in+ TS.addError region (Just msg) variable1 variable2++ case (ext1, ext2) of+ (Empty _, Empty _) ->+ case Map.null uniqueFields1 && Map.null uniqueFields2 of+ True -> return ()+ False -> TS.addError region Nothing variable1 variable2++ (Empty var1, Extension var2) ->+ case (Map.null uniqueFields1, Map.null uniqueFields2) of+ (_, False) -> addFieldMismatchError uniqueFields2+ (True, True) -> unify region var1 var2+ (False, True) ->+ do subRecord <- freshRecord uniqueFields1 var1+ unify region subRecord var2++ (Extension var1, Empty var2) ->+ case (Map.null uniqueFields1, Map.null uniqueFields2) of+ (False, _) -> addFieldMismatchError uniqueFields1+ (True, True) -> unify region var1 var2+ (True, False) ->+ do subRecord <- freshRecord uniqueFields2 var2+ unify region var1 subRecord++ (Extension var1, Extension var2) ->+ case (Map.null uniqueFields1, Map.null uniqueFields2) of+ (True, True) ->+ unify region var1 var2++ (True, False) ->+ do subRecord <- freshRecord uniqueFields2 var2+ unify region var1 subRecord++ (False, True) ->+ do subRecord <- freshRecord uniqueFields1 var1+ unify region subRecord var2++ (False, False) ->+ do record1' <- freshRecord uniqueFields1 =<< fresh Nothing+ record2' <- freshRecord uniqueFields2 =<< fresh Nothing+ unify region record1' var2+ unify region var1 record2'+++unifyOverlappingFields+ :: A.Region+ -> Map.Map String [Variable]+ -> Map.Map String [Variable]+ -> StateT TS.SolverState IO ()+unifyOverlappingFields region fields1 fields2 =+ Map.intersectionWith (zipWith (unify region)) fields1 fields2+ |> Map.elems + |> concat+ |> sequence_+++diffFields :: Map.Map String [a] -> Map.Map String [a] -> Map.Map String [a]+diffFields fields1 fields2 =+ let eat (_:xs) (_:ys) = eat xs ys+ eat xs _ = xs+ in+ Map.union (Map.intersectionWith eat fields1 fields2) fields1+ |> Map.filter (not . null)+++data ExpandedRecord = ExpandedRecord+ { _fields :: Map.Map String [Variable]+ , _extension :: Extension+ }++data Extension = Empty Variable | Extension Variable+++gatherFields :: Variable -> IO ExpandedRecord+gatherFields var =+ do desc <- UF.descriptor var+ case structure desc of+ (Just (Record1 fields ext)) ->+ do (ExpandedRecord deeperFields rootExt) <- gatherFields ext+ return (ExpandedRecord (Map.unionWith (++) fields deeperFields) rootExt)++ (Just EmptyRecord1) ->+ return (ExpandedRecord Map.empty (Empty var))++ _ ->+ return (ExpandedRecord Map.empty (Extension var))+++-- assumes that one of the dicts has stuff in it+fieldMismatchError :: Map.Map String a -> String+fieldMismatchError missingFields =+ case Map.keys missingFields of+ [] -> ""+ [key] ->+ "Looks like a record is missing the field '" ++ key ++ "'.\n " +++ "Maybe there is a misspelling in a record access or record update?"+ keys ->+ "Looks like one record is missing fields "+ ++ List.intercalate ", " (init keys) ++ ", and " ++ last keys+++combinedDescriptors :: Descriptor -> Descriptor+ -> (Maybe Var.Canonical, Flex, Int, Maybe Var.Canonical)+combinedDescriptors desc1 desc2 =+ (name', flex', rank', alias')+ where+ rank' :: Int+ rank' = min (rank desc1) (rank desc2)++ alias' :: Maybe Var.Canonical+ alias' = alias desc1 <|> alias desc2++ name' :: Maybe Var.Canonical+ name' = case (name desc1, name desc2) of+ (Just name1, Just name2) ->+ case (flex desc1, flex desc2) of+ (_, Flexible) -> Just name1+ (Flexible, _) -> Just name2+ (Is Number, Is _) -> Just name1+ (Is _, Is Number) -> Just name2+ (Is _, Is _) -> Just name1+ (_, _) -> Nothing+ (Just name1, _) -> Just name1+ (_, Just name2) -> Just name2+ _ -> Nothing++ flex' :: Flex+ flex' = case (flex desc1, flex desc2) of+ (f, Flexible) -> f+ (Flexible, f) -> f+ (Is Number, Is _) -> Is Number+ (Is _, Is Number) -> Is Number+ (Is super, Is _) -> Is super+ (_, _) -> Flexible
+ tests/Test.hs view
@@ -0,0 +1,14 @@+module Main where++import Test.Framework++import Test.Compiler+import Test.Property+++main :: IO () +main =+ defaultMain+ [ compilerTests+ , propertyTests+ ]
+ tests/Test/Compiler.hs view
@@ -0,0 +1,73 @@+{-# OPTIONS_GHC -W #-}+module Test.Compiler (compilerTests) where++import qualified Data.Map as Map+import Data.Traversable (traverse)++import System.FilePath ((</>))+import System.FilePath.Find (find, (==?), extension)++import Test.Framework+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion, assertFailure, assertBool)++import qualified Elm.Compiler as Compiler+import qualified Elm.Compiler.Module as Module+++compilerTests :: Test+compilerTests =+ buildTest $ do+ goods <- testIf isSuccess =<< getElms "good"+ bads <- testIf isFailure =<< getElms "bad"+ return $+ testGroup "Compile Tests"+ [ testGroup "Good Tests" goods+ , testGroup "Bad Tests" bads+ ]+++-- GATHER ELM FILES++getElms :: FilePath -> IO [FilePath]+getElms filePath =+ find+ (return True)+ (extension ==? ".elm")+ (testsDir </> filePath)+++testsDir :: FilePath+testsDir =+ "tests" </> "compiler" </> "test-files"+++-- RUN COMPILER++testIf+ :: (Either String (Module.Interface, String) -> Assertion)+ -> [FilePath]+ -> IO [Test]+testIf handleResult filePaths =+ traverse setupTest filePaths+ where+ setupTest filePath =+ do source <- readFile filePath+ let result = Compiler.compile "elm-lang" "core" source Map.empty+ return (testCase filePath (handleResult result))+++-- CHECK RESULTS++isSuccess :: Either String a -> Assertion+isSuccess result =+ case result of+ Right _ -> assertBool "" True+ Left msg -> assertFailure msg+++isFailure :: Either a b -> Assertion+isFailure result =+ case result of+ Left _ -> assertFailure "Compilation succeeded but should have failed"+ Right _ -> assertBool "" True
+ tests/Test/Property.hs view
@@ -0,0 +1,68 @@+module Test.Property where++import Control.Applicative ((<*))+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import Test.HUnit (assert)+import Test.QuickCheck+import Text.Parsec.Combinator (eof)+import Text.PrettyPrint as P++import AST.Literal as Lit+import qualified AST.Pattern as P+import qualified AST.Variable as V+import AST.PrettyPrint (Pretty, pretty)+import Parse.Helpers (IParser, iParse)+import Parse.Literal (literal)+import qualified Parse.Pattern as Pat (expr)+import qualified Parse.Type as Type (expr)+import Test.Property.Arbitrary+++propertyTests :: Test+propertyTests =+ testGroup "Parse/Print Agreement Tests"+ [ testCase "Long Pattern test" $ assert (prop_parse_print Pat.expr longPat)+ , testProperty "Literal test" $ prop_parse_print literal+ , testProperty "Pattern test" $ prop_parse_print Pat.expr+ , testProperty "Type test" $ prop_parse_print Type.expr+ ]++ where+ -- This test was autogenerated from the Pattern test and should be+ -- left in all its ugly glory.+ longPat = P.Data (V.Raw "I")+ [ P.Literal (Lit.Chr '+')+ , P.Record+ [ "q7yclkcm7k_ikstrczv_"+ , "wQRv6gKsvvkjw4b5F"+ ,"c9'eFfhk9FTvsMnwF_D"+ ,"yqxhEkHvRFwZ"+ ,"o"+ ,"nbUlCn3y3NnkVoxhW"+ ,"iJ0MNy3KZ_lrs"+ ,"ug"+ ,"sHHsX"+ ,"mRKs9d"+ ,"o2KiCX5'ZRzHJfRi8" ]+ , P.Var "su'BrrbPUK6I33Eq"+ ]+++prop_parse_print :: (Pretty a, Arbitrary a, Eq a) => IParser a -> a -> Bool+prop_parse_print parser value =+ either (const False) (== value) (parse_print parser value)+++parse_print :: (Pretty a) => IParser a -> a -> Either String a+parse_print parser value =+ let doc =+ pretty value++ string =+ P.renderStyle P.style { mode = P.LeftMode } doc+ in+ case iParse (parser <* eof) string of+ Left msg -> Left (show msg)+ Right x -> Right x
+ tests/Test/Property/Arbitrary.hs view
@@ -0,0 +1,137 @@+{-# OPTIONS_GHC -W -fno-warn-orphans #-}+module Test.Property.Arbitrary where++import Control.Applicative ((<$>), (<*>), pure)+import Test.QuickCheck.Arbitrary+import Test.QuickCheck.Gen++import qualified Data.Set as Set+import qualified Parse.Helpers (reserveds)++import qualified AST.Literal as L+import qualified AST.Pattern as P+import qualified AST.Type as T+import qualified AST.Variable as V++instance Arbitrary V.Raw where+ arbitrary = V.Raw <$> capVar+ shrink (V.Raw v) = V.Raw <$> shrink v++instance Arbitrary L.Literal where+ arbitrary =+ oneof+ [ L.IntNum <$> arbitrary+ , L.FloatNum <$> (arbitrary `suchThat` noE)+ , L.Chr <$> arbitrary+ -- This is too permissive+ , L.Str <$> arbitrary+ -- Booleans aren't actually source syntax + -- , Boolean <$> arbitrary+ ]++ shrink lit =+ case lit of+ L.IntNum n -> L.IntNum <$> shrink n+ L.FloatNum f -> L.FloatNum <$> (filter noE . shrink $ f)+ L.Chr c -> L.Chr <$> shrink c+ L.Str s -> L.Str <$> shrink s+ L.Boolean b -> L.Boolean <$> shrink b++noE :: Double -> Bool+noE = notElem 'e' . show++genVector :: Int -> (Int -> Gen a) -> Gen [a]+genVector n generator = do+ len <- choose (0,n)+ let m = n `div` (len + 1)+ vectorOf len $ generator m++instance Arbitrary v => Arbitrary (P.Pattern v) where+ arbitrary = sized pat+ where+ pat :: (Arbitrary v) => Int -> Gen (P.Pattern v)+ pat n =+ oneof+ [ pure P.Anything+ , P.Var <$> lowVar+ , P.Record <$> (listOf1 lowVar)+ , P.Literal <$> arbitrary+ , P.Alias <$> lowVar <*> pat (n-1)+ , P.Data <$> arbitrary <*> genVector n pat+ ]++ shrink pat =+ case pat of+ P.Anything -> []+ P.Var v -> P.Var <$> shrinkWHead v+ P.Literal l -> P.Literal <$> shrink l+ P.Alias s p -> p : (P.Alias <$> shrinkWHead s <*> shrink p)+ P.Data s ps -> ps ++ (P.Data <$> shrink s <*> shrink ps)+ P.Record fs ->+ P.Record <$> filter (all notNull) (filter notNull (shrink fs))+ where+ notNull = not . null++shrinkWHead :: Arbitrary a => [a] -> [[a]]+shrinkWHead [] = error "Should be nonempty"+shrinkWHead (x:xs) = (x:) <$> shrink xs++instance Arbitrary v => Arbitrary (T.Type v) where+ arbitrary = sized tipe+ where+ tipe :: Arbitrary v => Int -> Gen (T.Type v)+ tipe n =+ let depthTipe = tipe =<< choose (0,n)+ field = (,) <$> lowVar <*> depthTipe+ fields = genVector n (\m -> (,) <$> lowVar <*> tipe m)+ fields1 = (:) <$> field <*> fields+ depthTipes = (:) <$> depthTipe <*> genVector n tipe+ in+ oneof+ [ T.Lambda <$> depthTipe <*> depthTipe+ , T.Var <$> lowVar+ , T.Type <$> arbitrary+ , T.App <$> (T.Type <$> arbitrary) <*> depthTipes+ , T.Record <$> fields <*> pure Nothing+ , T.Record <$> fields1 <*> (Just . T.Var <$> lowVar)+ ]++ shrink tipe =+ case tipe of+ T.Lambda s t -> s : t : (T.Lambda <$> shrink s <*> shrink t)+ T.Var _ -> []+ T.Aliased v t -> t : (T.Aliased v <$> shrink t)+ T.Type v -> T.Type <$> shrink v+ T.App t ts -> t : ts ++ (T.App <$> shrink t <*> shrink ts)+ T.Record fs t -> map snd fs ++ record+ where+ record =+ case t of+ Nothing -> T.Record <$> shrinkList shrinkField fs <*> pure Nothing+ Just _ ->+ do fields <- filter (not . null) $ shrinkList shrinkField fs+ return $ T.Record fields t++ shrinkField (n,t) = (,) <$> shrinkWHead n <*> shrink t++lowVar :: Gen String+lowVar = notReserved $ (:) <$> lower <*> listOf varLetter+ where+ lower = elements ['a'..'z']++capVar :: Gen String+capVar = notReserved $ (:) <$> upper <*> listOf varLetter+ where+ upper = elements ['A'..'Z']++varLetter :: Gen Char+varLetter = elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['\'', '_']++notReserved :: Gen String -> Gen String+notReserved = flip exceptFor Parse.Helpers.reserveds++exceptFor :: (Ord a) => Gen a -> [a] -> Gen a+exceptFor g xs = g `suchThat` notAnX+ where+ notAnX = flip Set.notMember xset+ xset = Set.fromList xs