Full Code of wende/elmchemy for AI

master 21650416e634 cached
70 files
215.4 KB
53.4k tokens
29 symbols
1 requests
Download .txt
Showing preview only (232K chars total). Download the full file or copy to clipboard to get everything.
Repository: wende/elmchemy
Branch: master
Commit: 21650416e634
Files: 70
Total size: 215.4 KB

Directory structure:
gitextract_du00lrkm/

├── .gitattributes
├── .gitignore
├── .gitmodules
├── .npmrc
├── .tool-versions
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE
├── LICENSE
├── Main.elm
├── Makefile
├── README.md
├── book.json
├── bump.sh
├── elchemy
├── elchemy_ex/
│   ├── .gitignore
│   ├── README.md
│   ├── config/
│   │   └── config.exs
│   ├── elm/
│   │   └── Hello.elm
│   ├── elm-package.json
│   ├── mix.exs
│   └── test/
│       ├── elchemy_ex_test.exs
│       └── test_helper.exs
├── elchemy_node.js
├── elm-package.json
├── elmchemy
├── lib/
│   └── mix/
│       ├── mix.exs
│       └── tasks/
│           └── compile.elchemy.ex
├── mix.exs
├── package.json
├── roadmap/
│   ├── BASIC_TYPES.md
│   ├── COMMENTS.md
│   ├── FLAGS.md
│   ├── FUNCTIONS.md
│   ├── INLINING.md
│   ├── INSTALLATION.md
│   ├── INTEROP.md
│   ├── MODULES.md
│   ├── README.md
│   ├── SIDE_EFFECTS.md
│   ├── STRUCTURES.md
│   ├── SUMMARY.md
│   ├── SYNTAX.md
│   ├── TESTING.md
│   ├── TROUBLESHOOTING.md
│   ├── TYPES.md
│   └── TYPE_ALIASES.md
├── src/
│   └── Elchemy/
│       ├── Alias.elm
│       ├── Ast.elm
│       ├── Compiler.elm
│       ├── Context.elm
│       ├── Expression.elm
│       ├── Ffi.elm
│       ├── Function.elm
│       ├── Helpers.elm
│       ├── Meta.elm
│       ├── Operator.elm
│       ├── Selector.elm
│       ├── Statement.elm
│       ├── Type.elm
│       └── Variable.elm
├── templates/
│   ├── Hello.elm
│   ├── elchemy.exs
│   ├── elchemy_test.exs
│   └── elm-package.json
└── tests/
    ├── .gitignore
    ├── Main.elm
    ├── Tests.elm
    ├── UnitTests.elm
    └── elm-package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
stable/* linguist-vendored
example/* linguist-vendored


================================================
FILE: .gitignore
================================================
.DS_Store
elm-stuff/
node_modules/
elchemy.js
output.ex
example/elm.js
.#*
.elchemy*
*.orig
elixir-stuff/
elchemy_ex/lib/*
*un~
elchemy-*.ez
_build/
docs/
example/
stable/
_book/
.elixir_ls/
.vscode/
test_project/


================================================
FILE: .gitmodules
================================================
[submodule "elchemy-core"]
	path = elchemy-core
	url = https://github.com/wende/elchemy-core.git


================================================
FILE: .npmrc
================================================
tag-version-prefix = ""

================================================
FILE: .tool-versions
================================================
elm 0.18.0
elixir 1.7.4


================================================
FILE: .travis.yml
================================================
language: elixir
elixir:
  - 1.5.0
  - 1.6.4
  - 1.7.0
  - 1.7.1
  - 1.7.3
otp_release:
  - 20.0
  - 21.0
matrix:
  exclude:
  # Elixir 1.5.0 doesn't work with OTP 21
  - elixir: 1.5.0
    otp_release: 21.0
  # Elixir 1.6.4 doesn't work with OTP 21
  - elixir: 1.6.4
    otp_release: 21.0
os:
  - linux
cache:
  directories:
    - test/elm-stuff/build-artifacts
    - sysconfcpus

before_install:
  - if [ ${TRAVIS_OS_NAME} == "osx" ];
    then brew update; brew install nvm; mkdir ~/.nvm; export NVM_DIR=~/.nvm; source $(brew --prefix nvm)/nvm.sh;
    fi
  - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
  - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142
    if [ ! -d sysconfcpus/bin ];
    then
      git clone https://github.com/obmarg/libsysconfcpus.git;
      cd libsysconfcpus;
      ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus;
      make && make install;
      cd ..;
    fi

install:
  - nvm install 6.11.2
  - nvm use 6.11.2
  - node --version
  - npm --version
  - npm install -g elm@0.18.0
  - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old
  - printf '%s\n\n' '#!/bin/bash' 'echo "Running elm-make with sysconfcpus -n 2"' '$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old "$@"' > $(npm config get prefix)/bin/elm-make
  - chmod +x $(npm config get prefix)/bin/elm-make
  - npm install
  - elm package install --yes
  - cd elchemy-core/
  - mix local.rebar --force # for Elixir 1.3.0 and up
  - mix local.hex --force
  - mix deps.get
  - cd ../
  - make compile
  - make compile-std

after_failure:
  - find elchemy-core/lib | xargs cat

script:
  - make test-all

notifications:
  email: false


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at krzysztof.wende@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: ISSUE_TEMPLATE
================================================
### Check first
If anything doesn't work, try
```
npm install -g elchemy
elchemy clean
elchemy init
mix test
```

To experiment with the latest Elchemy version of the parser online go to
https://wende.github.io/elchemy/stable/

### Template: 
Add:

## Example:
```elm

```
->
```elixir

```


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Krzysztof Wende

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Main.elm
================================================
port module Main exposing (main)

import Html exposing (..)
import Html
import Elchemy.Compiler as Compiler
import Markdown


type Msg
    = Replace String
    | String


view : String -> Html Msg
view model =
    Markdown.toHtml [] <| "```elixir\n" ++ (Compiler.tree model) ++ "\n```"


init : String -> ( String, Cmd Msg )
init v =
    ( v, Cmd.none )


main : Program String String Msg
main =
    Html.programWithFlags
        { init = init
        , update = update
        , view = view
        , subscriptions =
            (\_ ->
                Sub.batch
                    [ updateInput Replace
                    ]
            )
        }


update : Msg -> String -> ( String, Cmd Msg )
update action model =
    case action of
        Replace m ->
            ( m, Cmd.none )

        String ->
            ( "", Cmd.none )


port updateInput : (String -> msg) -> Sub msg


================================================
FILE: Makefile
================================================
dev:
	pkill -f http-server &
	echo "Make sure to install http-server with npm i -g http-server"
	elm-make Main.elm --output=example/elm.js --debug
	(http-server ./example/ -p 8081 -c-1 &) && open "http://127.0.0.1:8081"

release:
	elm-make Main.elm --output=example/elm.js
	mkdir -p docs/stable
	cp -r example/ docs/stable/

compile:
	elm-make Main.elm --yes --output compiled.js
	sed 's/var Elm = {}/&; \
	require(".\/elchemy_node.js").execute(_user$$project$$Elchemy_Compiler$$tree, _user$$project$$Elchemy_Compiler$$fullTree, _user$$project$$Elchemy_Compiler$$treeAndCommons)/' compiled.js > elchemy.js
	rm compiled.js

compile-watch:
	find . -name "*.elm" | grep -v "elm-stuff" | grep -v .# | entr make compile

test:
	./node_modules/.bin/elm-test

test-all:
	make test
	make test-std
	make compile-elixir # Change to compile-elixir-and-test when let..in fixed completely
	make test-project

test-project:
	rm -rf test_project
	mix new test_project
	cd test_project ; \
		(yes | ../elchemy init) ;\
		../elchemy compile elm lib  ;\
		cp -r ../elchemy-core/lib lib/elm-deps ;\
		cp -r ../elchemy-core/elm lib/elm-deps-elixir-files ;\
		mix test

test-std:
	cd elchemy-core/ && mix test

compile-std:
	cd elchemy-core && rm -rf .elchemy
	make compile-std-incremental

compile-std-incremental:
	make compile
	cd elchemy-core && ../elchemy compile elm lib

compile-std-watch:
	find elchemy-core -name "*.elm" | grep -v ".#" | grep -v "elm-stuff" | entr make compile-std

compile-std-tests-watch:
	find elchemy-core \( -name "*.elm" -or -name '*.ex*' \) | grep -v "elchemy.ex" | grep -v ".#" | grep -v "elm-stuff" | entr bash -c "make compile && make compile-std && make test-std"

compile-incremental-std-tests-watch:
	find elchemy-core \( -name "*.elm" -or -name '*.ex*' \) | grep -v "elchemy.ex" | grep -v ".#" | grep -v "elm-stuff" | entr bash -c "make compile && make compile-std-incremental && make test-std"

tests-watch:
	find . -name "*.elm" | grep -v ".#" | grep -v "elm-stuff" | entr ./node_modules/.bin/elm-test

install-sysconf:
	git clone "https://github.com/obmarg/libsysconfcpus.git"
	cd libsysconfcpus && ./configure && make && make install
	cd .. && rm -rf libsysconfcpus

compile-elixir:
	make compile
	rm -rf elchemy_ex/elm-deps
	rm -rf elchemy_ex/.elchemy
	cd elchemy_ex && ../elchemy compile ../src lib

compile-elixir-and-run:
	make compile-elixir
	cd elchemy_ex && mix compile

compile-elixir-and-test:
	make compile-elixir
	cd elchemy_ex && mix test

build-docs:
	cd ../elchemy-page && git checkout master && git pull && elm install && yarn && yarn build
	rm -rf docs/*
	cp -r ../elchemy-page/dist/* docs/


================================================
FILE: README.md
================================================

<p align="center">
<img src="https://github.com/wende/elchemy/blob/master/logo.png?raw=true" width="250" height="250">
</p>
<p align="center">
  <a href="https://join.slack.com/t/elchemy-lang/shared_invite/enQtNjMzMDI0NzM3MzQ3LWVmZDZkMzY3OWZkMzJlOGIzZjMzMjcwMDgzNDFlZDYzY2NiYzE0ZjdhOTRmMmMyMjRiOTUzNjhhNWQ1M2VlMGY">
    <img src="https://img.shields.io/badge/chat-on%20slack-blueviolet.svg">
  </a>
  <a href="http://elchemy.neontree.pl">
    <img src="https://img.shields.io/badge/try%20now-online-yellow.svg">
  </a>
  <a href="https://wende.gitbooks.io/elchemy/content/">
    <img src="https://img.shields.io/badge/read-docs-informational.svg">
  </a>
  <a href="https://medium.com/@krzysztof.wende/elmchemy-write-type-safe-elixir-code-with-elms-syntax-part-1-introduction-8968b76d721d">
    <img src="https://img.shields.io/badge/getting-started-green.svg">
  </a>
  <a href="https://travis-ci.org/wende/elchemy">
    <img src="https://travis-ci.org/wende/elchemy.svg?branch=master">
  </a>
 
</p>

#### Quick install

```shell
npm install -g elchemy
```


# What is it?

Elchemy lets you write simple, fast and quality type safe code while leveraging both the Elm's safety and Elixir's ecosystem


### In case of any questions about the project feel free to submit them in Issues with Q&A label


# Features

- **Type inference:** Powerful type inference means you rarely have to annotate types. Everything gets checked for you by the compiler
- **Easy and type-safe interop**: You can call Elixir/Erlang without any extra boiler-plate. All the calls you make are checked in terms of type-safety as thoroughly as possible based on Elixir's typespecs.
- **All the best of Elm and Elixir**: Elchemy inherits what's best in Elm - type safety, inference and extreme expressiveness, but also what's best in Elixir - Doc-tests, tooling and obviously the entire BEAM platform.
- **Nearly no runtime errors** - Elchemy's type system **eliminates almost all runtime errors**. With a shrinking set of edge cases, your entire Elchemy codebase is safe. Elixir parts of the codebase are the only ones to be a suspect in cases of runtime errors happening.
- **Beautiful and fully readable output** - The produced code is idiomatic, performant and can be easily read and analyzed without taking a single look at the original source.


# Maturity of the project

- Parser - **99%** of Elm's syntax (see [elm-ast](https://github.com/Bogdanp/elm-ast/issues))
- Compiler - **90%** (Sophisticated incremental compilation. 
  No support for Windows yet though ([#287](https://github.com/wende/elchemy/issues/287)) and also big reliance on unix tools ([#288](https://github.com/wende/elchemy/issues/288))
- Elchemy-core - **95%** ( Everything covered except side effects and JSON Decoders) 
- Interop with Elixir - **90%** - Purity tests ([#162](https://github.com/wende/elchemy/issues/162)) and handling of macro-heavy libraries ([#276](https://github.com/wende/elchemy/issues/276)) to go 
- Ideology - **70%** - We've got a pretty solid idea of where Elchemy is going 
- Documentation - **80%** - There are two tutorials and a complete Gitbook documentation. Few entrance level tutorials though, this project tries to change it. 
- Elchemy-effects - **20%** - You can't and shouldn't write anything with side-effects in Elchemy yet. We're working on finding the best solution for effects that would fit both Elm's and Elixir's community (see [#297](https://github.com/wende/elchemy/issues/297) for more info)
- Elchemy-core for Erlang VM - **5%** - Everything for os related tasks like filesystem, OTP goodies etc are yet to be done
- Elchemy type checker - **20%** - Self-hosted elchemy type inference algorithm, written by @wende and inspired by @Bogdanp.

# Usage

### Prerequisites

- [elixir@1.4.0-1.6.x](https://elixir-lang.org/install.html)
- [node@5+](https://nodejs.org/en/)
- [elm-lang@0.18.0](https://guide.elm-lang.org/install.html) (`npm install -g elm@0.18.0`)
- [elm-github-install@0.1.2](https://github.com/gdotdesign/elm-github-install) - Compiler will install it automatically for you, if you don't have it yet.


### Installation in an existing Elixir project


Install `elchemy` globally with:

```shell
npm install -g elchemy
```


Then, in the root directory of your project do:

```shell
elchemy init
```

And follow the instructions.

`elchemy` will find all `*.elm` files specified in `elchemy_path` and compile it into corresponding `*.ex` files in `lib` directory.

You can override output directory specifying `elixirc_paths`.


### Installation as a standalone

```shell
npm install -g elchemy
```

Usage

```
elchemy compile source_dir output_dir
```

### Recommended editors setup

- [Atom](https://atom.io/) with [elixir-language](https://atom.io/packages/language-elixir) and [atom-elixir](https://github.com/msaraiva/atom-elixir) or [elixir-ide](https://atom.io/packages/ide-elixir) for Elixir; and [language-elm](https://atom.io/packages/language-elm) + [elmjutsu](https://atom.io/packages/elmjutsu) for Elchemy.
- [Visual Studio Code](https://code.visualstudio.com/) with [vscode-elixir](https://marketplace.visualstudio.com/items?itemName=mjmcloug.vscode-elixir), [vscode-elixir-ls](https://github.com/JakeBecker/vscode-elixir-ls) and [vscode-elm](https://github.com/elm-tooling/elm-language-client-vscode)


### Build from source

```
git clone https://github.com/wende/elchemy.git
cd elchemy
make compile
./elchemy compile source_dir output_dir
```

and

```
make dev
```

In order to launch and test the web demo.


## Troubleshooting

If something doesn't work, try 


```
npm install -g elchemy
elchemy clean
elchemy init
mix test
```

first


# FAQ

## Why *would* I want to use that?

- You like types
- But even more you prefer compile-time errors over run-time error
- You prefer `add b c = b + c` over `defp add(a, b), do: b + c`
- You like curry
- You think failing fast is cool, but not as cool as not failing at all


## Why *wouldn't* I want to use that?

- Your project relies on die-hard battle tested libraries, and you despise any versions starting with 0
- You're afraid that when you learn what Monad is your mustache will grow, and eyesight weaken


## Can I use it in already existing Elixir project?

You can, but nice and dandy compile tools are still on their way


## Will my employer notice I'm having an affair with Elchemy?

The output files of Elchemy treat the code readability as a first class citizen. The code is meant to be properly indented, the comments aren't omitted, and the code is optimized as hard as it can ( f.i case clauses reduce to function overloads)


## When will Elchemy become 1.0.0?

Once it's done, so as it is supposed to be in the so called semantic versioning. :innocent:


## Can I contribute?

Definitely. Yes. Please do. :two_hearts:


## How are types represented?

You're a nosy one, aren't you? :smile: Elchemy represents all type constructors as snake cased atoms, and all type applications as tuples. Which means that `MyType 42 "Forty two" Error` in Elchemy equals to `{:my_type, 42, "Forty Two", :error}` in Elixir.


## Can I use already existing Elm libraries with Elchemy?

As long as they don't use any Native modules, Ports or Elm runtime they can be safely imported and used


## Can I use already existing Elixir libraries with Elchemy?

Yes. You can do an `ffi` call to any function in any module. Whether it's Elixir module, Erlang module, or even a macro you can include it in your code. Ffi calls are a treated specially in Elchemy, and they get generated test to analyze the types based on @specs, so that you don't compromise type safety for using Elixir code. 
In order to increase readability it's advised not to use `ffi` calls if not necessary and always document and doctest them.


## But what about out of function macros? Like tests and `use Module`?

Unfortunately you can't write any macros with `do..end` blocks yet. You can write any out of function code using an elixir inline code with:


```elm
{- ex
  *code_here*
-}
```

But it is a last resort solution and shouldn't ever be abused.


## Can I define an Elixir macro in Elchemy?

So you want to write an Elm-like code, that will manipulate Elixir code, which generates an Elixir code that manipulates Elixir code? How about no?


## Do I need to have Elm installed to compile my `.elm` files with Elchemy?

Elchemy uses Elm to typecheck your program. It is possible to use it without Elm on your machine, while it's not advised.


# Contributor credits:

- Tomasz Cichociński - [@baransu](https://github.com/baransu)
- Colin Bankier - [@colinbankier](https://github.com/colinbankier)
- Nathaniel Knight - [@neganp](https://github.com/neganp)

# Inspiration:

- [Elm](https://github.com/elm/compiler) by Evan Czaplicki - [@evancz](https://github.com/evancz)
- [Elixir](https://github.com/elixir-lang/elixir) by José Valim - [@josevalim](https://github.com/josevalim)
- [Elm-AST](https://github.com/bogdanp/elm-ast) by Bogdan Popa - [@bogdanp](https://github.com/bogdanp) 


# Contributing Guide

- Everyone is welcome to contribute :hugs:
- Refer to https://bogdanp.github.io/elm-ast/example/ to have better understanding of parsed tokens.
- Refer to https://wende.github.io/elchemy/stable/ to know the latest development version of the parser
- For project management we use ZenHub. You can see the Kanban board, card estimates and all the reports by installing a browser extension here: [Opera/Chrome](https://chrome.google.com/webstore/detail/zenhub-for-github/ogcgkffhplmphkaahpmffcafajaocjbd), [Firefox](zenhub.com)


## Targeted values:

- Fully readable and indented elixir code generated from compilation
- Seamless and stress less interop with existing Elixir code, preferably with magically working type safety
- Full integration with entire elm syntax for editors and compilers magic




================================================
FILE: book.json
================================================
{
    "root" : "./roadmap"
}


================================================
FILE: bump.sh
================================================
 #!/bin/bash

 set -e

 if [ -z "$1" ]; then
    echo 'usage ./bump.sh [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]'
    exit 0
fi
if git diff-index --quiet HEAD --; then
    CHANGELOG=`git changelog -x --tag VER`
    npm version $1
    SEMVER='[0-9][0-9]*\.[0-9][0-9]*\.[0-9]*-*[0-9]*'
    VER=`npm ls | grep -o elchemy@$SEMVER | grep -o $SEMVER`
    CHANGELOG=${CHANGELOG/VER/$VER}
    echo "$CHANGELOG"

    make compile-std

    cd elchemy-core

    CORE_BRANCH=`git branch | grep \* | cut -d ' ' -f2`

    sed -i "" "s/$SEMVER/$VER/g" mix.exs

    git pull origin $CORE_BRANCH

    git commit -am "Release $VER"
    git tag $VER

    if ! [[ $* == *-n* ]]; then
      git push origin $CORE_BRANCH $VER
    fi

    cd ..

    ELCHEMY_BRANCH=`git branch | grep \* | cut -d ' ' -f2`

    sed -i "" "s/$SEMVER/$VER/g" mix.exs
    sed -i "" "s/elchemy-core\": \"0.0.0 <= v < $SEMVER/elchemy-core\": \"0.0.0 <= v < $VER/g" ./templates/elm-package.json

    rm -f elchemy-*.ez
    mix archive.build
    mix archive.install "elchemy-$VER.ez" --force

    git pull origin $ELCHEMY_BRANCH
    sed -i "" "s/$SEMVER/$VER/g" src/Elchemy/Compiler.elm
    make compile
    make build-docs
    make release
    git add docs/ -f

    sed -i "" "s/version=\"$SEMVER\"/version=\"$VER\"/g" ./elchemy
    sed -i "" "s/name\": \"elchemy\"/name\": \"elmchemy\"/g" package.json
    if ! [[ $* == *-n* ]]; then
      npm publish
    fi
    sed -i "" "s/name\": \"elmchemy\"/name\": \"elchemy\"/g" package.json

    if ! [[ $* == *-n* ]]; then
      npm publish
    fi

    git tag -d "$VER"
    git commit -am "$CHANGELOG"
    git tag $VER
    if ! [[ $* == *-n* ]]; then
      git push origin $ELCHEMY_BRANCH $VER
      hub release create -p -a "elchemy-$VER.ez" $VER
    fi
else
    echo "Git directory must be clean"
    exit 1
fi


================================================
FILE: elchemy
================================================
#!/bin/bash


version="0.8.8"

if ! $(elm -v 2> /dev/null | grep 0.18 > /dev/null ); then
    echo "Elchemy requires elm 0.18. Install it and make sure it's available system-wide."
    echo "For 0.19 support visit https://github.com/wende/elchemy/issues/348"
    exit 1
fi

set -e

VERBOSE=false
if [[ $* == *--verbose* ]]; then
  VERBOSE=true
fi

function create_file {
    local file=$1
    if [[ ${file} == *"elm-stuff/packages"* ]]; then
        file=${file/elm-stuff\/packages/elm-deps}
    fi
    mkdir -p `dirname $file`
    echo "" > $file
    echo "$file"
}

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
    DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
    SOURCE="$(readlink "$SOURCE")"
    [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SOURCE_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"

case "$1" in
    version)
        echo "Elchemy $version"
        ;;
    clean)
      rm -rf ./elm-deps
      rm -rf ./elm-stuff
      rm -rf ~/.elm-install/github.com/
      find . | grep "\.elchemy.ex" | xargs rm -f
      rm -rf .elchemy
      ;;
    new)
        mix new $2
        cd $2
        $SOURCE_DIR/elchemy init
        ;;
    init)
        if [ -a ./mix.exs ]
        then
            cp $SOURCE_DIR/templates/elchemy.exs ./.elchemy.exs
            mix archive.install "https://github.com/wende/elchemy/releases/download/$version/elchemy-$version.ez"
            mkdir -p elm
            cp $SOURCE_DIR/templates/elm-package.json ./
            cp $SOURCE_DIR/templates/Hello.elm ./elm/
            if [ -d ./test ]; then
                cp $SOURCE_DIR/templates/elchemy_test.exs ./test/
            fi
            printf  "Elchemy $version initialised. Make sure to add:\n\n\t|> elem(Code.eval_file(\".elchemy.exs\"), 0).init\n\nto your mix.exs file as the last line of the project() function.\nThis pipes the project keyword list to the elchemy init function to configure some additional values.\n\nThen run mix test to check if everything went fine\n"
            printf "\nelm-deps" >> .gitignore
            printf "\nelm-stuff" >> .gitignore
            printf "\node_modules" >> .gitignore
            printf "\.elchemy" >> .gitignore
        else
            printf  "ERROR: No elixir project found. Make sure to run init in a project"
        fi
    ;;
    compile)
        # Create ./.elchemy if doesn't exist
        mkdir -p ".elchemy"
        MTIME="$(cat ".elchemy/mtime" 2> /dev/null || echo "1995-04-10 23:35:02")"
        echo "" > .elchemy/output
        if [ ! -d ./elm-deps ] || [[ !  $(find ./elm-package.json -newermt "$MTIME") == "" ]]; then
            if ! hash elm-github-install 2>/dev/null; then
                echo "No elm-github-install found. Installing..."
                npm i -g elm-github-install@1.6.1
            fi
            echo "-- Downloading Elchemy deps --"
            elm-install
        fi
        # Copy all elixir files that are inside packages
        echo "-- Copying Elixir native files --"
        for f in `{ find -L elm-stuff/packages -name "*.ex*" | grep -v "\.elchemy\.ex" ;}`
        do
          if [ $VERBOSE = true ]; then
            echo "FOUND $f"
          fi
          file="${file/^elm\//lib\//}"
          file=$(create_file $f)
          if [ $VERBOSE = true ]; then
             echo "TO $file"
           fi
          cp $f $file
        done
        i=0
        echo "-- Compiling Elm files --"
        # Find all elm files inside packages and compile them
        for f in `{ find $2 -name "*.elm" -newermt "$MTIME" | grep -v "elm-stuff" | grep -v "#." ; find -L elm-stuff/packages -name "*.elm" -newermt "$MTIME" | grep -v "/tests/" | grep -v "/example/" ;}`
        do
            if [[ ${f} == *"elm-lang"* ]] || [[ ${f} == *"Elchemy.elm"* ]]; then
                continue
            fi
            echo "----------"
            echo "Type Checking $f"
            echo ">>>>$f" >> .elchemy/output
            # We don't need to typecheck deps again
            if [[ ${f} != *"elm-stuff"* ]] && ! [[ $* == *--unsafe* ]]; then
                (echo n | elm-make $f --output .elchemy/output_tmp.js) || { echo 'Type Check failed' ; exit 1; }
                rm .elchemy/output_tmp.js
            fi
            i=$((i+1))
            echo "#$i"
            cat $f >> .elchemy/output
        done
        echo "-- Linking files --"
        node --max_old_space_size=8192 $SOURCE_DIR/elchemy.js .elchemy/output .elchemy/elixir_output .elchemy/cache.json
        current_file=""
        while IFS= read -r line; do
            if [[ $line =~ ">>>>" ]]; then
                current_file="${line/\/\///}"
                current_file="${current_file/>>>>/}"
                echo "Linking: $current_file"
                current_file="${current_file/$2\//$3/}"
                current_file="${current_file%%.elm}.elchemy.ex"
                current_file=$(echo ${current_file} | perl -pe 's/([a-z0-9])([A-Z])/$1_\L$2/g')
                current_file=$(create_file $current_file)
                echo "To: $current_file"
                else
                if [ "$current_file" != "" ]; then
                    printf '%s\n' "$line" >> "$current_file"
                fi
            fi
        done < .elchemy/elixir_output
        #rm .elchemy/elixir_output
        #rm .elchemy/output
        echo $(date +"%Y-%m-%d %H:%M:%S") > .elchemy/mtime
        ;;
    *)
        echo $"Usage: $0 <COMMAND> [<ARGS>]"
        cat <<EOF

Available commands include:

    new <PROJECT_NAME>
        Start a new project

    init
        Add Elchemy to an existing project

    compile [INPUT_DIR] [OUTPUT_DIR] [--unsafe]
        Compile Elchemy source code

    clean
        Remove temporary files

    version
        Print Elchmey's version number number and exit

EOF
        exit 1

esac


================================================
FILE: elchemy_ex/.gitignore
================================================
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

elm-deps
elm-stuff


================================================
FILE: elchemy_ex/README.md
================================================
# ElchemyEx

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `elchemy_ex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:elchemy_ex, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/elchemy_ex](https://hexdocs.pm/elchemy_ex).



================================================
FILE: elchemy_ex/config/config.exs
================================================
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
#     config :elchemy_ex, key: :value
#
# and access this configuration in your application as:
#
#     Application.get_env(:elchemy_ex, :key)
#
# You can also configure a 3rd-party app:
#
#     config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
#     import_config "#{Mix.env}.exs"


================================================
FILE: elchemy_ex/elm/Hello.elm
================================================
module Hello exposing (..)


{-| Prints "world!"

    hello == "world!"
-}
hello : String
hello =
    "world!"


================================================
FILE: elchemy_ex/elm-package.json
================================================
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "../src"
    ],
    "exposed-modules": [
        "Compiler"
    ],
    "dependencies": {
        "wende/elchemy-core" : "0.0.0 <= v <= 1.0.0",
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0",
        "evancz/elm-markdown": "3.0.2 <= v < 4.0.0",
        "Bogdanp/elm-ast": "8.0.9 <= v < 9.0.0",
        "elm-community/list-extra": "6.0.0 <= v < 7.0.0"
    },
    "dependency-sources": {
        "wende/elchemy-core" : "../elchemy-core/"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}


================================================
FILE: elchemy_ex/mix.exs
================================================
defmodule ElchemyEx.Mixfile do
  use Mix.Project

  def project do
    [
      app: :elchemy_ex,
      version: "0.1.0",
      elixir: "~> 1.4",
      # Commented out until release
      # compilers: [:elchemy, :yecc, :leex, :erlang, :elixir, :app],
      elixirc_paths: ["lib", "elm-deps"],
      elchemy_path: "../src",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end


================================================
FILE: elchemy_ex/test/elchemy_ex_test.exs
================================================
defmodule ElchemyExTest do
  use ExUnit.Case

  test "Parsing works" do
    assert Ast.parse("module A exposing (..)\na = 1") != []
  end
end


================================================
FILE: elchemy_ex/test/test_helper.exs
================================================
ExUnit.start()


================================================
FILE: elchemy_node.js
================================================
var path = require("path")
var fs = require("fs");

var SOURCE_DIR = process.argv[2]
var TARGET_DIR = process.argv[3]
var CACHE_PATH = process.argv[4]

function execute(tree, fullTree, treeAndCommons) {
  var cacheExists = CACHE_PATH && fs.existsSync(CACHE_PATH)

  // If cache is provided and cache doesn't exist the compiler doesn't expect cache, and will produce cache after compilation
  if(CACHE_PATH && !cacheExists) {
    var compiledSource = fs.readFileSync(SOURCE_DIR).toString();
    var compiledOutput = treeAndCommons(compiledSource);
    var compiledCode = compiledOutput._0
    var compiledCache = JSON.stringify(compiledOutput._1)
    fs.writeFileSync(TARGET_DIR, compiledCode);
    fs.writeFileSync(CACHE_PATH, compiledCache);
  }
  // If cache ISN'T provided the compiler DOESN'T expect cache.
  else if(!CACHE_PATH){
  	var compiledSource = fs.readFileSync(SOURCE_DIR).toString();
    var compiledCode = tree(compiledSource);
  	fs.writeFileSync(TARGET_DIR, compiledCode);
  }
  // If found it will use the cached ExContext.commons and compile target using it
  else {
    var cachedCommons = JSON.parse(fs.readFileSync(CACHE_PATH).toString())
    var compiledSource = fs.readFileSync(SOURCE_DIR).toString();
    var compiledOutput = fullTree(cachedCommons)(compiledSource);
    var compiledCode = compiledOutput._0
    fs.writeFileSync(TARGET_DIR, compiledCode);
  }
}


module.exports = {
  execute: execute
}


================================================
FILE: elm-package.json
================================================
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        ".",
        "./src"
    ],
    "exposed-modules": [
        "Elchemy.Compiler"
    ],
    "dependencies": {
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0",
        "evancz/elm-markdown": "3.0.2 <= v < 4.0.0",
        "Bogdanp/elm-ast": "8.0.0 <= v < 10.0.0",
        "elm-community/list-extra": "6.0.0 <= v < 7.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}


================================================
FILE: elmchemy
================================================
#!/bin/bash

echo "!!! DEPRECATION NOTICE !!!"
echo "elmchemy name is deprecated. Use elchemy (without an m) instead"
echo "!!! DEPRECATION NOTICE !!!"

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
    DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
    SOURCE="$(readlink "$SOURCE")"
    [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SOURCE_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"

$SOURCE_DIR/elchemy "$@"


================================================
FILE: lib/mix/mix.exs
================================================
defmodule Elchemy.Mixfile do
  use Mix.Project

  def project do
    [app: :elchemy,
     name: "Elchemy Compiler",
     description: "Mix compiler wrapper around Elchemy project",
     version: "0.4.25",
     elixir: "~> 1.4",
     description: "",
     package: package(),
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     elixirc_paths: ["elm", "lib"],
     elchemy_path: "elm",
     deps: deps()]
  end
  defp package do
    # These are the default files included in the package
    [
      name: :elchemy,
      files: ["lib", "priv", "mix.exs", "README*", "readme*", "LICENSE*", "license*"],
      maintainers: ["Krzysztof Wende"],
      licenses: ["Apache 2.0"],
      links: %{"GitHub" => "https://github.com/wende/elchemy"}
    ]
  end
  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [extra_applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:my_dep, "~> 0.4.25"}
  #
  # Or git/path repositories:
  #
  #   {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.4.25"}
  #
  # Type "mix help deps" for more examples and options
  defp deps do
    []
  end
end


================================================
FILE: lib/mix/tasks/compile.elchemy.ex
================================================
defmodule Mix.Tasks.Compile.Elchemy do
  use Mix.Task

  def run(_args) do
    project = Mix.Project.config
    src = project[:elchemy_path]
    dests = project[:elixirc_paths] || ["lib"]
    elchemy_executable = project[:elchemy_executable] || "elchemy"
    version = System.version

    # Crash if elchemy not found globally
    unless 0 == Mix.shell.cmd("which #{elchemy_executable}") do
      Mix.raise "Elchemy not found under #{elchemy_executable}. You might need to run `npm install elchemy -g`"
    end

    # Crash if elchemy not found globally
    unless dests, do: IO.warn "No 'elixirc_paths' setting found"
    if src && dests do
      [dest | _] = dests
      unless 0 == Mix.shell.cmd("#{elchemy_executable} compile #{src} #{dest}") do
        Mix.raise "Elchemy failed the compilation with an error\n"
      end
    end

    # Force project to be reloaded and deps compiled after elm-deps created.
    IO.puts "-- Recompiling dependencies for elchemy --"
    if project = Mix.Project.pop() do
      %{name: name, file: file} = project
      Mix.Project.push(name, file)
    end
    Mix.Task.run "deps.get"
    Mix.Task.run "deps.compile"
    IO.puts "-- Elchemy compilation complete --\n"

    if Regex.match?(~r/1.7.[0-9]+$/, version), do: IO.warn "Elchemy's functionality is limited in Elixit 1.7. To support the full experience please use different Elixir version"
  end
end


================================================
FILE: mix.exs
================================================
defmodule Elchemy.Mixfile do
  use Mix.Project

  def project do
    [app: :elchemy,
     name: "Elchemy Compiler",
     description: "Mix compiler wrapper around Elchemy project",
     version: "0.8.8",
     elixir: "~> 1.4",
     description: "",
     package: package(),
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     elixirc_paths: ["elm", "lib", "elm-deps"],
     elchemy_path: "elm",
     deps: deps()]
  end
  defp package do
    # These are the default files included in the package
    [
      name: :elchemy,
      files: ["lib", "priv", "mix.exs", "README*", "readme*", "LICENSE*", "license*"],
      maintainers: ["Krzysztof Wende"],
      licenses: ["Apache 2.0"],
      links: %{"GitHub" => "https://github.com/wende/elchemy"}
    ]
  end
  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [extra_applications: [:logger]]
  end

  defp deps do
    []
  end
end


================================================
FILE: package.json
================================================
{
  "name": "elchemy",
  "version": "0.8.8",
  "description": "Write Elixir code using Elm-inspired syntax (elm-make compatible)",
  "directories": {
    "example": "example",
    "test": "tests"
  },
  "files": [
    "elchemy",
    "elchemy.js",
    "elchemy_node.js",
    "templates/*"
  ],
  "bin": {
    "elchemy": "./elchemy"
  },
  "scripts": {
    "test": "elm-test",
    "precommit": "lint-staged"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/wende/elchemy.git"
  },
  "bugs": {
    "url": "https://github.com/wende/elchemy/issues"
  },
  "homepage": "https://github.com/wende/elchemy#readme",
  "devDependencies": {
    "elm-format": "^0.6.1-alpha",
    "elm-test": "0.18.0",
    "husky": "^0.13.3",
    "lint-staged": "^3.4.1"
  },
  "lint-staged": {
    "*.elm": [
      "node_modules/.bin/elm-format --yes",
      "git add"
    ]
  }
}


================================================
FILE: roadmap/BASIC_TYPES.md
================================================
# Basic types


Because Elm and Elixir share a lot of common basic types, there is no need to redefine all of them for Elchemy. For simplicity and interoperability some of the standard types translate directly to each other.

Here is a table of all standard types used in the Elchemy environment and their Elixir equivalents:

|  Elchemy | Elixir |
|  --- |  --- |
| `a` | `any()`
| `comparable` | `term()`
| `Int` | `integer()`
| `Float` | `number()`
| `number` | `number()`
| `Bool` | `boolean()`
| `Char` | `integer()`
| `String` | `String.t()`
| `List x` | `list()`
| `(1, 2)` | `{1, 2}`
| `Maybe Int` | `{integer()}` &#124; `nil`
| `Just x` | `{x}`
| `Nothing` | `nil`
| `Result x y` | `{:ok, y}` &#124; `{:error, x}`
| `Ok x` | `{:ok, x}`
| `Err x` | `{:error, x}`
| `{x = 1, y = 1}`   | `%{x: 1, y: 1}` |
| `Dict String Int`   |  `%{key(String.t()) => integer())}` |


================================================
FILE: roadmap/COMMENTS.md
================================================
# Comments

In Elchemy there is several types of comments.

### Inline comments
```elm
-- My comment
```

Which are always ignored by the compiler and won't be included in the compiled output file

### Block comment

Second type of a comment is a block comment

``` elm
{- block comment -}
```

Which are included in the output file, but can only be used as a top level construct

### Doc comments
Another one and probably most important is a doc-comment.

``` elm
{-| Doc comment -}
```
Doc-comments follow these rules:

1. **First doc comment in a file always compiles to a `@moduledoc`**
2. Doc comments before types compile to `@typedoc`
3. Doc comments before functions compile to `@doc`
4. A doc comment with 4 space indentation and containing a `==` comparison in it will be compiled to a **one line** [doctest](https://elixir-lang.org/getting-started/mix-otp/docs-tests-and-with.html#doctests)

Doctest example:

```elm
{-| My function adds two values

       myFunction 1 2 == 3
-}
```

Would end up being

```elixir
@doc """
     My function adds two values

        iex> my_function().(1).(2)
        3
 """
```


================================================
FILE: roadmap/FLAGS.md
================================================
# Flags

Elchemy compiler for purposes of experimenting and stretching the boundaries accepts flags.

#### DO NOT ATTEMPT TO DO THAT UNLESS YOU'RE SURE IT'S THE ONLY WAY

To pass a flag to a compiler there is a special comment syntax

```
{- flag flagname:+argument flagname2:+argument2 }
```

So far there is 4 flag types:
#### `notype:+TypeName`
Omits putting the `@type` into the compiled output code  
Used when you need type checking inside Elchemy ecosystem, without forwarding the definition into the output code.  
Example:
```elm
{- flag notype:+MyHiddenType -}
type MyHiddenType = Hidden a
```
---
#### `nodef:+functionName`
Omits entire function definition from the code output. The function `@spec` will still be produced.
Example:
```elm
{- flag nodef:+myHiddenFunction -}
myHiddenFunction = 1
```
---
#### `nospec:+functionName`
Omits function spec from the code output
Example:
```elm
{- flag nospec:+myHiddenFunction -}
myHiddenFunction : Int
```
---
#### `noverify:+functionName`
Omits function verify macro from the code output. Usable only when using for functions defined as FFI
Example:
```elm
{- flag nospec:+myHiddenFunction -}
myHiddenFunction : Int
```


================================================
FILE: roadmap/FUNCTIONS.md
================================================
# Function definition and currying

### Function definition
To define a function that gets exported you **need** to declare a type for it with

``` elm
functionName : ArgType -> ArgType2 -> ReturnType
```

And a function body underneath

``` elm
functionName argOne argTwo = body
```

This will output following definition:

``` elixir
curry function_name/2
@spec function_name(arg_type, arg_type2) :: return_type
def function_name(arg_one, arg_two) do
  body
end
```

Following rules apply:

1. A function will use `def` or `defp` based on `exposing` clause at the top of the module
2. The name of the function as well as its spec will always be snake_cased version of the camelCase name
3. `curry function/arity` is a construct that makes the function redefined in a form that takes 0 arguments, and returns X times curried function (2 times in this example). Which means that from elixir our function can be called both as: `function_name(arg_one, arg_two)` or `function_name().(arg_one).(arg_two)` and it won't have any different effect
4. `@spec` clause will always resolve types provided, to a most readable and still understandable by the elixir compiler form

#### Curried definition
Because of the curried nature of Elm function definitions we can just make our function return functions

For example instead of writing

``` elm
addTwo : Int -> Int
addTwo a = 2 + a
```

We could just write

``` elm
addTwo : Int -> Int
addTwo = (+) 2
```

In which case Elchemy will recognize a curried return and still provide you with a 1 and 0 arity functions, not only the 0 one.
And output of such a definition would look like:

``` elixir
curry add_two/1
@spec add_two(integer) :: integer
def add_two(x1) do
   (&+/0).(2).(x1)
end
```
Which basically means that Elchemy derives function arity from the type, rathar then function body


================================================
FILE: roadmap/INLINING.md
================================================
# Inlining Elixir Code


#### DO NOT ATTEMPT TO DO THAT UNLESS YOU'RE SURE IT'S THE ONLY WAY

In Elchemy it's possible to inline Elixir code using

```elm
{- ex

## Code
def any_function() do
  1
end

-}
```


================================================
FILE: roadmap/INSTALLATION.md
================================================
# Installation

You can install Elchemy using [npm](https://www.npmjs.com/) with
```
npm install -g elchemy
```

To integrate Elchemy with your project you need to execute:

```
elchemy init
```

Inside your elixir project directory.
If you don't have a project created, you need to first create it. It's advised to use  [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html#our-first-project) for that.

Assuming the simplest example project called `my_project` the standard path would be:

```
mix new my_project
cd my_project
elchemy init
```

Then open your `mix.exs` file inside project root directory. And add:
```elixir
|> elem(Code.eval_file(".elchemy.exs"), 0).init
```

At the end of your `project/0` function definition. Like so:
(As of OTP 21.0 and above you must also add `@compile :tuple_calls` at the top of the Mix module. It is caused by tuple calls support being removed from newer versions of Erlang)
Before:
```elixir
defmodule MyProject.Mixfile do
  use Mix.Project

  def project do
    [
      app: :my_project,
      version: "0.1.0",
      elixir: "~> 1.5",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
  ...
```
After:
```elixir
defmodule MyProject.Mixfile do
  use Mix.Project
  
  def project do
    [
      app: :my_project,
      version: "0.1.0",
      elixir: "~> 1.5",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ] |> elem(Code.eval_file(".elchemy.exs"), 0).init
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
  ...
```


================================================
FILE: roadmap/INTEROP.md
================================================
# Interop


## Calling Elchemy

To call generated code from Elchemy you don't need anything special.  
Each function exposed from a module can be called either in it's regular or curried form.

Keep in mind that in case of functions expecting a [higher-order function\[s\]](https://en.wikipedia.org/wiki/Higher-order_function) in its parameters Elchemy will also do it's magic to make sure it's compatible both ways.

For instance if your function looks like that:
```elm
applyFunction2 : (a -> b -> c) -> a -> b -> c
applyFunction2 f p1 p2 = f p1 p2
```

You can call it Elixir way:  
```elixir
apply_function2(fn a, b -> a + b end)
```
As well as Elchemy (curried) way:
```elixir
apply_function2(fn a -> fn b -> a + b end end)
```
And it will work as fine


---
## Calling Elixir


To call elixir from Elchemy you need to define a foreign function interface.
To do that you can use `ffi` [special syntax (?)](SPECIAL_SYNTAX.md)

`ffi` requires the function to have a very specific format, which is:

1. You need to make sure the type signature is adequate to the corresponding typespec of a function
2. There should be no explicitly stated parameters (Defined as `f = ...` not `f a b c = ...`)
3. The **only** expression inside the function should be an ffi call in a format of:
`ffi "Module" "function"`

A good example of an ffi call would be

```elm
    upcase : String -> String
    upcase =
        ffi "String" "upcase"
```
A generated code of that statement would be

``` elixir
    @spec upcase(String.t) :: String.t
    curry upcase/1
    verify as: String.upcase/1
    def upcase(a1), do: String.upcase(a1)
```

Where `verify, as:` is a type safety generator about which you can read more in [testing](TESTING.md) section.


================================================
FILE: roadmap/MODULES.md
================================================
# Module definition and imports

To define a module in Elchemy you need to use a

``` elm
module ModuleName exposing (..)
```

Which would directly translate to `defmodule` block where functions/types mentioned in the `exposing` clause will automatically use `def` or `defp`

## Imports

There are two types of imports in Elchemy.

### Without exposing
One is a import without exposed functions like

``` elm
import SomeModule
```
Which would directly translate to

``` elixir
alias SomeModule
```

Because it doesn't import any of the exposed contents, only makes sure
that the module is in our namespace.

### With exposing

``` elm
import SomeModule exposing (funA, TypeA, funB)
```
Which outputs

``` elixir
import SomeModule, only: [{:fun_a, 0}, {:fun_b, 0}]
```

Which would put `SomeModule` into our namespace and also allow us to use
`fun_a` and `fun_b` without explicitly adding a module name before them.


================================================
FILE: roadmap/README.md
================================================
# Elchemy

This page lists all of the ideas and solutions behind Elchemy to share the
ideology as well as existing and incoming solutions to problems this project faced
or eventually will have to face

# Table of contents
## Basics
  - [Basic Types](BASIC_TYPES.md)
  - [Defining Types](TYPES.md)
  - [Defining Type Aliases](TYPE_ALIASES.md)
  - [Structures](STRUCTURES.md)
  - [Defining Modules](MODULES.md)
  - [Defining Functions](FUNCTIONS.md)
  - [Comments](COMMENTS.md)
  - [Interop (Foreign Function Interface)](INTEROP.md)

## Advanced
  - [Side Effects / TEA](SIDE_EFFECTS.md)
  - [Unit Testing](TESTING.md)
  - [Compiler flags](FLAGS.md)
  - [Inlining Elixir](INLINING.md)

## Tooling
  - [Installation](./INSTALLATION.md)
  - [Troubleshooting](TROUBLESHOOTING.md)

# Introduction


Elchemy is a set of tools and frameworks, designed to provide a language and an environment
as close to [Elm programming language](http://elm-lang.org) as possible, to build server applications
in a DSL-like manner for Erlang VM platform, with a readable and efficient Elixir code as an output.

## Features

Elchemy inherits many values from its parents: Elm and Elixir

### Elm
- ML like syntax maximizing expressiveness with additional readability and simplicity constraints
- Static typing with type inference
- Beautiful compilation errors
- Tagged union types and type aliases with type parameters (aka generic types)
- All functions are curried by default
- [No typeclasses](http://www.haskellforall.com/2012/05/scrap-your-type-classes.html)

### Erlang/Elixir
- Documentation as a first class citizen
- Doc tests
- Battle-tested distribution system that just works

### Additional
- Foreign function calls type safety
- Foreign function calls purity checks
- Dependency system based on GitHub
- Compile time code optimizations


================================================
FILE: roadmap/SIDE_EFFECTS.md
================================================
# Side Effects

Side Effects are yet to come. You can read in detail about the plans for implementing them here [#297](https://github.com/wende/elchemy/issues/297)


================================================
FILE: roadmap/STRUCTURES.md
================================================
# Structs

Elchemy represents all the structs as maps, so a struct defined like

``` elm
human : { name : String
        , age : Int
        }
```
Is an equivalent of

``` elixir
@spec human :: %{name: String.t(), age: integer()}
```

Also type aliases denoting structs can be instantiated like functions

``` elm
type alias Human =
     { name : String
     , age : Int
     }
```
``` elm
Human "Krzysztof" 22
```

## Struct polymorphism
What's more structs can describe a map that has at least specified elements using an update syntax.

``` elm
type alias Employee x =
     { x
     | salary : Int
     }
```
Which means any struct that has a field `salary` of type integer.
That way we can define our pseudo-inheritance and polymorphism for more advanced structures.

``` elm
type alias Human =
     Employee
        { name : String
        , age : Int }

human : Human
```
Would resolve to

``` elixir
@spec human :: %{
  salary: integer(),
  name: String.t,
  age: integer()
}
```

But be advised that using this "polymorphic" approach strips us from the ability to use type aliases as constructors.


================================================
FILE: roadmap/SUMMARY.md
================================================
# Summary

### Documentation
* [Table Of Contents](./README.md)
* [Installation](./INSTALLATION.md)
* Basics
    * [Syntax overview](./SYNTAX.md)
    * [Basic Types](./BASIC_TYPES.md)
    * [Defining Types](./TYPES.md)
    * [Defining Type Aliases](./TYPE_ALIASES.md)
    * [Defining Structures](./STRUCTURES.md)
    * [Defining Modules](./MODULES.md)
    * [Defining Functions](./FUNCTIONS.md)
    * [Comments](./COMMENTS.md)
    * [Interop](./INTEROP.md)
* Advanced
    * [Side Effects / TEA](./SIDE_EFFECTS.md)
    * [Unit Testing](./TESTING.md)
    * [Compiler Flags](./FLAGS.md)
    * [Inlining Elixir](./INLINING.md)
* Tooling
    * [Troubleshooting](./TROUBLESHOOTING.md)


================================================
FILE: roadmap/SYNTAX.md
================================================
Elchemy is a functional reactive programming language that compiles to (server-side) Elixir (Erlang VM).
Elchemy is statically typed, which means that the compiler catches most errors immediately and provides clear and understandable error messages.

```elm
-- Single line comments start with two dashes.
{- Multiline comments can be enclosed in a block like this.
{- They can be nested. -}
-}

{-- The Basics --}

-- Arithmetic
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20

-- Every number literal without a decimal point can be either an Int or a Float.
33 / 2 -- 16.5 with floating point division
33 // 2 -- 16 with integer division

-- Exponents
5 ^ 2 -- 25

-- Booleans
not True -- False
not False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True

-- Strings and characters
"This is a string because it uses double quotes."
'a' -- characters in single quotes

-- Strings can be appended.
"Hello " ++ "world!" -- "Hello world!"

{-- Lists, Tuples, and Records --}

-- Every element in a list must have the same type.
["the", "quick", "brown", "fox"]
[1, 2, 3, 4, 5]
-- The second example can also be written with two dots.
List.range 1 5

-- Append lists just like strings.
List.range 1 5 ++ List.range 6 10 == List.range 1 10 -- True

-- To add one item, use "cons".
0 :: List.range 1 5 -- [0, 1, 2, 3, 4, 5]

-- The head and tail of a list are returned as a Maybe. Instead of checking
-- every value to see if it's null, you deal with missing values explicitly.
List.head (List.range 1 5) -- Just 1
List.tail (List.range 1 5) -- Just [2, 3, 4, 5]
List.head [] -- Nothing
-- List.functionName means the function lives in the List module.

-- Every element in a tuple can be a different type, but a tuple has a
-- fixed length.
("elchemy", 42)

-- Access the elements of a pair with the first and second functions.
-- (This is a shortcut; we'll come to the "real way" in a bit.)
Tuple.first ("elchemy", 42) -- "elchemy"
Tuple.second ("elchemy", 42) -- 42

-- The empty tuple, or "unit", is sometimes used as a placeholder.
-- It is the only value of its type, also called "Unit".
()

-- Records are like tuples but the fields have names. The order of fields
-- doesn't matter. Notice that record values use equals signs, not colons.
{ x = 3, y = 7 }

-- Access a field with a dot and the field name.
{ x = 3, y = 7 }.x -- 3

-- Or with an accessor function, which is a dot and the field name on its own.
.y { x = 3, y = 7 } -- 7

-- Update the fields of a record. (It must have the fields already.)
{ person |
  name = "George" }

-- Update multiple fields at once, using the current values.
{ particle |
  position = particle.position + particle.velocity,
  velocity = particle.velocity + particle.acceleration }

{-- Control Flow --}

-- If statements always have an else, and the branches must be the same type.
if powerLevel > 9000 then
  "WHOA!"
else
  "meh"

-- If statements can be chained.
if n < 0 then
  "n is negative"
else if n > 0 then
  "n is positive"
else
  "n is zero"

-- Use case statements to pattern match on different possibilities.
case aList of
  [] -> "matches the empty list"
  [x]-> "matches a list of exactly one item, " ++ toString x
  x::xs -> "matches a list of at least one item whose head is " ++ toString x
-- Pattern matches go in order. If we put [x] last, it would never match because
-- x::xs also matches (xs would be the empty list). Matches do not "fall through".
-- The compiler will alert you to missing or extra cases.

-- Pattern match on a Maybe.
case List.head aList of
  Just x -> "The head is " ++ toString x
  Nothing -> "The list was empty."

{-- Functions --}

-- Elchemy's syntax for functions is very minimal, relying mostly on whitespace
-- rather than parentheses and curly brackets. There is no "return" keyword.

-- Define a function with its name, arguments, an equals sign, and the body.
multiply a b =
  a * b

-- Apply (call) a function by passing it arguments (no commas necessary).
multiply 7 6 -- 42

-- Partially apply a function by passing only some of its arguments.
-- Then give that function a new name.
double =
  multiply 2

-- Constants are similar, except there are no arguments.
answer =
  42

-- Pass functions as arguments to other functions.
List.map double (List.range 1 4) -- [2, 4, 6, 8]

-- Or write an anonymous function.
List.map (\a -> a * 2) (List.range 1 4) -- [2, 4, 6, 8]

-- You can also write operators as functions wrapping them in parens
List.map ((+) 2) (List.range 1 4) -- [3, 4, 5, 6]

-- You can pattern match in function definitions when there's only one case.
-- This function takes one tuple rather than two arguments.
-- This is the way you'll usually unpack/extract values from tuples.
area (width, height) =
  width * height

area (6, 7) -- 42

-- Use curly brackets to pattern match record field names.
-- Use let to define intermediate values.
volume {width, height, depth} =
  let
    area = width * height
  in
    area * depth

volume { width = 3, height = 2, depth = 7 } -- 42

-- Functions can be recursive.
fib n =
  if n < 2 then
    1
  else
    fib (n - 1) + fib (n - 2)

List.map fib (List.range 0 8) -- [1, 1, 2, 3, 5, 8, 13, 21, 34]

-- Another recursive function (use List.length in real code).
listLength aList =
  case aList of
    [] -> 0
    x::xs -> 1 + listLength xs

-- Function calls happen before any infix operator. Parens indicate precedence.
cos (degrees 30) ^ 2 + sin (degrees 30) ^ 2 -- 1
-- First degrees is applied to 30, then the result is passed to the trig
-- functions, which is then squared, and the addition happens last.

{-- Types and Type Annotations --}

-- The compiler will infer the type of every value in your program.
-- Types are always uppercase. Read x : T as "x has type T".
-- Some common types
5 : Int
6.7 : Float
"hello" : String
True : Bool

-- Functions have types too. Read -> as "goes to". Think of the rightmost type
-- as the type of the return value, and the others as arguments.
not : Bool -> Bool
round : Float -> Int

-- When you define a value, it's good practice to write its type above it.
-- The annotation is a form of documentation, which is verified by the compiler.
double : Int -> Int
double x = x * 2

-- Function arguments are passed in parentheses.
-- Lowercase types are type variables: they can be any type, as long as each
-- call is consistent.
List.map : (a -> b) -> List a -> List b
-- "List dot map has type a-goes-to-b, goes to list of a, goes to list of b."

-- There are three special lowercase types: number, comparable, and appendable.
-- Numbers allow you to use arithmetic on Ints and Floats.
-- Comparable allows you to order numbers and strings, like a < b.
-- Appendable things can be combined with a ++ b.

{-- Type Aliases and Union Types --}

-- When you write a record or tuple, its type already exists.
-- (Notice that record types use colon and record values use equals.)
origin : { x : Float, y : Float, z : Float }
origin =
  { x = 0, y = 0, z = 0 }

-- You can give existing types a nice name with a type alias.
type alias Point3D =
  { x : Float, y : Float, z : Float }

-- If you alias a record, you can use the name as a constructor function.
otherOrigin : Point3D
otherOrigin =
  Point3D 0 0 0

-- But it's still the same type, so you can equate them.
origin == otherOrigin -- True

-- By contrast, defining a union type creates a type that didn't exist before.
-- A union type is so called because it can be one of many possibilities.
-- Each of the possibilities is represented as a "tag".
type Direction =
  North | South | East | West

-- Tags can carry other values of known type. This can work recursively.
type IntTree =
  Leaf | Node Int IntTree IntTree
-- "Leaf" and "Node" are the tags. Everything following a tag is a type.

-- Tags can be used as values or functions.
root : IntTree
root =
  Node 7 Leaf Leaf

-- Union types (and type aliases) can use type variables.
type Tree a =
  Leaf | Node a (Tree a) (Tree a)
-- "The type tree-of-a is a leaf, or a node of a, tree-of-a, and tree-of-a."

-- Pattern match union tags. The uppercase tags will be matched exactly. The
-- lowercase variables will match anything. Underscore also matches anything,
-- but signifies that you aren't using it.
leftmostElement : Tree a -> Maybe a
leftmostElement tree =
  case tree of
    Leaf -> Nothing
    Node x Leaf _ -> Just x
    Node _ subtree _ -> leftmostElement subtree

-- That's pretty much it for the language itself. Now let's see how to organize
-- and run your code.

{-- Modules and Imports --}

-- The core libraries are organized into modules, as are any third-party
-- libraries you may use. For large projects, you can define your own modules.

-- Put this at the top of the file. If omitted, you're in Main.
module Name

-- By default, everything is exported. You can specify exports explicitly.
module Name exposing (MyType, myValue)

-- Import a type and all it's Tags
import Name exposing (MyType(..))

-- One common pattern is to export a union type but not its tags. This is known
-- as an "opaque type", and is frequently used in libraries.

-- Import code from other modules to use it in this one.
-- Places Dict in scope, so you can call Dict.insert.
import Dict

-- Imports the Dict module and the Dict type, so your annotations don't have to
-- say Dict.Dict. You can still use Dict.insert.
import Dict exposing (Dict)


{-- Command Line Tools --}

-- Install in a project
$ elchemy init

```



================================================
FILE: roadmap/TESTING.md
================================================
# Testing

So far there is no unit testing tool for Elchemy.  
To test your code use Elixir's `ExUnit` or see document [COMMENTS.md](COMMENTS.md) for using Doctests.


================================================
FILE: roadmap/TROUBLESHOOTING.md
================================================
# Troubleshooting

Generally grand amount of problems can be solved with a simple command:
```
elchemy clean
```

It cleans all of the caches, temporary files, dependencies and code file outputs out of the current project.

If
```
elchemy clean
elchemy init
mix compile
```

still yields compilation errors feel free to report an issue.
For the sake of being able to reproduce please provide:

- Elchemy version (`elchemy version`)
- Elixir version (`elixir -v`)
- Erlang version (first line in `erl`)
- Elm version (`elm -v`)
- Operating system (`uname -msr`)

It's also helpful to include:
- Result of `tree -L 2` inside your project folder
- Code of the file failing


================================================
FILE: roadmap/TYPES.md
================================================
# Union types

Elchemy (exactly like Elm) uses [Tagged union types](https://en.wikipedia.org/wiki/Tagged_union) 
What it means is basically you can define a type by adding a tag to
an already existing value and the meaning of this tag is to inform about the
context of that value.

For instance:

``` elm
type Shape = Dot Int | Line Int Int | Triangle Int Int Int
```
What's important is that Dot, Line and Triangle are just tags, so they can't be used as a type name (in function signatures for example)
The only purpose of these is to pattern match on them in constructs like case..of, let..in or in arguments

Elchemy represents tagged unions as tuples with a first element being an atom with snake_cased tag name, or - in case of just tags - as a single atom value.

For example our previously defined type would translate to:

``` elixir
@type shape :: { :dot, integer() } |
               { :line, integer(), integer() } |
               { :triangle, integer(), integer(), integer() }
```
But a type like this:

``` elm
type Size = XS | S | M | L | XL
```
Would translate to:

``` elixir
@type size :: :xs | :s | :m | :l | :xl
```
### Type Parameters

Types can also take type parameters like:

``` elm
type Maybe x = Just x | Nothing
```
All type parameters will resolve to any() by Elchemy.

### Types as constructors

A Type can be insinstantiated using a tag and values. 

For example to instantiate `Just Int` we would write `Just 10`.
If you don't provide all of the parameters, Elchemy will recognize it and
translate it into a curried function, so that 'Just' instead turning into;

``` elixir
:just
```

turns into:

``` elixir
fn x1 -> {:just, x1} end
```


================================================
FILE: roadmap/TYPE_ALIASES.md
================================================
# Type Aliases

In Elchemy Type Aliases are completely virtual constructs that never make it out of the compiler.
However whenever a type alias is used throughout your code Elchemy will expand the alias and substitute with the right replacement.

For instance if we write

``` elm
type alias MyList = List Int

a : MyList
a = [1, 2, 3]
```

The Elixir output would be

``` elixir
@spec a :: list(integer())
def a(), do: [1, 2, 3]
```
With correct type resolution


## Type aliases as constructors

If a type alias represents a structure like

``` elm
type alias Human = { name : String, age : Int }
```
You can use the name of an alias as a function to quickly instatiate a struct
. For instance:

``` elm
Human "Krzysztof" 22
```


================================================
FILE: src/Elchemy/Alias.elm
================================================
module Elchemy.Alias exposing (registerAliases, replaceTypeAliases, resolveTypeBody)

import Ast.Statement exposing (Statement(..), Type(..))
import Dict exposing (Dict)
import Elchemy.Ast as Ast
import Elchemy.Context as Context
    exposing
        ( Alias
        , AliasType
        , Context
        , TypeBody(..)
        , wrongArityAlias
        )
import Elchemy.Helpers as Helpers exposing ((=>), lastAndRest)


registerAliases : Context -> List Statement -> Context
registerAliases c list =
    List.foldl registerAlias c list


registerAlias : Statement -> Context -> Context
registerAlias s c =
    case s of
        TypeAliasDeclaration tc t ->
            registerTypeAlias c tc t

        TypeDeclaration tc types ->
            registerUnionType c tc types

        FunctionTypeDeclaration name t ->
            registerFunctionDefinition c name t

        _ ->
            c


resolveTypeBody : Context -> TypeBody -> List Type -> Type
resolveTypeBody c typeBody givenArgs =
    case typeBody of
        SimpleType t ->
            t

        ArgumentedType name expectedArgs return ->
            let
                arity =
                    List.length givenArgs

                expected =
                    List.length expectedArgs
            in
                if arity == expected then
                    resolveTypes c expectedArgs givenArgs return
                else
                    wrongArityAlias c expected givenArgs name


registerTypeAlias : Context -> Type -> Type -> Context
registerTypeAlias c tc t =
    case tc of
        TypeConstructor [ name ] arguments ->
            let
                arity =
                    List.length arguments

                typeBody =
                    ArgumentedType name arguments t

                ali =
                    Alias c.mod arity Context.TypeAlias t typeBody []
            in
                Context.addAlias c.mod name ali c

        ts ->
            Context.crash c <| "Wrong type alias declaration " ++ toString ts


registerUnionType : Context -> Type -> List Type -> Context
registerUnionType c tc types =
    case tc of
        TypeConstructor [ name ] arguments ->
            let
                typeVar =
                    TypeVariable <| "@" ++ name

                arity =
                    List.length arguments

                ( names, newC ) =
                    registerTypes types name c

                ali =
                    Alias c.mod arity Context.Type typeVar (SimpleType typeVar) names
            in
                Context.addAlias c.mod name ali newC

        ts ->
            Context.crash c <| "Wrong type declaration " ++ toString ts


registerFunctionDefinition : Context -> String -> Type -> Context
registerFunctionDefinition c name t =
    let
        arity =
            replaceTypeAliases c t
                |> Helpers.typeApplicationToList
                |> List.length
    in
        Context.addFunctionDefinition c name (Context.FunctionDefinition (arity - 1) t)


registerTypes : List Type -> String -> Context -> ( List String, Context )
registerTypes types parentAlias c =
    let
        addType t ( names, context ) =
            case t of
                TypeConstructor [ name ] args ->
                    (name :: names)
                        => Context.addType c.mod parentAlias name (List.length args) context

                any ->
                    Context.crash c "Type can only start with a tag"
    in
        List.foldl addType ( [], c ) types


{-| Function taking a type and replacing all aliases it points to with their dealiased version
-}
replaceTypeAliases : Context -> Type -> Type
replaceTypeAliases c t =
    let
        mapOrFunUpdate mod default typeName args =
            Context.getAlias mod typeName c
                |> Helpers.filterMaybe (.aliasType >> (==) Context.TypeAlias)
                |> Maybe.map (\{ typeBody } -> resolveTypeBody c typeBody args)
                |> Maybe.andThen
                    (\body ->
                        case body of
                            TypeRecordConstructor _ _ ->
                                Just body

                            TypeApplication _ _ ->
                                Just body

                            _ ->
                                Nothing
                    )
                |> Maybe.withDefault default

        typeConstructorReplace default fullType args =
            Helpers.moduleAccess c.mod fullType
                |> (\( mod, typeName ) -> mapOrFunUpdate mod default typeName args)

        replaceAlias t =
            case t of
                TypeConstructor fullType args ->
                    typeConstructorReplace t fullType args

                t ->
                    t
    in
        Ast.walkTypeOutwards replaceAlias t


resolveTypes : Context -> List Type -> List Type -> Type -> Type
resolveTypes c expected given return =
    let
        expectedName n =
            case n of
                TypeVariable name ->
                    name

                other ->
                    Context.crash c <|
                        "type can only take variables. "
                            ++ toString other
                            ++ "is incorrect"

        paramsWithResolution =
            List.map2 (,) (List.map expectedName expected) given
                |> List.foldl (uncurry Dict.insert) Dict.empty

        replace t =
            case t of
                (TypeVariable name) as default ->
                    Dict.get name paramsWithResolution
                        |> Maybe.withDefault default

                t ->
                    t
    in
        Ast.walkTypeOutwards replace return


{-| Resolve an alias from local context
-}
localAlias : String -> Context -> Maybe Alias
localAlias name context =
    Context.getAlias context.mod name context


================================================
FILE: src/Elchemy/Ast.elm
================================================
module Elchemy.Ast exposing (foldExpression, walkExpressionInwards, walkExpressionOutwards, walkTypeInwards, walkTypeOutwards)

{-| Contains helper functions to manage Elm Expression and Statement.Type ASTs
-}

import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (Type(..))


{-| Walks a tree of Ast.Statement.Type starting from the bottom branches and goes to the top using a replacer function
-}
walkTypeOutwards : (Type -> Type) -> Type -> Type
walkTypeOutwards f t =
    f <| walkType (walkTypeOutwards f) t


{-| Walks a tree of Ast.Statement.Type starting from the top and goes down the branches using a replacer function
-}
walkTypeInwards : (Type -> Type) -> Type -> Type
walkTypeInwards f t =
    f t |> walkType (walkTypeInwards f)


walkType : (Type -> Type) -> Type -> Type
walkType walkFunction t =
    case t of
        (TypeVariable name) as x ->
            x

        TypeConstructor modulePathAndName args ->
            TypeConstructor modulePathAndName (List.map walkFunction args)

        TypeRecordConstructor name args ->
            TypeRecordConstructor (walkFunction name) <|
                List.map (Tuple.mapSecond walkFunction) args

        TypeTuple args ->
            TypeTuple <| List.map walkFunction args

        TypeRecord args ->
            TypeRecord <| List.map (Tuple.mapSecond walkFunction) args

        TypeApplication l r ->
            TypeApplication (walkFunction l) (walkFunction r)


{-| Walks a tree of Ast.Expression.Expression starting from the bottom branches and goes to the top using a replacer function
-}
walkExpressionOutwards : (Expression -> Expression) -> Expression -> Expression
walkExpressionOutwards f t =
    f <| walkExpression (walkExpressionOutwards f) t


{-| Walks a tree of Ast.Expression.Expression starting from the top and goes down the branches using a replacer function
-}
walkExpressionInwards : (Expression -> Expression) -> Expression -> Expression
walkExpressionInwards f t =
    f t |> walkExpression (walkExpressionInwards f)


walkExpression : (Expression -> Expression) -> Expression -> Expression
walkExpression f t =
    case f t of
        (Variable _) as x ->
            x

        (Character _) as c ->
            c

        (String _) as s ->
            s

        (Integer _) as i ->
            i

        (Float _) as f ->
            f

        List exps ->
            List (List.map (walkExpression f) exps)

        Tuple exps ->
            Tuple (List.map (walkExpression f) exps)

        Access mod field ->
            Access (walkExpression f mod) field

        (AccessFunction field) as af ->
            af

        Record fields ->
            Record <| List.map (Tuple.mapSecond <| walkExpression f) fields

        RecordUpdate name fields ->
            RecordUpdate name <| List.map (Tuple.mapSecond <| walkExpression f) fields

        If check true false ->
            If (walkExpression f check) (walkExpression f true) (walkExpression f false)

        Let assignments body ->
            Let (List.map (Tuple.mapSecond <| walkExpression f) assignments) (walkExpression f body)

        Case target branches ->
            Case (walkExpression f target) (List.map (\( l, r ) -> ( walkExpression f l, walkExpression f r )) branches)

        Lambda args body ->
            Lambda (List.map (walkExpression f) args) (walkExpression f body)

        Application left right ->
            Application (walkExpression f left) (walkExpression f right)

        BinOp op left right ->
            BinOp (walkExpression f op) (walkExpression f left) (walkExpression f right)


{-| Walks a tree of Ast.Expression.Expression starting from the top and goes down the branches using a folder function
-}
foldExpression : (Expression -> acc -> acc) -> acc -> Expression -> acc
foldExpression f acc t =
    let
        rec =
            flip <| foldExpression f
    in
        f t <|
            case t of
                Variable _ ->
                    acc

                Character _ ->
                    acc

                String _ ->
                    acc

                Integer _ ->
                    acc

                Float _ ->
                    acc

                List exps ->
                    List.foldl rec acc exps

                Tuple exps ->
                    List.foldl rec acc exps

                Access mod field ->
                    rec mod acc

                AccessFunction field ->
                    acc

                Record fields ->
                    List.foldl (Tuple.second >> rec) acc fields

                RecordUpdate name fields ->
                    List.foldl (Tuple.second >> rec) acc fields

                If check true false ->
                    acc |> rec check |> rec true |> rec false

                Let assignments body ->
                    List.foldl (\( a, b ) lAcc -> rec a lAcc |> rec b) acc assignments |> rec body

                Case target branches ->
                    acc |> rec target |> (\nAcc -> List.foldl (\( a, b ) lAcc -> rec a lAcc |> rec b) nAcc branches)

                Lambda args body ->
                    List.foldl rec acc args |> rec body

                Application left right ->
                    acc |> rec left |> rec right

                BinOp op left right ->
                    acc |> rec op |> rec left |> rec right


================================================
FILE: src/Elchemy/Compiler.elm
================================================
module Elchemy.Compiler exposing (version, tree)

{-| Module responsible for compiling Elm code to Elixir

@docs version, tree

-}

import Ast
import Ast.Statement exposing (Statement)
import Dict exposing (Dict)
import Elchemy.Alias as Alias
import Elchemy.Context as Context exposing (Context)
import Elchemy.Helpers as Helpers exposing (ind, toSnakeCase)
import Elchemy.Meta as Meta
import Elchemy.Statement as Statement
import Regex exposing (HowMany(..), Regex, regex)


{-| Returns current version
-}
version : String
version =
    "0.8.8"


glueStart : String
glueStart =
    ind 0 ++ "use Elchemy" ++ "\n"


glueEnd : String
glueEnd =
    "\n"
        ++ String.trim
            """
         end

         """
        ++ "\n"


getName : String -> ( String, String )
getName file =
    case String.split "\n" file of
        n :: rest ->
            ( n, String.join "\n" rest )

        [] ->
            ( "", "" )


{-| Transforms a code in Elm to code in Elixir
-}
tree : String -> String
tree =
    treeAndCommons >> Tuple.first


{-| Transforms a code in Elm to code in Elixir and returns commons
-}
treeAndCommons : String -> ( String, Context.Commons )
treeAndCommons m =
    fullTree Context.emptyCommons m


{-| Transforms a code in Elm with cache from previous run to code in Elixir and cache
-}
fullTree : Context.Commons -> String -> ( String, Context.Commons )
fullTree cachedCommons m =
    -- If only no blank characters
    if Regex.contains (Regex.regex "^\\s*$") m then
        ( "", cachedCommons )
    else if not <| String.contains (">>" ++ ">>") m then
        m
            |> parse "NoName.elm"
            |> getContext
            |> (\( c, a ) ->
                    case c of
                        Nothing ->
                            Debug.crash "Failed getting context"

                        Just c ->
                            ( getCode c a, c.commons )
               )
    else
        let
            multiple =
                String.split (">>" ++ ">>") m

            count =
                Debug.log "Number of files" (List.length multiple)

            files =
                multiple
                    |> List.map getName
                    |> List.indexedMap (,)
                    |> List.map
                        (\( i, ( name, code ) ) ->
                            let
                                _ =
                                    flip Debug.log name <|
                                        "Parsing "
                                            ++ toString (count - i)
                                            ++ "/"
                                            ++ toString count
                                            ++ " # "
                            in
                                ( name, parse name code )
                        )

            wContexts =
                files
                    |> List.map (\( name, ast ) -> ( name, getContext ast ))
                    |> List.filterMap
                        (\a ->
                            case a of
                                ( _, ( Nothing, _ ) ) ->
                                    Nothing

                                ( name, ( Just c, ast ) ) ->
                                    Just ( name, c, ast )
                        )

            commons =
                wContexts
                    |> List.map (\( name, ctx, ast ) -> ctx.commons)
                    |> (::) cachedCommons
                    |> getCommonImports
                    |> (\modules -> { modules = modules })

            wTrueContexts =
                wContexts
                    |> List.map (\( name, c, ast ) -> ( name, { c | commons = commons }, ast ))

            compileWithIndex ( i, ( name, c, ast ) ) =
                let
                    _ =
                        flip Debug.log name <|
                            "Compiling "
                                ++ toString (count - i)
                                ++ "/"
                                ++ toString count
                                ++ " # "
                in
                    -- "Used to avoid quadruple > becuase it's a meta string"
                    ">>" ++ ">>" ++ name ++ "\n" ++ getCode c ast
        in
            wTrueContexts
                |> List.indexedMap (,)
                |> List.map compileWithIndex
                |> String.join "\n"
                |> flip (,) commons


getCommonImports : List Context.Commons -> Dict String Context.Module
getCommonImports commons =
    let
        merge aliases acc =
            Dict.merge Dict.insert (\k v v2 -> Dict.insert k v2) Dict.insert acc aliases Dict.empty
    in
        List.foldl (.modules >> merge) Dict.empty commons


getContext : List Statement -> ( Maybe Context, List Statement )
getContext statements =
    case statements of
        [] ->
            ( Nothing, [] )

        mod :: statements ->
            let
                base =
                    Statement.moduleStatement mod
            in
                ( Just (Alias.registerAliases base statements), statements )


aggregateStatements : Statement -> ( Context, String ) -> ( Context, String )
aggregateStatements s ( c, code ) =
    let
        ( newC, newCode ) =
            Statement.elixirS c s
    in
        ( newC, code ++ newCode )


getCode : Context -> List Statement -> String
getCode context statements =
    let
        shadowsBasics =
            Context.importBasicsWithoutShadowed context

        ( newC, code ) =
            List.foldl aggregateStatements ( context, "" ) statements
    in
        ("# Compiled using Elchemy v" ++ version)
            ++ "\n"
            ++ ("defmodule " ++ context.mod ++ " do")
            ++ glueStart
            ++ ind context.indent
            ++ shadowsBasics
            ++ code
            ++ glueEnd
            ++ Meta.metaDefinition { newC | inMeta = True }
            ++ "\n\n"


parse : String -> String -> List Statement
parse fileName code =
    case Ast.parse (prepare code) of
        Ok ( _, _, statements ) ->
            statements

        Err ( (), { input, position }, [ msg ] ) ->
            let
                ( line, column ) =
                    getLinePosition position code
            in
                Debug.crash <|
                    "]ERR> Parsing error in:\n "
                        ++ fileName
                        ++ ":"
                        ++ toString line
                        ++ ":"
                        ++ toString column
                        ++ "\n"
                        ++ msg
                        ++ "\nat:\n "
                        ++ (input
                                |> String.lines
                                |> List.take 30
                                |> String.join "\n"
                           )
                        ++ "\n"

        err ->
            Debug.crash (toString err)


prepare : String -> String
prepare codebase =
    codebase |> removeComments


removeComments : String -> String
removeComments =
    Regex.replace All (regex " +--.*\\r?\\n") (always "")
        >> Regex.replace All (regex "\\s--.*\\r?\\n") (always "")
        >> Regex.replace All (regex "\n +\\w+ : .*") (always "")


getLinePosition : Int -> String -> ( Int, Int )
getLinePosition character input =
    let
        lines =
            String.slice 0 character input |> String.lines

        line =
            List.length lines

        column =
            List.reverse lines |> List.head |> Maybe.map String.length |> Maybe.withDefault 0
    in
        ( line, column )


================================================
FILE: src/Elchemy/Context.elm
================================================
module Elchemy.Context
    exposing
        ( Alias
        , AliasType(..)
        , Commons
        , Context
        , FunctionDefinition
        , Module
        , Parser
        , TypeBody(..)
        , addAlias
        , addFlag
        , addFunctionDefinition
        , addModuleAlias
        , addType
        , areMatchingArity
        , changeCurrentModule
        , crash
        , deindent
        , empty
        , emptyCommons
        , getAlias
        , getArity
        , getShadowedFunctions
        , getType
        , hasFlag
        , importBasicsWithoutShadowed
        , inArgs
        , indent
        , isPrivate
        , listOfImports
        , maybeModuleAlias
        , mergeTypes
        , mergeVariables
        , notImplemented
        , onlyWithoutFlag
        , putIntoModule
        , wrongArityAlias
        )

import Ast.Expression exposing (Expression)
import Ast.Statement exposing (ExportSet(..), Statement, Type(..))
import Dict exposing (Dict)
import Elchemy.Helpers as Helpers exposing (toSnakeCase)
import Set exposing (Set)


type alias Parser =
    Context -> Expression -> String


{-| A structure containing all the essential information about Type Alias
-}
type TypeBody
    = SimpleType Type
    | ArgumentedType String (List Type) Type


type alias Alias =
    { parentModule : String
    , arity : Int
    , aliasType : AliasType
    , body : Type
    , typeBody : TypeBody
    , types : List String
    }


type alias UnionType =
    { arity : Int
    , parentModule : String
    , parentAlias : String
    }


{-| Type of an Alias which can be either Type or Type Alias. It's important to note
that a Type in here is only a definition of a type like

    type A
        = TagA
        | TagB

Where only A is a `Context.AliasType.Type`, two separate tags are other instances and
belong to Types not Aliases

-}
type AliasType
    = Type
    | TypeAlias


{-| A flag for a compiler and its value
-}
type alias Flag =
    ( String, String )


{-| Definition of a function and its correspoint Ast.Type structure
-}
type alias FunctionDefinition =
    { arity : Int, def : Ast.Statement.Type }


{-| Dict holding information about defined modules
-}
type alias Module =
    { aliases : Dict String Alias
    , types : Dict String UnionType
    , functions : Dict String FunctionDefinition
    , exports : ExportSet
    }


type alias Commons =
    { modules : Dict String Module
    }


{-| Context containing all the necessary information about current place in a file
like what's the name of a module, what aliases, types and variables are currently defined,
what flags were set for the compiler, what functions were defined and if it is in definition mode.
-}
type alias Context =
    { mod : String
    , commons : Commons
    , exports : ExportSet
    , indent : Int
    , flags : List Flag
    , variables : Set String
    , inArgs : Bool
    , hasModuleDoc : Bool
    , lastDoc : Maybe String
    , inTypeDefiniton : Bool
    , importedTypes : Dict String String
    , aliasedModules : Dict String String

    -- Dict functionName (moduleName, arity)
    , importedFunctions : Dict String ( String, Int )
    , meta : Maybe Expression
    , inMeta : Bool
    }


{-| Crashes the compiler because the alias was used with wrong arity.
Shouldn't ever happen if run after elm-make
-}
wrongArityAlias : Context -> Int -> List Type -> String -> a
wrongArityAlias c arity list name =
    crash c <|
        "Expected "
            ++ toString arity
            ++ " arguments for "
            ++ name
            ++ ". But got "
            ++ (toString <| List.length list)


{-| Puts something into module
Usage:

    putIntoModule "Module" "name" .aliases (x -> { c | aliases = x }) ali c

-}
putIntoModule :
    String
    -> String
    -> (Module -> Dict String a)
    -> (Module -> Dict String a -> Module)
    -> a
    -> Context
    -> Context
putIntoModule mod name getter setter thing c =
    let
        updateMod : Maybe Module -> Maybe Module
        updateMod maybeMod =
            maybeMod
                |> Maybe.map getter
                |> Maybe.withDefault Dict.empty
                |> Dict.update name (always <| Just thing)
                |> setter (maybeMod |> Maybe.withDefault emptyModule)
                |> Just

        commons =
            c.commons
    in
        { c | commons = { commons | modules = commons.modules |> Dict.update mod updateMod } }


{-| Adds an alias definition to the context
-}
addAlias : String -> String -> Alias -> Context -> Context
addAlias mod name =
    putIntoModule mod name .aliases (\m x -> { m | aliases = x })


{-| Adds a type definition to the context
-}
addType : String -> String -> String -> Int -> Context -> Context
addType mod parentAlias name arity =
    let
        t =
            { arity = arity, parentModule = mod, parentAlias = parentAlias }
    in
        putIntoModule mod name .types (\m x -> { m | types = x }) t


{-| Add type definition into context
-}
addFunctionDefinition : Context -> String -> FunctionDefinition -> Context
addFunctionDefinition c name d =
    putIntoModule c.mod name .functions (\m x -> { m | functions = x }) d c


{-| Get's either alias or type from context based on `from` accessor
-}
getFromContext :
    (Module -> Dict String a)
    -> String
    -> String
    -> Context
    -> Maybe a
getFromContext from mod name context =
    context
        |> .commons
        |> .modules
        |> Dict.get mod
        |> Maybe.map from
        |> Maybe.andThen (Dict.get name)


{-| Get's an alias from context based on name of a module and of an alias
Wrapped in Maybe
-}
getAlias : String -> String -> Context -> Maybe Alias
getAlias =
    getFromContext .aliases


{-| Get's a type from context based on name of a module and of a type
Wrapped in Maybe
-}
getType : String -> String -> Context -> Maybe UnionType
getType =
    getFromContext .types


{-| Gets arity of the function in the module
-}
getArity : Context -> String -> String -> Maybe Int
getArity ctx m fn =
    let
        local =
            ctx.commons.modules
                |> Dict.get m
                |> Maybe.map .functions
                |> Maybe.andThen (Dict.get fn)
                |> Maybe.map .arity

        imported =
            ctx.importedFunctions
                |> Dict.get fn
                |> Maybe.map Tuple.second
    in
        Helpers.maybeOr local imported


{-| Checks if function arity stored in context is the same as arguments count
-}
areMatchingArity : Context -> String -> String -> List a -> Bool
areMatchingArity c mod fn args =
    List.length args == Maybe.withDefault -1 (getArity c mod fn)


{-| Returns empty context
-}
empty : String -> ExportSet -> Context
empty name exports =
    { mod = name
    , exports = exports
    , indent = 0
    , flags = []
    , variables = Set.empty
    , inArgs = False
    , hasModuleDoc = False
    , lastDoc = Nothing
    , commons = { modules = Dict.singleton name (Module Dict.empty Dict.empty Dict.empty exports) }
    , inTypeDefiniton = False
    , importedTypes =
        Dict.fromList
            [ ( "Order", "Elchemy.XBasics" )
            , ( "Result", "Elchemy.XResult" )
            ]
    , importedFunctions = Dict.empty
    , aliasedModules = Dict.empty
    , meta = Nothing
    , inMeta = False
    }


{-| Returns empty commons structure
-}
emptyCommons : Commons
emptyCommons =
    { modules = Dict.empty
    }


changeCurrentModule : String -> Context -> Context
changeCurrentModule mod c =
    { c | mod = mod }


{-| Returns empty module record
-}
emptyModule : Module
emptyModule =
    Module Dict.empty Dict.empty Dict.empty AllExport


{-| Increases current indenation level of a context
-}
indent : Context -> Context
indent c =
    { c | indent = c.indent + 1 }


{-| Decreases current indenation level of a context
-}
deindent : Context -> Context
deindent c =
    { c | indent = c.indent - 1 }


{-| Adds a flag to the compiler of a context
-}
addFlag : Flag -> Context -> Context
addFlag flag c =
    { c | flags = flag :: c.flags }


{-| Puts the code only if the given flag and its given value DOESN'T exist
-}
onlyWithoutFlag : Context -> String -> String -> String -> String
onlyWithoutFlag c key value code =
    if hasFlag key value c then
        ""
    else
        code


{-| -}
getAllFlags : String -> Context -> List String
getAllFlags key c =
    c.flags
        |> List.filter (Tuple.first >> (==) key)
        |> List.map Tuple.second


{-| True if has a flag with a particular value
-}
hasFlag : String -> String -> Context -> Bool
hasFlag key value c =
    c.flags
        |> List.any ((==) ( key, value ))


{-| Makes the state to be inside argument declaration,
thanks to that the compiler knows not to treat the declaration of new variables
as a refference to an older values or functions and prevents injection of parens
-}
inArgs : Context -> Context
inArgs c =
    { c | inArgs = True }


{-| Tells you if a function is private or public based on context of a module
-}
isPrivate : Context -> String -> Bool
isPrivate context name =
    case context.exports of
        SubsetExport exports ->
            if List.any ((==) (FunctionExport name)) exports then
                False
            else
                True

        AllExport ->
            False

        other ->
            crash context "No such export"


{-| Merges a set of two variables from two different contexts
-}
mergeVariables : Context -> Context -> Context
mergeVariables left right =
    { left | variables = Set.union left.variables right.variables }


{-| Finds all defined functions and all auto imported functions (XBasics) and returns
the commons subset. Return empty list for XBasics
-}
getShadowedFunctions : Context -> List String -> List ( String, FunctionDefinition )
getShadowedFunctions context list =
    let
        functions =
            context.commons.modules
                |> Dict.get context.mod
                |> Maybe.map .functions
                |> Maybe.withDefault Dict.empty

        findReserved name =
            functions
                |> Dict.get name
                |> Maybe.map ((,) name >> List.singleton)
                |> Maybe.withDefault []
    in
        if context.mod == "Elchemy.XBasics" then
            []
        else
            list
                |> List.concatMap findReserved


{-| Changes function definitions to a list of qualified imports including 0 and full arity
-}
listOfImports : List ( String, FunctionDefinition ) -> List String
listOfImports shadowed =
    let
        importTuple ( name, arity ) =
            toSnakeCase False name
                ++ ": 0, "
                ++ toSnakeCase False name
                ++ ": "
                ++ toString arity
    in
        shadowed
            |> List.map (Tuple.mapSecond .arity)
            |> List.map importTuple


{-| Get code representation of import XBasics with exclusion of functions defined locally
-}
importBasicsWithoutShadowed : Context -> String
importBasicsWithoutShadowed c =
    let
        importModule mod list =
            if list /= [] then
                list
                    |> String.join ", "
                    |> (++) ("import " ++ mod ++ ", except: [")
                    |> flip (++) "]\n"
            else
                ""

        shadowedBasics =
            getShadowedFunctions c Helpers.reservedBasicFunctions
                |> listOfImports

        shadowedKernel =
            getShadowedFunctions c Helpers.reservedKernelFunctions
                |> listOfImports
    in
        importModule "Elchemy.XBasics" shadowedBasics
            ++ importModule "Kernel" shadowedKernel


{-| Register a new module alias
import ModuleA as ModuleB
Would delias all ModuleB calls to ModuleA in case of Type and TypeAlias constructors
-}
addModuleAlias : String -> Maybe String -> Context -> Context
addModuleAlias oldName newName c =
    newName
        |> Maybe.map (\name -> { c | aliasedModules = c.aliasedModules |> Dict.insert name oldName })
        |> Maybe.withDefault c


{-| Replace a module name with it's original name it aliases to. Otherwise return the same name
-}
maybeModuleAlias : Context -> String -> String
maybeModuleAlias c s =
    c.aliasedModules
        |> Dict.get s
        |> Maybe.withDefault s


{-| Merges everything that should be imported from given module, based
on given export set value
-}
mergeTypes : ExportSet -> String -> Context -> Context
mergeTypes set mod c =
    let
        getAll getter mod =
            c.commons.modules
                |> Dict.get mod
                |> Maybe.map getter
                |> Maybe.withDefault Dict.empty

        getAlias : String -> Dict String Alias
        getAlias aliasName =
            getAll .aliases mod
                |> Dict.filter (\k _ -> k == aliasName)

        getTypes : String -> Maybe ExportSet -> Dict String UnionType
        getTypes aliasName maybeExportSet =
            getAll .types mod
                |> Dict.filter (\k { parentAlias } -> parentAlias == aliasName)

        putAllLocal getter setter dict c =
            Dict.foldl (\key value acc -> putIntoModule c.mod key getter setter value acc) c dict

        importOne export c =
            case export of
                TypeExport aliasName types ->
                    c
                        |> putAllLocal .aliases (\m x -> { m | aliases = x }) (getAlias aliasName)
                        |> putAllLocal .types (\m x -> { m | types = x }) (getTypes aliasName types)

                FunctionExport _ ->
                    c

                _ ->
                    crash c "You can't import subset of subsets"
    in
        case set of
            AllExport ->
                c
                    |> putAllLocal .aliases (\m x -> { m | aliases = x }) (getAll .aliases mod)
                    |> putAllLocal .types (\m x -> { m | types = x }) (getAll .types mod)

            SubsetExport list ->
                List.foldl importOne c list

            _ ->
                crash c "You can't import something that's not a subset"


{-| Throw a nice error with the context involving it
-}
crash : Context -> String -> a
crash c prompt =
    Debug.crash <|
        "Compilation error:\n\n\t"
            ++ prompt
            ++ "\n\nin module: "
            ++ c.mod


{-| Throw a nice error saying that this feature is not implemented yet
-}
notImplemented : Context -> String -> a -> String
notImplemented c feature value =
    " ## ERROR: No "
        ++ feature
        ++ " implementation for "
        ++ toString value
        ++ " yet"
        ++ "\n"
        |> Debug.crash


================================================
FILE: src/Elchemy/Expression.elm
================================================
module Elchemy.Expression exposing (elixirE)

import Ast.Expression exposing (Expression(..))
import Elchemy.Context as Context
    exposing
        ( Context
        , areMatchingArity
        , deindent
        , inArgs
        , indent
        , mergeVariables
        , onlyWithoutFlag
        )
import Elchemy.Operator as Operator
import Elchemy.Selector as Selector
import Elchemy.Type as Type
import Elchemy.Variable as Variable exposing (rememberVariables)
import Elchemy.Helpers as Helpers
    exposing
        ( (=>)
        , Operator(..)
        , applicationToList
        , atomize
        , generateArguments
        , ind
        , isCapitilzed
        , lastAndRest
        , maybeOr
        , modulePath
        , operatorType
        , toSnakeCase
        , translateOperator
        )


{-| Encode any given expression
-}
elixirE : Context -> Expression -> String
elixirE c e =
    case e of
        Variable var ->
            elixirVariable c var

        -- Primitive types
        (Application name arg) as application ->
            tupleOrFunction c application

        RecordUpdate name keyValuePairs ->
            "%{"
                ++ toSnakeCase True name
                ++ " | "
                ++ (List.map (\( a, b ) -> toSnakeCase True a ++ ": " ++ elixirE c b) keyValuePairs
                        |> String.join ", "
                   )
                ++ "}"

        -- Primitive operators
        Access (Variable modules) right ->
            modulePath modules
                ++ "."
                ++ String.join "." (List.map (toSnakeCase True) right)

        Access left right ->
            elixirE c left
                ++ "."
                ++ String.join "." right

        AccessFunction name ->
            "(fn a -> a." ++ toSnakeCase True name ++ " end)"

        BinOp (Variable [ op ]) l r ->
            Operator.elixirBinop c elixirE op l r

        -- Rest
        e ->
            elixirControlFlow c e


{-| Encode control flow expressions
-}
elixirControlFlow : Context -> Expression -> String
elixirControlFlow c e =
    case e of
        Case var body ->
            caseE c var body

        Lambda args body ->
            lambda c args body

        (If check onTrue ((If _ _ _) as onFalse)) as exp ->
            [ "cond do" ]
                ++ handleIfExp (indent c) exp
                ++ [ ind c.indent, "end" ]
                |> String.join ""

        If check onTrue onFalse ->
            "if "
                ++ elixirE c check
                ++ " do "
                ++ elixirE c onTrue
                ++ " else "
                ++ elixirE c onFalse
                ++ " end"

        Let variables expression ->
            variables
                |> Variable.organizeLetInVariablesOrder c
                |> Variable.groupByCrossDependency
                |> (flip List.foldl ( c, "" ) <|
                        \varGroup ( cAcc, codeAcc ) ->
                            (case varGroup of
                                [] ->
                                    cAcc => ""

                                [ ( var, exp ) ] ->
                                    elixirLetInBranch cAcc ( var, exp )

                                multiple ->
                                    elixirLetInMutualFunctions cAcc multiple
                            )
                                |> (\( c, string ) ->
                                        mergeVariables c cAcc
                                            => codeAcc
                                            ++ string
                                            ++ ind c.indent
                                   )
                   )
                |> (\( c, code ) -> code ++ elixirE c expression)

        _ ->
            elixirPrimitve c e


{-| Encodes a mutual function usage into `let` macro
-}
elixirLetInMutualFunctions : Context -> List ( Expression, Expression ) -> ( Context, String )
elixirLetInMutualFunctions context expressionsList =
    let
        vars =
            List.map Tuple.first expressionsList

        names =
            expressionsList
                |> List.map (Tuple.first >> Variable.extractName c >> toSnakeCase True)

        c =
            rememberVariables vars context

        letBranchToLambda : Context -> ( Expression, Expression ) -> String
        letBranchToLambda c ( head, body ) =
            case applicationToList head of
                [] ->
                    ""

                [ single ] ->
                    elixirE c body

                (Variable [ name ]) :: args ->
                    lambda c args body

                _ ->
                    Context.crash c <| toString head ++ " is not a let in branch"
    in
        c
            => "{"
            ++ (names |> String.join ", ")
            ++ "} = let ["
            ++ (expressionsList
                    |> List.map (\(( var, exp ) as v) -> ( Variable.extractName c var, v ))
                    |> List.map (Tuple.mapSecond <| letBranchToLambda (indent c))
                    |> List.map (\( name, body ) -> ind (c.indent + 1) ++ toSnakeCase True name ++ ": " ++ body)
                    |> String.join ","
               )
            ++ ind c.indent
            ++ "]"


{-| Encodes a branch of let..in expression

    let
      {a, b} == 2 --< This is a branch
    in
      10

-}
elixirLetInBranch : Context -> ( Expression, Expression ) -> ( Context, String )
elixirLetInBranch c ( left, exp ) =
    let
        wrapElixirE c exp =
            case exp of
                Let _ _ ->
                    "(" ++ ind (c.indent + 1) ++ elixirE (indent c) exp ++ ind c.indent ++ ")"

                _ ->
                    elixirE c exp
    in
        case applicationToList left of
            [ (Variable [ name ]) as var ] ->
                rememberVariables [ var ] c
                    => toSnakeCase True name
                    ++ " = "
                    ++ wrapElixirE (c |> rememberVariables [ var ]) exp

            ((Variable [ name ]) as var) :: args ->
                if Helpers.isCapitilzed name then
                    (c |> rememberVariables args)
                        => tupleOrFunction (rememberVariables args c) left
                        ++ " = "
                        ++ wrapElixirE c exp
                else
                    rememberVariables [ var ] c
                        => toSnakeCase True name
                        ++ " = rec "
                        ++ toSnakeCase True name
                        ++ ", "
                        ++ lambda (c |> rememberVariables [ var ]) args exp

            [ assign ] ->
                rememberVariables [ assign ] c
                    => elixirE (inArgs c) assign
                    ++ " = "
                    ++ wrapElixirE (rememberVariables [ assign ] c) exp

            _ ->
                c => ""


{-| Encode primitive value
-}
elixirPrimitve : Context -> Expression -> String
elixirPrimitve c e =
    case e of
        Integer value ->
            toString value

        Float value ->
            let
                name =
                    toString value
            in
                if String.contains "." name then
                    name
                else
                    name ++ ".0"

        Character value ->
            case value of
                ' ' ->
                    "?\\s"

                '\n' ->
                    "?\\n"

                '\x0D' ->
                    "?\\r"

                '\t' ->
                    "?\\t"

                '\\' ->
                    "?\\\\"

                '\x00' ->
                    "?\\0"

                other ->
                    "?" ++ String.fromChar other

        String value ->
            "\"" ++ value ++ "\""

        List vars ->
            "["
                ++ (List.map (elixirE c) vars
                        |> String.join ", "
                   )
                ++ "]"

        Tuple vars ->
            "{"
                ++ (List.map (elixirE c) vars
                        |> String.join ", "
                   )
                ++ "}"

        Record keyValuePairs ->
            "%{"
                ++ (List.map (\( a, b ) -> toSnakeCase True a ++ ": " ++ elixirE c b) keyValuePairs
                        |> String.join ", "
                   )
                ++ "}"

        _ ->
            Context.notImplemented c "expression" e


{-| Change if expression body into list of clauses
-}
handleIfExp : Context -> Expression -> List String
handleIfExp c e =
    case e of
        If check onTrue onFalse ->
            [ ind c.indent
            , elixirE (indent c) check
            , " -> "
            , elixirE (indent c) onTrue
            ]
                ++ handleIfExp c onFalse

        _ ->
            [ ind c.indent
            , "true -> "
            , elixirE (indent c) e
            ]


{-| Returns if called function is a special macro inline by the compiler
-}
isMacro : Expression -> Bool
isMacro e =
    case e of
        Application a _ ->
            isMacro a

        Variable [ x ] ->
            List.member x
                [ "tryFfi"
                , "ffi"
                , "lffi"
                , "macro"
                , "flambda"
                , "updateIn"
                , "updateIn2"
                , "updateIn3"
                , "updateIn4"
                , "updateIn5"
                , "putIn"
                , "putIn"
                , "putIn2"
                , "putIn3"
                , "putIn4"
                , "putIn5"
                , "getIn"
                , "getIn2"
                , "getIn3"
                , "getIn4"
                , "getIn5"
                ]

        other ->
            False


{-| Flattens Type application into a List of expressions or returns a singleton if it's not a type
-}
flattenApplication : Expression -> List Expression
flattenApplication application =
    case application of
        Application left right ->
            if isMacro application || isTuple application then
                flattenApplication left ++ [ right ]
            else
                [ application ]

        other ->
            [ other ]


{-| Returns uncurried function application if arguments length is matching definition arity
otherwise returns curried version
-}
functionApplication : Context -> Expression -> Expression -> String
functionApplication c left right =
    let
        reduceArgs c args separator =
            args |> List.map (elixirE c) |> String.join separator
    in
        case applicationToList (Application left right) of
            (Variable [ fn ]) :: args ->
                if areMatchingArity c c.mod fn args then
                    toSnakeCase True fn ++ "(" ++ reduceArgs c args ", " ++ ")"
                else if c.inMeta then
                    Context.crash c "You need to use full "
                else
                    elixirE c left ++ ".(" ++ elixirE c right ++ ")"

            (Access (Variable modules) [ fn ]) :: args ->
                let
                    mod =
                        modulePath modules

                    fnName =
                        toSnakeCase True fn
                in
                    if areMatchingArity c mod fn args then
                        mod ++ "." ++ fnName ++ "(" ++ reduceArgs c args ", " ++ ")"
                    else
                        mod ++ "." ++ fnName ++ "().(" ++ reduceArgs c args ").(" ++ ")"

            _ ->
                elixirE c left ++ ".(" ++ elixirE c right ++ ")"


encodeAccessMacroAndRest : Context -> ( Selector.AccessMacro, List Expression ) -> String
encodeAccessMacroAndRest c ( Selector.AccessMacro t arity selectors, rest ) =
    let
        encodeSelector (Selector.Access s) =
            ":" ++ toSnakeCase True s

        encodedSelectors =
            selectors |> List.map encodeSelector |> String.join ", "

        encodedType =
            case t of
                Selector.Update ->
                    "update_in_"

                Selector.Get ->
                    "get_in_"

                Selector.Put ->
                    "put_in_"

        encodedRest =
            case rest of
                [] ->
                    ""

                list ->
                    ".("
                        ++ (List.map (elixirE c) rest |> String.join ").(")
                        ++ ")"
    in
        encodedType
            ++ "(["
            ++ encodedSelectors
            ++ "])"
            ++ encodedRest


{-| Returns code representation of tuple or function depending on definition
-}
tupleOrFunction : Context -> Expression -> String
tupleOrFunction c a =
    case flattenApplication a of
        -- Not a macro
        (Application left right) :: [] ->
            functionApplication c left right

        -- A macro
        (Variable [ "ffi" ]) :: rest ->
            Context.crash c "Ffi inside function body is deprecated since Elchemy 0.3"

        (Variable [ "macro" ]) :: rest ->
            Context.crash c "You can't use `macro` inside a function body"

        (Variable [ "tryFfi" ]) :: rest ->
            Context.crash c "tryFfi inside function body is deprecated since Elchemy 0.3"

        (Variable [ "lffi" ]) :: rest ->
            Context.crash c "Lffi inside function body is deprecated since Elchemy 0.3"

        (Variable [ "flambda" ]) :: rest ->
            Context.crash c "Flambda is deprecated since Elchemy 0.3"

        [ Variable [ "Just" ], arg ] ->
            "{" ++ elixirE c arg ++ "}"

        [ Variable [ "Ok" ], arg ] ->
            "{:ok, " ++ elixirE c arg ++ "}"

        [ Variable [ "Err" ], arg ] ->
            "{:error, " ++ elixirE c arg ++ "}"

        [ Variable [ "Do" ], arg ] ->
            "quote do " ++ elixirE c arg ++ " end"

        -- Regular non-macro application
        ((Variable list) as call) :: rest ->
            Selector.maybeAccessMacro c call rest
                |> Maybe.map (encodeAccessMacroAndRest c)
                |> Maybe.withDefault
                    (Helpers.moduleAccess c.mod list
                        |> (\( mod, last ) ->
                                aliasFor (Context.changeCurrentModule (Context.maybeModuleAlias c mod) c) last rest
                                    |> Maybe.withDefault
                                        ("{"
                                            ++ elixirE c (Variable [ last ])
                                            ++ ", "
                                            ++ (List.map (elixirE c) rest |> String.join ", ")
                                            ++ "}"
                                        )
                           )
                    )

        other ->
            Context.crash c ("Shouldn't ever work for" ++ toString other)


{-| Return an alias for type alias or union type if it exists, return Nothing otherwise
-}
aliasFor : Context -> String -> List Expression -> Maybe String
aliasFor c name rest =
    maybeOr (typeAliasApplication c name rest) (typeApplication c name rest)


{-| Returns Just only if the passed alias type is a type alias
-}
filterTypeAlias : Context.Alias -> Maybe Context.Alias
filterTypeAlias ({ aliasType } as ali) =
    case aliasType of
        Context.TypeAlias ->
            Just ali

        Context.Type ->
            Nothing


{-| Returns a type alias application based on current context definitions
-}
typeAliasApplication : Context -> String -> List Expression -> Maybe String
typeAliasApplication c name args =
    Context.getAlias c.mod name c
        |> Maybe.andThen filterTypeAlias
        |> Maybe.andThen (Type.typeAliasConstructor args)
        |> Maybe.map (elixirE c)


{-| Returns a type application based on current context definitions
-}
typeApplication : Context -> String -> List Expression -> Maybe String
typeApplication c name args =
    Context.getType c.mod name c
        |> (Maybe.map <|
                \{ arity } ->
                    let
                        len =
                            List.length args

                        dif =
                            arity - len

                        arguments =
                            generateArguments dif

                        varArgs =
                            List.map (List.singleton >> Variable) arguments
                    in
                        if arity == 0 then
                            atomize name
                        else if dif >= 0 then
                            (arguments
                                |> List.map ((++) "fn ")
                                |> List.map (flip (++) " -> ")
                                |> String.join ""
                            )
                                ++ "{"
                                ++ atomize name
                                ++ ", "
                                ++ (List.map (rememberVariables varArgs c |> elixirE) (args ++ varArgs)
                                        |> String.join ", "
                                   )
                                ++ "}"
                                |> flip (++) (String.repeat dif " end")
                        else
                            Context.crash c <|
                                "Expected "
                                    ++ toString arity
                                    ++ " arguments for '"
                                    ++ name
                                    ++ "'. Got: "
                                    ++ toString (List.length args)
           )


{-| Returns True if an expression is type application or false if it's a regular application
-}
isTuple : Expression -> Bool
isTuple a =
    case a of
        Application a _ ->
            isTuple a

        Variable [ "()" ] ->
            True

        Variable [ name ] ->
            isCapitilzed name

        Variable list ->
            Helpers.moduleAccess "" list
                |> (\( _, last ) -> isTuple (Variable [ last ]))

        other ->
            False


{-| Create 'case' expression by passing a value being "cased on" and list of branches
-}
caseE : Context -> Expression -> List ( Expression, Expression ) -> String
caseE c var body =
    "case "
        ++ elixirE c var
        ++ " do"
        ++ String.join "" (List.map (rememberVariables [ var ] c |> caseBranch) body)
        ++ ind c.indent
        ++ "end"


{-| Create a single branch of case statement by giving left and right side of the arrow
-}
caseBranch : Context -> ( Expression, Expression ) -> String
caseBranch c ( left, right ) =
    (ind (c.indent + 1) ++ elixirE (inArgs c) left)
        ++ " ->"
        ++ ind (c.indent + 2)
        ++ elixirE (c |> indent |> indent |> rememberVariables [ left ]) right


{-| Used to encode a function and create a curried function from a lambda expression
-}
lambda : Context -> List Expression -> Expression -> String
lambda c args body =
    case args of
        arg :: rest ->
            "fn "
                ++ elixirE (inArgs c) arg
                ++ " -> "
                ++ lambda (c |> rememberVariables [ arg ]) rest body
                ++ " end"

        [] ->
            elixirE c body


{-| Produce a variable out of it's expression, considering some of the hardcoded values
used for easier interaction with Elixir
-}
elixirVariable : Context -> List String -> String
elixirVariable c var =
    case var of
        [] ->
            ""

        [ "()" ] ->
            "{}"

        [ "Nothing" ] ->
            "nil"

        [ "Just" ] ->
            "fn x1 -> {x1} end"

        [ "Err" ] ->
            "fn x1 -> {:error, x1} end"

        [ "Ok" ] ->
            "fn x1 -> {:ok, x1} end"

        [ "curry" ] ->
            "curried()"

        [ "uncurry" ] ->
            "uncurried()"

        list ->
            Helpers.moduleAccess c.mod list
                |> (\( mod, name ) ->
                        if isCapitilzed name then
                            aliasFor (Context.changeCurrentModule mod c) name []
                                |> Maybe.withDefault (atomize name)
                        else if String.startsWith "@" name then
                            String.dropLeft 1 name
                                |> atomize
                        else
                            case operatorType name of
                                Builtin ->
                                    -- We need a curried version, so kernel won't work
                                    if name == "<|" then
                                        "flip().((&|>/0).())"
                                    else
                                        "(&XBasics." ++ translateOperator name ++ "/0).()"

                                Custom ->
                                    translateOperator name

                                None ->
                                    name |> toSnakeCase True |> Variable.varOrNah c
                   )


================================================
FILE: src/Elchemy/Ffi.elm
================================================
module Elchemy.Ffi exposing (generateFfi)

import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (Type(TypeConstructor))
import Dict
import Elchemy.Context as Context exposing (Context, Parser, onlyWithoutFlag)
import Elchemy.Function as Function
import Elchemy.Helpers as Helpers
    exposing
        ( applicationToList
        , generateArguments
        , generateArguments_
        , ind
        , toSnakeCase
        )
import Elchemy.Type as Type
import Elchemy.Variable as Variable exposing (rememberVariables)


{-| Encodes and inlines a foreign function interface macro
-}
generateFfi :
    Context
    -> Parser
    -> String
    -> List (List Type)
    -> Expression
    -> String
generateFfi c elixirE name argTypes e =
    let
        typeDef =
            c.commons.modules
                |> Dict.get c.mod
                |> Maybe.andThen (.functions >> Dict.get name)

        appList =
            applicationToList e

        uncurryArguments c =
            uncurrify c elixirE argTypes

        wrapAllInVar =
            List.map <| List.singleton >> Variable
    in
        case ( typeDef, applicationToList e ) of
            ( Nothing, (Variable [ "ffi" ]) :: _ ) ->
                Context.crash c "Ffi requires type definition"

            ( Nothing, (Variable [ "macro" ]) :: _ ) ->
                Context.crash c "Macro requires type definition"

            ( Just def, [ Variable [ "ffi" ], String mod, String fun ] ) ->
                let
                    arguments =
                        generateArguments_ "a" def.arity
                in
                    Function.functionCurry c elixirE name def.arity []
                        ++ (onlyWithoutFlag c "noverify" name <|
                                ind c.indent
                                    ++ "verify as: "
                                    ++ mod
                                    ++ "."
                                    ++ fun
                                    ++ "/"
                                    ++ toString def.arity
                           )
                        ++ ind c.indent
                        ++ "def"
                        ++ Function.privateOrPublic c name
                        ++ " "
                        ++ toSnakeCase True name
                        ++ "("
                        ++ (arguments |> String.join ", ")
                        ++ ")"
                        ++ ", do: "
                        ++ mod
                        ++ "."
                        ++ fun
                        ++ "("
                        ++ (uncurryArguments (rememberVariables (wrapAllInVar arguments) c) |> String.join ", ")
                        ++ ")"

            ( Just def, [ Variable [ "macro" ], String mod, String fun ] ) ->
                let
                    arguments =
                        generateArguments_ "a" def.arity

                    varArgs =
                        wrapAllInVar arguments
                in
                    if Type.hasReturnedType (TypeConstructor [ "Macro" ] []) def.def then
                        "defmacro"
                            ++ Function.privateOrPublic c name
                            ++ " "
                            ++ toSnakeCase True name
                            ++ "("
                            ++ (arguments |> String.join ", ")
                            ++ ")"
                            ++ ", do: "
                            ++ mod
                            ++ "."
                            ++ fun
                            ++ "("
                            ++ (varArgs |> List.map (elixirE (rememberVariables varArgs c)) |> String.join ", ")
                            ++ ")"
                    else
                        Context.crash c "Macro calls have to return a Macro type"

            ( Just def, [ Variable [ "tryFfi" ], String mod, String fun ] ) ->
                let
                    arguments =
                        generateArguments_ "a" def.arity
                in
                    Function.functionCurry c elixirE name def.arity []
                        ++ ind c.indent
                        ++ "def"
                        ++ Function.privateOrPublic c name
                        ++ " "
                        ++ toSnakeCase True name
                        ++ "("
                        ++ (generateArguments_ "a" def.arity |> String.join ", ")
                        ++ ")"
                        ++ " do "
                        ++ ind (c.indent + 1)
                        ++ "try_catch fn -> "
                        ++ ind (c.indent + 2)
                        ++ mod
                        ++ "."
                        ++ fun
                        ++ "("
                        ++ (uncurryArguments (rememberVariables (wrapAllInVar arguments) c) |> String.join ", ")
                        ++ ")"
                        ++ ind (c.indent + 1)
                        ++ "end"
                        ++ ind c.indent
                        ++ "end"

            _ ->
                Context.crash c "Wrong ffi definition"


{-| Walk through function definition and uncurry all of the multi argument functions
-}
uncurrify : Context -> Parser -> List (List Type) -> List String
uncurrify c elixirE argTypes =
    let
        arity =
            List.length argTypes - 1

        indexes =
            List.range 1 arity
    in
        List.map2 (,) indexes argTypes
            |> List.map
                (\( i, arg ) ->
                    case arg of
                        [] ->
                            Context.crash c "Impossible"

                        [ any ] ->
                            "a" ++ toString i

                        list ->
                            let
                                var =
                                    Variable [ "a" ++ toString i ]

                                makeFlambda =
                                    Flambda <| List.length list - 1
                            in
                                resolveFfi c elixirE (makeFlambda var)
                )


type Ffi
    = Lffi Expression Expression
    | Ffi Expression Expression Expression
    | TryFfi Expression Expression Expression
    | Flambda Int Expression
    | Macro Expression Expression Expression


{-| encodes an ffi based on context and a parser
-}
resolveFfi : Context -> Parser -> Ffi -> String
resolveFfi c elixirE ffi =
    let
        combineComas args =
            args |> List.map (elixirE c) |> String.join ","
    in
        case ffi of
            TryFfi (String mod) (String fun) (Tuple args) ->
                "try_catch fn _ -> "
                    ++ mod
                    ++ "."
                    ++ fun
                    ++ "("
                    ++ combineComas args
                    ++ ")"
                    ++ " end"

            -- One or many arg fun
            TryFfi (String mod) (String fun) any ->
                "try_catch fn _ -> "
                    ++ mod
                    ++ "."
                    ++ fun
                    ++ "("
                    ++ elixirE c any
                    ++ ")"
                    ++ " end"

            -- Elchemy hack
            Ffi (String mod) (String fun) (Tuple args) ->
                mod ++ "." ++ fun ++ "(" ++ combineComas args ++ ")"

            -- One or many arg fun
            Ffi (String mod) (String fun) any ->
                mod ++ "." ++ fun ++ "(" ++ elixirE c any ++ ")"

            -- Elchemy hack
            Macro (String mod) (String fun) (Tuple args) ->
                mod ++ "." ++ fun ++ "(" ++ combineComas args ++ ")"

            -- One or many arg fun
            Macro (String mod) (String fun) any ->
                mod ++ "." ++ fun ++ "(" ++ elixirE c any ++ ")"

            -- Elchemy hack
            Lffi (String fun) (Tuple args) ->
                fun ++ "(" ++ combineComas args ++ ")"

            -- One arg fun
            Lffi (String fun) any ->
                fun ++ "(" ++ elixirE c any ++ ")"

            Flambda arity fun ->
                let
                    args =
                        generateArguments arity
                in
                    "fn ("
                        ++ String.join "," args
                        ++ ") -> "
                        ++ elixirE c fun
                        ++ (List.map (\a -> ".(" ++ a ++ ")") args
                                |> String.join ""
                           )
                        ++ " end"

            _ ->
                Context.crash c "Wrong ffi call"


================================================
FILE: src/Elchemy/Function.elm
================================================
module Elchemy.Function
    exposing
        ( functionCurry
        , genFunctionDefinition
        , genOverloadedFunctionDefinition
        , privateOrPublic
        )

import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (Type)
import Dict
import Elchemy.Context as Context exposing (Context, Parser, inArgs, indent)
import Elchemy.Helpers as Helpers
    exposing
        ( Operator(..)
        , generateArguments
        , ind
        , isCustomOperator
        , operatorType
        , toSnakeCase
        , translateOperator
        )
import Elchemy.Variable as Variable exposing (rememberVariables)


{-| Encodes a function defintion with all decorations like curry and type spec
-}
genFunctionDefinition :
    Context
    -> Parser
    -> String
    -> List Expression
    -> Expression
    -> String
genFunctionDefinition c elixirE name args body =
    let
        typeDef =
            c.commons.modules
                |> Dict.get c.mod
                |> Maybe.andThen (.functions >> Dict.get name)

        arity =
            typeDef |> Maybe.map .arity |> Maybe.withDefault 0

        lambdasAt =
            getLambdaArgumentIndexes (Maybe.map .def typeDef)
    in
        if Context.hasFlag "nodef" name c then
            functionCurry c elixirE name arity lambdasAt
        else
            functionCurry c elixirE name arity lambdasAt
                ++ genElixirFunc c elixirE name args (arity - List.length args) body
                ++ "\n"


{-| Generates an overloaded function defintion when body is matched on case
-}
genOverloadedFunctionDefinition :
    Context
    -> Parser
    -> String
    -> List Expression
    -> Expression
    -> List ( Expression, Expression )
    -> String
genOverloadedFunctionDefinition c elixirE name args body expressions =
    let
        typeDef =
            c.commons.modules
                |> Dict.get c.mod
                |> Maybe.andThen (.functions >> Dict.get name)

        arity =
            typeDef |> Maybe.map .arity |> Maybe.withDefault 0

        lambdasAt =
            getLambdaArgumentIndexes (Maybe.map .def typeDef)

        pairAsArgs asArgs =
            asArgs
                |> List.map2 (flip <| BinOp <| Variable [ "as" ]) args

        caseBranch ( left, right ) =
            case left of
                Tuple matchedArgs ->
                    genElixirFunc c elixirE name (pairAsArgs matchedArgs) (arity - List.length (pairAsArgs matchedArgs)) right

                _ ->
                    genElixirFunc c elixirE name (pairAsArgs [ left ]) (arity - 1) right
    in
        if Context.hasFlag "nodef" name c then
            functionCurry c elixirE name arity lambdasAt
        else
            functionCurry c elixirE name arity lambdasAt
                ++ (expressions
                        |> List.map caseBranch
                        |> List.foldr (++) ""
                        |> flip (++) "\n"
                   )


{-| Encodes a function defintion based on given params
-}
genElixirFunc :
    Context
    -> Parser
    -> String
    -> List Expression
    -> Int
    -> Expression
    -> String
genElixirFunc c elixirE name args missingArgs body =
    case ( operatorType name, args ) of
        ( Builtin, [ l, r ] ) ->
            [ ind c.indent
            , "def"
            , privateOrPublic c name
            , " "
            , elixirE (c |> rememberVariables [ l ]) l
            , " "
            , translateOperator name
            , " "
            , elixirE (rememberVariables [ r ] c) r
            , " do"
            , ind <| c.indent + 1
            , elixirE (indent c |> rememberVariables args) body
            , ind c.indent
            , "end"
            ]
                |> String.join ""

        ( Custom, _ ) ->
            [ ind c.indent
            , "def"
            , privateOrPublic c name
            , " "
            , translateOperator name
            , "("
            , args
                |> List.map (c |> rememberVariables args |> elixirE)
                |> flip (++) (generateArguments missingArgs)
                |> String.join ", "
            , ") do"
            , ind <| c.indent + 1
            , elixirE (indent c |> rememberVariables args) body
            , generateArguments missingArgs
                |> List.map (\a -> ".(" ++ a ++ ")")
                |> String.join ""
            , ind c.indent
            , "end"
            ]
                |> String.join ""

        ( Builtin, _ ) ->
            Context.crash c
                ("operator " ++ name ++ " has to have 2 arguments but has " ++ toString args)

        ( None, _ ) ->
            let
                missing =
                    generateArguments missingArgs

                wrapIfMiss s =
                    if List.length missing > 0 then
                        s
                    else
                        ""

                missingVarargs =
                    List.map (List.singleton >> Variable) missing
            in
                [ ind c.indent
                , "def"
                , privateOrPublic c name
                , " "
                , toSnakeCase True name
                , "("
                , args
                    ++ missingVarargs
                    |> List.map (c |> inArgs |> elixirE)
                    |> String.join ", "
                , ") do"
                , ind <| c.indent + 1
                , wrapIfMiss "("
                , elixirE (indent c |> rememberVariables (args ++ missingVarargs)) body
                , wrapIfMiss ")"
                , missing
                    |> List.map (\a -> ".(" ++ a ++ ")")
                    |> String.join ""
                , ind c.indent
                , "end"
                ]
                    |> String.join ""


{-| Returns "p" if a function is private. Else returns empty string
-}
privateOrPublic : Context -> String -> String
privateOrPublic context name =
    if Context.isPrivate context name then
        "p"
    else
        ""


{-| Encodes a curry macro for the function
-}
functionCurry : Context -> Parser -> String -> Int -> List ( Int, Int ) -> String
functionCurry c elixirE name arity lambdasAt =
    case ( arity, Context.hasFlag "nocurry" name c ) of
        ( 0, _ ) ->
            ""

        ( _, True ) ->
            ""

        ( arity, False ) ->
            let
                resolvedName =
                    if isCustomOperator name then
                        translateOperator name
                    else
                        toSnakeCase True name

                p =
                    privateOrPublic c name

                lambdas =
                    lambdasAt
                        |> List.map (\( a, b ) -> "{" ++ toString a ++ ", " ++ toString b ++ "}")
            in
                if lambdas == [] || p == "p" then
                    [ ind c.indent
                    , "curry"
                    , " "
                    , resolvedName
                    , "/"
                    , toString arity
                    ]
                        |> String.join ""
                else
                    [ ind c.indent
                    , "curry"
                    , " "
                    , resolvedName
                    , "/"
                    , toString arity
                    , ", lambdas: ["
                    , lambdas |> String.join ", "
                    , "]"
                    ]
                        |> String.join ""


{-| Gives indexes (starting from 0) of the arguments which
are lambdas with arity bigger than 1
-}
getLambdaArgumentIndexes : Maybe Type -> List ( Int, Int )
getLambdaArgumentIndexes t =
    Maybe.map Helpers.typeApplicationToList t
        |> Maybe.withDefault []
        |> List.map Helpers.typeApplicationToList
        |> List.indexedMap (,)
        -- -1 since a -> b is not 2 arity
        |> List.map (Tuple.mapSecond <| List.length >> (+) -1)
        |> List.filter (\( _, r ) -> r > 1)


================================================
FILE: src/Elchemy/Helpers.elm
================================================
module Elchemy.Helpers
    exposing
        ( (=>)
        , MaybeUpper(..)
        , Operator(..)
        , applicationToList
        , atomize
        , capitalize
        , constructApplication
        , escape
        , filterMaybe
        , findInList
        , generateArguments
        , generateArguments_
        , ind
        , indAll
        , indNoNewline
        , isCapitilzed
        , isCustomOperator
        , isStdModule
        , lastAndRest
        , listNonEmptyOr
        , listToApplication
        , maybeOr
        , maybeReplaceStd
        , moduleAccess
        , modulePath
        , operatorType
        , operators
        , ops
        , prependAll
        , replaceOp
        , replaceOp_
        , replaceReserved
        , reservedBasicFunctions
        , reservedKernelFunctions
        , reservedWords
        , toSnakeCase
        , translateOperator
        , trimIndentations
        , typeApplicationToList
        , uncons
        , unquoteSplicing
        )

import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (Type(..))
import Char
import Dict exposing (Dict)
import Regex exposing (HowMany(..), Regex(..), regex)


type MaybeUpper
    = Upper String
    | Lower String


{-| Convert string to snakecase, if the flag is set to true then it won't replace reserved words
-}
toSnakeCase : Bool -> String -> String
toSnakeCase isntAtom s =
    let
        safe x =
            if isntAtom then
                replaceReserved x
            else
                x
    in
        if String.toUpper s == s then
            String.toLower s
        else
            s
                |> Regex.split Regex.All (Regex.regex "(?=[A-Z])")
                |> String.join "_"
                |> String.toLower
                |> safe


capitalize : String -> String
capitalize s =
    String.uncons s
        |> Maybe.map (Tuple.mapFirst Char.toUpper >> uncurry String.cons)
        |> Maybe.withDefault ""


atomize : String -> String
atomize s =
    ":" ++ toSnakeCase False s


{-| Returns if string start with uppercase
-}
isCapitilzed : String -> Bool
isCapitilzed s =
    String.uncons s
        |> Maybe.map (Tuple.first >> Char.isUpper)
        |> Maybe.withDefault False


indNoNewline : Int -> String
indNoNewline i =
    List.repeat ((i + 1) * 2) " " |> String.join ""


ind : Int -> String
ind i =
    "\n" ++ indNoNewline i


prependAll : String -> String -> String
prependAll with target =
    String.lines target
        |> List.map
            (\line ->
                if String.trim line == "" then
                    line
                else
                    with ++ line
            )
        |> String.join "\n"


indAll : Int -> String -> String
indAll i s =
    "\n" ++ prependAll (String.dropLeft 1 (ind i)) s


uncons : List a -> ( Maybe a, List a )
uncons list =
    case list of
        a :: b ->
            ( Just a, b )

        [] ->
            ( Nothing, [] )


lastAndRest : List a -> ( Maybe a, List a )
lastAndRest list =
    list
        |> List.reverse
        |> uncons
        |> Tuple.mapSecond List.reverse


moduleAccess : String -> List String -> ( String, String )
moduleAccess defaultModule stringList =
    stringList
        |> List.reverse
        |> uncons
        |> Tuple.mapSecond (List.reverse >> listNonEmptyOr (String.join ".") defaultModule)
        |> Tuple.mapFirst (Maybe.withDefault "")
        |> (\( a, b ) -> ( b, a ))


listNonEmptyOr : (List a -> b) -> b -> List a -> b
listNonEmptyOr f b aList =
    case aList of
        [] ->
            b

        list ->
            f list


unquoteSplicing : String -> String
unquoteSplicing =
    Regex.replace All (regex "(^\\{|\\}$)") (\_ -> "")


operators : Dict String String
operators =
    [ ( "||", "||" )
    , ( "&&", "&&" )
    , ( "==", "==" )
    , ( "/=", "!=" )
    , ( "<", "<" )
    , ( ">", ">" )
    , ( ">=", ">=" )
    , ( "<=", "<=" )
    , ( "++", "++" )
    , ( "+", "+" )
    , ( "-", "-" )
    , ( "*", "*" )
    , ( "/", "/" )
    , ( ">>", ">>>" )
    , ( "<|", "" )
    , ( "<<", "" )
    , ( "|>", "|>" )

    -- Exception
    , ( "%", "rem" )

    -- Exception
    , ( "//", "div" )

    -- Exception
    --, ( "rem", "rem" )
    -- Exception
    , ( "^", "" )

    -- Exception
    , ( "::", "cons" )
    , ( "not", "!" )
    , ( ",", "tuple2" )
    , ( ",,", "tuple3" )
    , ( ",,,", "tuple4" )
    , ( ",,,,", "tuple5" )
    , ( "as", "=" )
    ]
        |> List.foldl (uncurry Dict.insert) Dict.empty


type Operator
    = None
    | Builtin
    | Custom


isCustomOperator : String -> Bool
isCustomOperator op =
    operatorType op == Custom


operatorType : String -> Operator
operatorType name =
    let
        is_builtin =
            operators
                |> Dict.keys
                |> List.any ((==) name)

        is_custom =
            Regex.contains (regex "^[+\\-\\/*=.$<>:&|^?%#@~!]+$") name
    in
        case ( is_builtin, is_custom ) of
            ( True, _ ) ->
                Builtin

            ( False, True ) ->
                Custom

            _ ->
                None


translateOperator : String -> String
translateOperator op =
    case Dict.get op operators of
        Just "" ->
            Debug.crash <|
                op
                    ++ " is not a valid or not implemented yet operator"

        Just key ->
            key

        _ ->
            replaceOp op


trimIndentations : String -> String
trimIndentations line =
    Regex.replace All (regex "\\s+\\n") (always "\n") line


generateArguments : Int -> List String
generateArguments =
    generateArguments_ "x"


generateArguments_ : String -> Int -> List String
generateArguments_ str n =
    List.range 1 n
        |> List.map toString
        |> List.map ((++) str)


escape : String -> String
escape s =
    Regex.replace All (regex "\\\\") (always "\\\\") s


ops : List ( Int, Char )
ops =
    [ '+', '-', '/', '*', '=', '.', '$', '<', '>', ':', '&', '|', '^', '?', '%', '#', '@', '~', '!' ] |> List.indexedMap (,)


{-| Gives a String representation of module path
-}
modulePath : List String -> String
modulePath list =
    let
        snakeIfLower a =
            if isCapitilzed a then
                a
            else
                toSnakeCase True a
    in
        list
            |> List.map snakeIfLower
            |> String.join "."
            |> maybeReplaceStd


maybeReplaceStd : String -> String
maybeReplaceStd s =
    if isStdModule s then
        "Elchemy.X" ++ s
    else
        s


isStdModule : String -> Bool
isStdModule a =
    List.member a
        [ "Basics"
        , "Bitwise"
        , "Char"
        , "Date"
        , "Debug"
        , "Dict"
        , "List"
        , "String"
        , "Maybe"
        , "Regex"
        , "Result"
        , "Set"
        , "String"
        , "Tuple"
        ]


reservedWords : List String
reservedWords =
    [ "fn"
    , "do"
    , "end"
    , "cond"
    , "receive"
    , "or"
    , "and"
    , "quote"
    , "unquote"
    , "unquote_splicing"
    , "module"
    , "use"
    ]


reservedBasicFunctions : List String
reservedBasicFunctions =
    [ -- From Elchemy STD
      "cons"
    , "compare"
    , "xor"
    , "negate"
    , "sqrt"
    , "clamp"
    , "logBase"
    , "e"
    , "pi"
    , "cos"
    , "sin"
    , "tan"
    , "acos"
    , "asin"
    , "atan"
    , "atan2"
    , "round"
    , "floor"
    , "ceiling"
    , "truncate"
    , "toFloat"
    , "toString"
    , "identity"
    , "always"
    , "flip"
    , "tuple2"
    , "tuple3"
    , "tuple4"
    , "tuple5"
    , "rec"
    ]


reservedKernelFunctions : List String
reservedKernelFunctions =
    [ -- From Elixir std
      "isTuple"
    , "abs"
    , "apply"
    , "binary_part"
    , "bit_size"
    , "byte_size"
    , "div"
    , "elem"
    , "exit"
    , "function_exported?"
    , "get_and_update_in"
    , "get_in"
    , "hd"
    , "inspect"
    , "is_atom"
    , "is_binary"
    , "is_bitstring"
    , "is_boolean"
    , "is_float"
    , "is_function"
    , "is_integer"
    , "is_list"
    , "is_map"
    , "is_number"
    , "is_pid"
    , "is_port"
    , "is_reference"
    , "is_tuple"
    , "length"
    , "macro_exported?"
    , "make_ref"
    , "map_size"
    , "max"
    , "min"
    , "node"
    , "not"
    , "pop_in"
    , "put_elem"
    , "put_in"
    , "rem"
    , "round"
    , "self"
    , "send"
    , "spawn"
    , "spawn_link"
    , "spawn_monitor"
    , "struct"
    , "struct!"
    , "throw"
    , "tl"
    , "trunc"
    , "tuple_size"
    , "update_in"
    ]


replaceOp : String -> String
replaceOp op =
    String.toList op
        |> List.map replaceOp_
        |> String.join ""
        |> flip (++) "__"


replaceOp_ : Char -> String
replaceOp_ op =
    case
        List.filter (\( i, o ) -> op == o) ops
    of
        ( index, _ ) :: _ ->
            "op" ++ toString index

        _ ->
            Debug.crash "Illegal op"


replaceReserved : String -> String
replaceReserved a =
    if List.member a reservedWords then
        a ++ "__"
    else
        a


{-| Change application into a list of expressions
-}
applicationToList : Expression -> List Expression
applicationToList application =
    case application of
        Application left right ->
            applicationToList left ++ [ right ]

        other ->
            [ other ]


{-| Change list of expressions into an application
-}
listToApplication : List Expression -> Expression
listToApplication list =
    case list of
        [] ->
            Debug.crash "Empty list to expression conversion"

        [ one ] ->
            one

        left :: rest ->
            Application left (listToApplication rest)


{-| Change type application into a list of expressions
-}
typeApplicationToList : Type -> List Type
typeApplicationToList application =
    case application of
        TypeApplication left right ->
            left :: typeApplicationToList right

        other ->
            [ other ]


{-| Construct application, rever of applicationToList function
-}
constructApplication : List String -> List Expression
constructApplication list =
    case list of
        [] ->
            Debug.crash "Wrong application"

        [ one ] ->
            [ Variable [ one ] ]

        head :: tail ->
            [ List.foldl (\a acc -> Application acc (Variable [ a ])) (Variable [ head ]) tail ]


{-| Nicer syntax for tuples
-}
(=>) : a -> b -> ( a, b )
(=>) =
    (,)
infixr 0 =>


{-| Take left maybe, or right maybe if Nothing
-}
maybeOr : Maybe a -> Maybe a -> Maybe a
maybeOr m1 m2 =
    case m1 of
        Just a ->
            m1

        Nothing ->
            m2


{-| Filter Maybe based on a predicate
-}
filterMaybe : (a -> Bool) -> Maybe a -> Maybe a
filterMaybe f m =
    flip Maybe.andThen m <|
        \a ->
            if f a then
                Just a
            else
                Nothing


{-| Finds a value in a list
-}
findInList : (a -> Bool) -> List a -> Maybe a
findInList f =
    flip List.foldl Nothing <|
        \a acc ->
            if f a then
                Just a
            else
                acc


================================================
FILE: src/Elchemy/Meta.elm
================================================
module Elchemy.Meta exposing (metaDefinition)

{-| Defines a meta module for macro interactions
-}

import Ast.Expression exposing (Expression(..))
import Dict
import Elchemy.Ast as Ast
import Elchemy.Context as Context exposing (Context)
import Elchemy.Expression as Expression
import Elchemy.Helpers as Helpers exposing (ind, modulePath)


type ImportOrRequire
    = Import String String Int
    | Require String


{-| Defines the meta module for Macro usage
-}
metaDefinition : Context -> String
metaDefinition c =
    let
        defMeta meta =
            "defmodule "
                ++ c.mod
                ++ ".Meta do"
                ++ ind c.indent
                ++ requiredImports
                ++ "\n"
                ++ ind c.indent
                ++ Expression.elixirE c meta
                ++ "\nend"

        getUsedFunctions =
            Ast.walkExpressionOutwards

        addMacro t acc =
            case t of
                Variable [ name ] ->
                    c.importedFunctions
                        |> Dict.get name
                        |> Maybe.map (\( mod, arity ) -> [ Import mod name arity ])
                        |> Maybe.withDefault []
                        |> (++) acc

                Access (Variable mods) _ ->
                    Require (modulePath mods) :: acc

                _ ->
                    acc

        requiredImports =
            c.meta
                |> Maybe.map (Ast.foldExpression addMacro [])
                |> Maybe.withDefault []
                |> List.foldl insertRequirement Dict.empty
                |> Dict.toList
                |> List.map
                    (\( k, v ) ->
                        case v of
                            [] ->
                                "require " ++ k

                            other ->
                                "import "
                                    ++ k
                                    ++ ", only: ["
                                    ++ (List.map stringify other |> String.join ",")
                                    ++ "]"
                    )
                |> String.join (ind c.indent)

        stringify ( name, arity ) =
            "{:" ++ name ++ ", " ++ toString arity ++ "}"

        insertRequirement rOrI dict =
            case rOrI of
                Require mod ->
                    dict
                        |> Dict.update mod (Maybe.withDefault [] >> Just)

                Import mod name arity ->
                    dict
                        |> Dict.update mod
                            (Maybe.map ((::) ( name, arity ))
                                >> Maybe.withDefault [ ( name, arity ) ]
                                >> Just
                            )
    in
        c.meta
            |> Maybe.map defMeta
            |> Maybe.withDefault ""


================================================
FILE: src/Elchemy/Operator.elm
================================================
module Elchemy.Operator exposing (elixirBinop)

import Ast.Expression exposing (Expression(..))
import Elchemy.Context as Context exposing (Context, Parser)
import Elchemy.Helpers as Helpers exposing (Operator(..), ind, operatorType, translateOperator)


{-| Encode binary operator inlcuding the researved ones
-}
elixirBinop : Context -> Parser -> String -> Expression -> Expression -> String
elixirBinop c elixirE op l r =
    case op of
        "//" ->
            "div(" ++ elixirE c l ++ ", " ++ elixirE c r ++ ")"

        "%" ->
            "rem(" ++ elixirE c l ++ ", " ++ elixirE c r ++ ")"

        "^" ->
            ":math.pow(" ++ elixirE c l ++ ", " ++ elixirE c r ++ ")"

        "::" ->
            "["
                ++ elixirE c l
                ++ " | "
                ++ elixirE c r
                ++ "]"

        "<<" ->
            elixirBinop c elixirE ">>" r l

        "<|" ->
            if l == Variable [ "Do" ] then
                "quote do " ++ elixirE c r ++ " end"
            else
                elixirBinop c elixirE "|>" r l

        "|>" ->
            "("
                ++ elixirE c l
                ++ (flattenPipes r
                        |> List.map (elixirE c)
                        |> List.map ((++) (ind c.indent ++ "|> ("))
                        |> List.map (flip (++) ").()")
                        |> String.join ""
                   )
                ++ ")"

        "as" ->
            elixirE c l
                ++ " = "
                ++ elixirE c r

        op ->
            case operatorType op of
                Builtin ->
                    [ "(", elixirE c l, " ", translateOperator op, " ", elixirE c r, ")" ]
                        |> String.join ""

                Custom ->
                    translateOperator op
                        ++ "("
                        ++ elixirE c l
                        ++ ", "
                        ++ elixirE c r
                        ++ ")"

                None ->
                    Context.crash c ("Illegal operator " ++ op)


{-| Flattens pipes into a list of expressions
-}
flattenPipes : Expression -> List Expression
flattenPipes e =
    case e of
        BinOp (Variable [ "|>" ]) l ((BinOp (Variable [ "|>" ]) r _) as n) ->
            [ l ] ++ flattenPipes n

        BinOp (Variable [ "|>" ]) l r ->
            [ l ] ++ [ r ]

        other ->
            [ other ]


================================================
FILE: src/Elchemy/Selector.elm
================================================
module Elchemy.Selector exposing (AccessMacro(..), AccessMacroType(..), Selector(..), maybeAccessMacro)

import Ast.Expression exposing (Expression(AccessFunction, Application, Variable))
import Char
import Elchemy.Context as Context exposing (Context)
import Elchemy.Helpers as Helpers
import List.Extra
import Regex


type Selector
    = Access String


type AccessMacroType
    = Get
    | Put
    | Update


type AccessMacro
    = AccessMacro AccessMacroType Int (List Selector)


getSelector : Context -> Expression -> Selector
getSelector c expression =
    case expression of
        AccessFunction name ->
            Access (Helpers.toSnakeCase True name)

        _ ->
            Context.crash c "The only allowed selectors are: .field"


maybeAccessMacro : Context -> Expression -> List Expression -> Maybe ( AccessMacro, List Expression )
maybeAccessMacro c call args =
    let
        accessMacroArgs arity args =
            case compare (List.length args) arity of
                LT ->
                    Context.crash c <|
                        "Access macros [updateIn/getIn/putIn] cannot be partially applied. Expecting "
                            ++ toString arity
                            ++ " selector arguments."

                EQ ->
                    ( List.map (getSelector c) args, [] )

                GT ->
                    List.Extra.splitAt arity args
                        |> Tuple.mapFirst (List.map <| getSelector c)
    in
        case ( call, args ) of
            ( Variable [ name ], args ) ->
                accessMacroType name
                    |> Maybe.map
                        (\( t, arity ) ->
                            let
                                ( selectors, rest ) =
                                    accessMacroArgs arity args
                            in
                                ( AccessMacro t arity selectors, rest )
                        )

            _ ->
                Nothing


accessMacroType : String -> Maybe ( AccessMacroType, Int )
accessMacroType string =
    let
        getArity =
            String.filter Char.isDigit
                >> String.toInt
                >> Result.withDefault 1

        getType x =
            [ ( "updateIn\\d?", Update )
            , ( "putIn\\d?", Put )
            , ( "getIn\\d?", Get )
            ]
                |> List.foldl
                    (\( match, res ) acc ->
                        case acc of
                            Nothing ->
                                if Regex.contains (Regex.regex match) x then
                                    Just res
                                else
                                    Nothing

                            res ->
                                res
                    )
                    Nothing
    in
        getType string
            |> Maybe.map (\t -> ( t, getArity string ))


================================================
FILE: src/Elchemy/Statement.elm
================================================
module Elchemy.Statement exposing (elixirS, moduleStatement)

import Ast
import Ast.BinOp exposing (operators)
import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (ExportSet(..), Statement(..), Type(..))
import Dict exposing (Dict)
import Elchemy.Alias as Alias
import Elchemy.Context as Context exposing (Context, deindent, indent, onlyWithoutFlag)
import Elchemy.Expression as Expression
import Elchemy.Ffi as Ffi
import Elchemy.Function as Function
import Elchemy.Helpers as Helpers
    exposing
        ( (=>)
        , Operator(..)
        , filterMaybe
        , ind
        , indAll
        , indNoNewline
        , isCustomOperator
        , modulePath
        , operatorType
        , prependAll
        , toSnakeCase
        , translateOperator
        , typeApplicationToList
        )
import Elchemy.Type as Type
import Regex exposing (HowMany(..), Regex, regex)


type ElchemyComment
    = Doc String
    | Ex String
    | Normal String
    | Flag String


type DocType
    = Fundoc
    | Typedoc
    | ModuleDoc


{-| Make sure first statement is a module declaration
-}
moduleStatement : Statement -> Context
moduleStatement s =
    case s of
        ModuleDeclaration path exports ->
            Context.empty (modulePath path) exports

        other ->
            Debug.crash "First statement must be module declaration"


typeDefinition : Context -> String -> List Type -> List Type -> Bool -> ( Context, String )
typeDefinition c name args types isUnion =
    let
        ( newC, code ) =
            c.lastDoc
                |> Maybe.map (elixirDoc c Typedoc name)
                |> Maybe.withDefault ( c, "" )

        getVariableName t =
            case t of
                TypeVariable name ->
                    name

                _ ->
                    Context.crash c (toString t ++ " is not a type variable")

        arguments =
            if args == [] then
                ""
            else
                "("
                    ++ (List.map getVariableName args |> String.join ", ")
                    ++ ")"

        mapType =
            (if isUnion then
                Type.uniontype { c | inTypeDefiniton = True }
             else
                Type.elixirT False { c | inTypeDefiniton = True }
            )
                << Alias.replaceTypeAliases c
    in
        (,) newC <|
            onlyWithoutFlag c "notype" name <|
                code
                    ++ ind c.indent
                    ++ "@type "
                    ++ toSnakeCase True name
                    ++ arguments
                    ++ " :: "
                    ++ (List.map mapType types |> String.join " | ")
                    ++ "\n"


{-| Encode any statement
-}
elixirS : Context -> Statement -> ( Context, String )
elixirS c s =
    case s of
        InfixDeclaration _ _ _ ->
            ( c, "" )

        TypeDeclaration (TypeConstructor [ name ] args) types ->
            typeDefinition c name args types True

        TypeAliasDeclaration (TypeConstructor [ name ] args) t ->
            typeDefinition c name args [ t ] False

        FunctionTypeDeclaration "meta" t ->
            if t == TypeConstructor [ "List" ] [ TypeConstructor [ "Macro" ] [] ] then
                ( c, "" )
            else
                Context.crash c "Function `meta` is reserved and its type has to be of List Macro"

        FunctionDeclaration "meta" [] body ->
            if not <| definitionExists "meta" c then
                Context.crash c "Function `meta` requires type definition of List Macro"
            else
                ( { c | meta = Just body }, "" )

        FunctionTypeDeclaration name typedef ->
            ( c, "" )

        FunctionDeclaration name args body ->
            let
                definition =
                    c.commons.modules
                        |> Dict.get c.mod
                        |> Maybe.andThen (.functions >> Dict.get name >> Maybe.map .def)
                        |> Maybe.map (Alias.replaceTypeAliases c)

                ( newC, code ) =
                    c.lastDoc
                        |> Maybe.map (elixirDoc c Fundoc name)
                        |> Maybe.withDefault ( c, "" )

                spec =
                    onlyWithoutFlag newC "nodef" name code
                        ++ (case operatorType name of
                                Builtin ->
                                    -- TODO implement operator specs
                                    ""

                                Custom ->
                                    definition
                                        |> Maybe.map
                                            (\def ->
                                                onlyWithoutFlag newC "nospec" name <|
                                                    ind newC.indent
                                                        ++ "@spec "
                                                        ++ translateOperator name
                                                        ++ Type.typespec newC def
                                            )
                                        |> Maybe.withDefault ""

                                None ->
                                    definition
                                        |> Maybe.map
                                            (\def ->
                                                onlyWithoutFlag newC "nospec" name <|
                                                    ind newC.indent
                                                        ++ "@spec "
                                                        ++ toSnakeCase True name
                                                        ++ Type.typespec newC def
                                            )
                                        |> Maybe.withDefault ""
                           )

                genFfi =
                    Ffi.generateFfi c Expression.elixirE name <|
                        (c.commons.modules
                            |> Dict.get c.mod
                            |> Maybe.andThen (.functions >> Dict.get name)
                            |> Maybe.map (.def >> typeApplicationToList)
                            |> Maybe.withDefault []
                            |> List.map typeApplicationToList
                        )

                isPrivate =
                    Context.isPrivate c name

                isTuple t =
                    case t of
                        Tuple _ ->
                            True

                        _ ->
                            False
            in
                newC
                    => (case body of
                            (Application (Application (Variable [ "ffi" ]) _) _) as app ->
                                spec
                                    ++ ind (c.indent + 1)
                                    ++ genFfi app

                            (Application (Application (Variable [ "tryFfi" ]) _) _) as app ->
                                spec
                                    ++ ind (c.indent + 1)
                                    ++ genFfi app

                            (Application (Application (Variable [ "macro" ]) _) _) as app ->
                                ind c.indent
                                    ++ genFfi app
                                    ++ "\n"

                            -- Case ((Variable [ _ ]) as var) expressions ->
                            --     if [ var ] == args then
                            --         Function.genOverloadedFunctionDefinition c Expression.elixirE name args body expressions
                            --     else
                            --         Function.genFunctionDefinition c Expression.elixirE name args body
                            --
                            -- Case (Tuple vars) expressions ->
                            --     if vars == args && List.all (Tuple.first >> isTuple) expressions then
                            --         Function.genOverloadedFunctionDefinition c Expression.elixirE name args body expressions
                            --     else
                            --         Function.genFunctionDefinition c Expression.elixirE name args body
                            _ ->
                                spec
                                    ++ Function.genFunctionDefinition c Expression.elixirE name args body
                       )

        Comment content ->
            elixirComment c content

        -- That's not a real import. In elixir it's called alias
        ImportStatement path aliasedAs Nothing ->
            (c |> Context.addModuleAlias (modulePath path) aliasedAs)
                => ind c.indent
                ++ "alias "
                ++ modulePath path
                ++ aliasAs aliasedAs

        ImportStatement path aliasedAs (Just ((SubsetExport exports) as subset)) ->
            let
                mod =
                    modulePath path

                imports =
                    List.map exportSetToList exports
                        |> List.foldr (++) []

                excepts =
                    c.commons.modules
                        |> Dict.get c.mod
                        |> Maybe.map (.functions >> Dict.keys >> duplicates imports)
                        |> Maybe.withDefault []

                only =
                    if imports == [] then
                        []
                    else
                        [ "only: ["
                            ++ String.join ", " (elixirExportList c mod imports)
                            ++ "]"
                        ]

                except =
                    if excepts == [] then
                        []
                    else
                        [ "except: ["
                            ++ String.join ", " (elixirExportList c mod excepts)
                            ++ "]"
                        ]

                importOrAlias =
                    if imports == [] && excepts == [] then
                        "alias "
                    else
                        "import "

                newC =
                    c
                        |> Context.addModuleAlias mod aliasedAs
                        |> insertImports mod subset
                        |> Context.mergeTypes subset (modulePath path)
            in
                newC
                    => ind newC.indent
                    ++ importOrAlias
                    ++ ([ [ modulePath path ], only, except ]
                            |> List.foldr (++) []
                            |> String.join ", "
                       )
                    ++ aliasAs aliasedAs

        -- Suppresses the compiler warning
        ImportStatement [ "Elchemy" ] Nothing (Just AllExport) ->
            ( c, "" )

        ImportStatement modPath aliasedAs (Just AllExport) ->
            let
                mod =
                    modulePath modPath

                exports =
                    c.commons.modules
                        |> Dict.get mod
                        |> Maybe.map (.functions >> Dict.keys)
                        |> Maybe.withDefault []

                excepts =
                    c.commons.modules
                        |> Dict.get c.mod
                        |> Maybe.map (.functions >> Dict.keys >> duplicates exports)
                        |> Maybe.withDefault []

                except =
                    if excepts == [] then
                        []
                    else
                        [ "except: ["
                            ++ String.join ", " (elixirExportList c mod excepts)
                            ++ "]"
                        ]

                newC =
                    c
                        |> Context.addModuleAlias mod aliasedAs
                        |> insertImports mod AllExport
                        |> Context.mergeTypes AllExport mod
            in
                newC
                    => ind c.indent
                    ++ "import "
                    ++ ([ [ mod ], except ]
                            |> List.foldr (++) []
                            |> String.join ", "
                       )
                    ++ aliasAs aliasedAs

        s ->
            (,) c <|
                Context.notImplemented c "statement" s


{-| Returns a String "as: ModuleAlias" or empty string if no module alias
-}
aliasAs : Maybe String -> String
aliasAs =
    Maybe.map (\newName -> ", as: " ++ newName)
        >> Maybe.withDefault ""


{-| Returns True if
-}
definitionExists : String -> Context -> Bool
definitionExists name c =
    c.commons.modules
        |> Dict.get c.mod
        |> Maybe.andThen (.functions >> Dict.get name)
        |> (/=) Nothing


{-| Based on ExportSet of the `import` call inserts all of the imported types
and functions into the current context
-}
insertImports : String -> ExportSet -> Context -> Context
insertImports mod subset c =
    let
        exportNames =
            Type.getExportedTypeNames c mod subset

        importedFunctions subset =
            case subset of
                AllExport ->
                    c.commons.modules
                        |> Dict.get mod
                        |> Maybe.map .functions
                        |> Maybe.map Dict.toList
                        |> Maybe.withDefault []
                        |> List.map (\( key, { arity } ) -> ( key, arity ))

                SubsetExport list ->
                    List.concatMap importedFunctions list

                FunctionExport name ->
                    c.commons.modules
                        |> Dict.get mod
                        |> Maybe.map .functions
                        |> Maybe.andThen (Dict.get name)
                        |> Maybe.map (\{ arity } -> [ ( name, arity ) ])
                        |> Maybe.withDefault []

                TypeExport _ _ ->
                    []
    in
        { c
            | importedTypes = List.foldl (flip Dict.insert mod) c.importedTypes exportNames
            , importedFunctions =
                importedFunctions subset
                    |> List.foldl (\( f, arity ) acc -> Dict.insert f ( mod, arity ) acc) c.importedFunctions
        }


{-| Verify correct flag format
-}
verifyFlag : Context -> List String -> Maybe ( String, String )
verifyFlag c flag =
    case flag of
        [ k, v ] ->
            Just ( k, v )

        [ "" ] ->
            Nothing

        a ->
            Context.crash c <| "Wrong flag format " ++ toString a


{-| Encode elixir comment and return a context with updated last doc
-}
elixirComment : Context -> String -> ( Context, String )
elixirComment c content =
    case getCommentType content of
        Doc content ->
            if c.hasModuleDoc then
                { c | lastDoc = Just content } => ""
            else
                elixirDoc c ModuleDoc c.mod content

        Ex content ->
            (,) c <|
                (content
                    |> String.split "\n"
                    |> List.map (Regex.replace All (regex "^   ") (always ""))
                    -- |> List.map String.trim
                    |> String.join "\n"
                    |> indAll c.indent
                )

        Flag content ->
            flip (,) "" <|
                (content
                    |> Regex.split All (regex "\\s+")
                    |> List.map (String.split ":+")
                    |> List.filterMap (verifyFlag c)
                    |> List.foldl Context.addFlag c
                )

        Normal content ->
            (,) c <|
                (content
                    |> prependAll "# "
                    |> indAll c.indent
                )


{-| Enocode a doc and return new context
-}
elixirDoc : Context -> DocType -> String -> String -> ( Context, String )
elixirDoc c doctype name content =
    let
        prefix =
            if not c.hasModuleDoc then
                "@moduledoc"
            else if doctype == Fundoc then
                "@doc"
            else
                "@typedoc"
    in
        (,)
            { c
                | hasModuleDoc = True
                , lastDoc = Nothing
            }
        <|
            ind c.indent
                ++ prefix
                ++ " \"\"\"\n "
                ++ (content
                        |> String.lines
                        |> List.map (maybeDoctest c name)
                        |> List.map Helpers.escape
                        |> List.map (Regex.replace All (regex "\"\"\"") (always "\\\"\\\"\\\""))
                        -- |> map trimIndentations
                        |> String.join (ind c.indent)
                        -- Drop an unnecessary ammounts of \n's
                        |> Regex.replace All (regex "\n(\n| ){3,}\n") (always "\n\n")
                   )
                ++ ind c.indent
                ++ "\"\"\""


{-| Get a type of the comment by it's content
-}
getCommentType : String -> ElchemyComment
getCommentType comment =
    let
        findCommentType regex commentType acc =
            case acc of
                Normal content ->
                    if Regex.contains regex content then
                        commentType <|
                            Regex.replace (Regex.AtMost 1) regex (always "") content
                    else
                        Normal content

                other ->
                    other
    in
        [ ( "^\\sex\\b", Ex )
        , ( "^\\|", Doc )
        , ( "^\\sflag\\b", Flag )
        ]
            |> List.map (\( a, b ) -> ( Regex.regex a, b ))
            |> List.foldl (uncurry findCommentType) (Normal comment)


{-| Encode all exports from a module
-}
exportSetToList : ExportSet -> List String
exportSetToList exp =
    case exp of
        TypeExport _ _ ->
            []

        FunctionExport name ->
            [ name ]

        AllExport ->
            []

        SubsetExport _ ->
            []


elixirExportList : Context -> String -> List String -> List String
elixirExportList c mod list =
    let
        defineFor name arity =
            "{:'"
                ++ name
                ++ "', "
                ++ toString arity
                ++ "}"

        wrap name =
            if isCustomOperator name then
                defineFor (translateOperator name) 0
                    ++ ", "
                    ++ defineFor (translateOperator name) 2
            else if name == "ffi" then
                ""
            else
                defineFor (toSnakeCase True name) 0
                    ++ (c.commons.modules
                            |> Dict.get mod
                            |> Maybe.map .functions
                            |> Maybe.andThen (Dict.get name)
                            |> Maybe.map .arity
                            |> filterMaybe ((/=) 0)
                            |> Maybe.map (defineFor (toSnakeCase True name))
                            |> Maybe.map ((++) ", ")
                            |> Maybe.withDefault ""
                       )
    in
        List.map wrap list


duplicates : List a -> List a -> List a
duplicates listA listB =
    List.filter (flip List.member listB) listA


{-| Replace a function doc with a doctest if in correct format
-}
maybeDoctest : Context -> String -> String -> String
maybeDoctest c forName line =
    if String.startsWith (ind (c.indent + 1)) ("\n" ++ line) then
        case Ast.parseExpression Ast.BinOp.operators (String.trim line) of
            Ok ( _, _, BinOp (Variable [ "==" ]) l r ) ->
                let
                    shadowed =
                        Context.getShadowedFunctions c Helpers.reservedBasicFunctions
                            ++ Context.getShadowedFunctions c Helpers.reservedKernelFunctions
                            |> List.filter (Tuple.first >> (==) forName)

                    importBasics =
                        if shadowed == [] then
                            ""
                        else
                            Context.importBasicsWithoutShadowed c
                                |> String.trimRight
                                |> String.split "\n"
                                |> String.join (ind (c.indent + 2) ++ "iex> ")
                                |> (++) (indNoNewline 1 ++ "iex> ")
                                |> flip (++) (ind 0)
                in
                    importBasics
                        ++ indNoNewline (c.indent + 1)
                        ++ "iex> import "
                        ++ c.mod
                        ++ ind (c.indent + 2)
                        ++ "iex> "
                        ++ Expression.elixirE c l
                        ++ ind (c.indent + 2)
                        ++ Expression.elixirE c r
                        ++ "\n"

            _ ->
                line
    else
        line


================================================
FILE: src/Elchemy/Type.elm
================================================
module Elchemy.Type exposing (elixirT, getExportedTypeNames, hasReturnedType, typeAliasConstructor, typespec, uniontype)

import Ast.Expression exposing (Expression(..))
import Ast.Statement exposing (ExportSet(..), Type(..))
import Dict
import Elchemy.Alias as Alias
import Elchemy.Context as Context exposing (Context, indent)
import Elchemy.Helpers as Helpers
    exposing
        ( atomize
        , filterMaybe
        , ind
        , lastAndRest
        , toSnakeCase
        , typeApplicationToList
        )


{-| Enocde any elm type
-}
elixirT : Bool -> Context -> Type -> String
elixirT flatten c t =
    case t of
        TypeTuple [] ->
            "no_return"

        TypeTuple [ a ] ->
            elixirT flatten c a

        TypeTuple ((a :: rest) as list) ->
            "{"
                ++ (List.map (elixirT flatten c) list |> String.join ", ")
                ++ "}"

        TypeVariable "number" ->
            "number"

        (TypeVariable name) as var ->
            case String.uncons name of
                Just ( '@', name ) ->
                    toSnakeCase True name

                any ->
                    if c.inTypeDefiniton then
                        name
                    else
                        "any"

        TypeConstructor t args ->
            let
                ( mod, last ) =
                    Helpers.moduleAccess c.mod t

                modulePath =
                    if mod == c.mod then
                        c.importedTypes
                            |> Dict.get last
                            |> Maybe.map (\a -> a ++ ".")
                            |> Maybe.withDefault ""
                    else
                        mod ++ "."
            in
                modulePath ++ elixirType flatten c last args

        TypeRecord fields ->
            "%{"
                ++ ind (c.indent + 1)
                ++ (fields
                        |> List.map (\( k, v ) -> toSnakeCase False k ++ ": " ++ elixirT flatten (indent c) v)
                        |> String.join ("," ++ ind (c.indent + 1))
                   )
                ++ ind c.indent
                ++ "}"

        (TypeRecordConstructor _ _) as tr ->
            "%{"
                ++ ind (c.indent + 1)
                ++ (typeRecordFields (indent c) flatten tr
                        |> String.join (", " ++ ind (c.indent + 1))
                   )
                ++ ind c.indent
                ++ "}"

        TypeApplication l r ->
            if flatten then
                typeApplicationToList r
                    |> lastAndRest
                    |> (\( last, rest ) ->
                            "("
                                ++ ((l :: rest)
                                        |> List.map (elixirT flatten (indent c))
                                        |> String.join ", "
                                   )
                                ++ " -> "
                                ++ (last
                                        |> Maybe.map (elixirT flatten c)
                                        |> Maybe.withDefault ""
                                   )
                                ++ ")"
                       )
            else
                "("
                    ++ elixirT flatten c l
                    ++ " -> "
                    ++ elixirT flatten c r
                    ++ ")"


{-| alias for elixirT with flatting of type application
-}
elixirTFlat : Context -> Type -> String
elixirTFlat =
    elixirT True


{-| alias for elixirT without flatting of type application
-}
elixirTNoFlat : Context -> Type -> String
elixirTNoFlat =
    elixirT False


{-| Return fieilds of type record as a list of string key value pairs
-}
typeRecordFields : Context -> Bool -> Type -> List String
typeRecordFields c flatten t =
    let
        keyValuePair ( k, v ) =
            k ++ ": " ++ elixirT flatten c v
    in
        case t of
            TypeRecordConstructor (TypeConstructor [ name ] args) fields ->
                let
                    inherited =
                        Context.getAlias c.mod name c
                            |> Maybe.map (\{ typeBody } -> Alias.resolveTypeBody c typeBody args)
                            |> Maybe.map (typeRecordFields c flatten)
                in
                    List.map keyValuePair fields
                        ++ Maybe.withDefault [ "" ] inherited

            TypeRecordConstructor (TypeRecord inherited) fields ->
                List.map keyValuePair <| fields ++ inherited

            TypeRecordConstructor (TypeVariable _) fields ->
                List.map keyValuePair fields

            TypeRecordConstructor (TypeTuple [ a ]) fields ->
                typeRecordFields c flatten (TypeRecordConstructor a fields)

            TypeRecordConstructor ((TypeRecordConstructor _ _) as tr) fields ->
                List.map keyValuePair fields
                    ++ typeRecordFields c flatten tr

            (TypeRecord fields) as tr ->
                List.map keyValuePair fields

            any ->
                Context.crash c ("Wrong type record constructor " ++ toString any)


{-| Translate and encode Elm type to Elixir type
-}
elixirType : Bool -> Context -> String -> List Type -> String
elixirType flatten c name args =
    case ( name, args ) of
        ( "String", [] ) ->
            "String.t"

        ( "Char", [] ) ->
            "integer"

        ( "Bool", [] ) ->
            "boolean"

        ( "Int", [] ) ->
            "integer"

        ( "Pid", [] ) ->
            "pid"

        ( "Float", [] ) ->
            "float"

        ( "List", [ t ] ) ->
            "list(" ++ elixirT flatten c t ++ ")"

        -- ( "Dict", [ key, val ] ) ->
        --     "%{}"
        ( "Maybe", [ t ] ) ->
            "{" ++ elixirT flatten c t ++ "} | nil"

        ( "Nothing", [] ) ->
            "nil"

        ( "Just", [ t ] ) ->
            elixirT flatten c t

        ( "Err", [ t ] ) ->
            "{:error, " ++ elixirT flatten c t ++ "}"

        ( "Ok", [ t ] ) ->
            if t == TypeTuple [] then
                "ok"
            else
                "{:ok," ++ elixirT flatten c t ++ "}"

        ( t, [] ) ->
            toSnakeCase True t

        -- aliasOr c t [] (atomize t)
        ( t, list ) ->
            toSnakeCase True t
                ++ "("
                ++ (List.map (elixirT flatten c) list |> String.join ", ")
                ++ ")"



-- aliasOr c t list <|
--     "{"
--         ++ atomize t
--         ++ ", "
--         ++ (List.map (elixirT flatten c) list |> String.join ", ")
--         ++ "}"


{-| Gets all types from a subset export
-}
getExportedTypeNames : Context -> String -> ExportSet -> List String
getExportedTypeNames c mod subset =
    case subset of
        SubsetExport list ->
            List.concatMap (getExportedTypeNames c mod) list

        TypeExport name _ ->
            [ name ]

        AllExport ->
            c.commons.modules
                |> Dict.get mod
                |> Maybe.map (\mod -> (mod.aliases |> Dict.keys) ++ (mod.types |> Dict.keys))
                |> Maybe.withDefault []

        FunctionExport _ ->
            []


{-| Enocde a typespec with 0 arity
-}
typespec0 : Context -> Type -> String
typespec0 c t =
    "() :: " ++ elixirTNoFlat c t


{-| Encode a typespec
-}
typespec : Context -> Type -> String
typespec c t =
    case t |> typeApplicationToList |> lastAndRest of
        ( Just last, args ) ->
            "("
                ++ (List.map (elixirTNoFlat c) args
                        |> String.join ", "
                   )
                ++ ") :: "
                ++ elixirTNoFlat c last

        ( Nothing, _ ) ->
            Context.crash c "impossible"


{-| Encode a union type
-}
uniontype : Context -> Type -> String
uniontype c t =
    case t of
        TypeConstructor [ name ] [] ->
            atomize name

        TypeConstructor [ name ] list ->
            "{"
                ++ atomize name
                ++ ", "
                ++ (List.map (elixirTNoFlat c) list |> String.join ", ")
                ++ "}"

        other ->
            Context.crash c ("I am looking for union type constructor. But got " ++ toString other)


{-| Change a constructor of a type alias into an expression after resolving it from contextual alias
-}
typeAliasConstructor : List Expression -> Context.Alias -> Maybe Expression
typeAliasConstructor args ({ parentModule, aliasType, arity, body, typeBody } as ali) =
    case ( aliasType, body ) of
        ( Context.Type, _ ) ->
            Nothing

        ( _, TypeConstructor [ name ] _ ) ->
            Nothing

        ( _, TypeRecord kvs ) ->
            let
                params =
                    List.length kvs
                        |> (+) (0 - List.length args)
                        |> List.range 1
                        |> List.map (toString >> (++) "arg")
                        |> List.map (List.singleton >> Variable)

                varargs =
                    kvs
                        |> List.map2 (flip (,)) (args ++ params)
                        |> List.map (Tuple.mapFirst Tuple.first)
            in
                Record varargs
                    |> Lambda params
                    |> Just

        -- Error in AST. Single TypeTuple are just paren app
        ( _, TypeTuple [ app ] ) ->
            typeAliasConstructor args { ali | typeBody = Context.SimpleType app }

        ( _, TypeTuple kvs ) ->
            let
                args =
                    List.length kvs
                        |> List.range 1
                        |> List.map (toString >> (++) "arg")
                        |> List.map (List.singleton >> Variable)
            in
                Just (Lambda args (Tuple args))

        ( _, TypeVariable name ) ->
            Just (Variable [ name ])

        other ->
            Nothing


{-| Apply alias, orelse return the provided default value
-}
aliasOr : Context -> String -> List Type -> String -> String
aliasOr c name args default =
    Context.getAlias c.mod name c
        |> (Maybe.map <|
                \{ parentModule, typeBody, aliasType } ->
                    if parentModule == c.mod then
                        elixirTNoFlat c (Alias.resolveTypeBody c typeBody args)
                    else
                        case aliasType of
                            Context.Type ->
                                parentModule ++ "." ++ elixirTNoFlat c (Alias.resolveTypeBody c typeBody args)

                            Context.TypeAlias ->
                                Alias.resolveTypeBody c typeBody args
                                    |> elixirTNoFlat { c | mod = parentModule }
           )
        |> Maybe.withDefault default


hasReturnedType : Type -> Type -> Bool
hasReturnedType returned t =
    case List.reverse (typeApplicationToList t) of
        [] ->
            False

        t :: _ ->
            t == returned


================================================
FILE: src/Elchemy/Variable.elm
================================================
module Elchemy.Variable
    exposing
        ( extractName
        , groupByCrossDependency
        , organizeLetInVariablesOrder
        , rememberVariables
        , varOrNah
        )

import Ast.Expression exposing (..)
import Elchemy.Context as Context exposing (Context, inArgs)
import Elchemy.Helpers as Helpers exposing (toSnakeCase)
import List.Extra
import Set


{-| Put variables into context so they are treated like variables in future
-}
rememberVariables : List Expression -> Context -> Context
rememberVariables list c =
    let
        addToContext var context =
            { context
                | variables = Set.insert (toSnakeCase True var) context.variables
            }
    in
        list
            |> List.map extractVariablesUsed
            |> List.foldr (++) []
            |> List.foldl addToContext c


{-| Check if a string is a variable or no, based on remembered variables
-}
varOrNah : Context -> String -> String
varOrNah c var =
    if Set.member var c.variables || c.inArgs then
        var
    else if c.inMeta then
        c.mod ++ "." ++ var ++ "()"
    else
        var ++ "()"


{-| Extract variables from an expression
-}
extractVariablesUsed : Expression -> List String
extractVariablesUsed exp =
    let
        many vars =
            vars
                |> List.map extractVariablesUsed
                |> List.foldr (++) []

        one var =
            [ var ]

        none =
            []
    in
        case exp of
            Record vars ->
                vars
                    |> List.map Tuple.second
                    |> many

            Tuple vars ->
                many vars

            Variable [ name ] ->
                one name

            List vars ->
                many vars

            Application left right ->
                many [ left, right ]

            BinOp (Variable [ "::" ]) x xs ->
                many [ x, xs ]

            BinOp (Variable [ "as" ]) ((Variable _) as v1) ((Variable _) as v2) ->
                many [ v1, v2 ]

            BinOp (Variable [ "as" ]) l ((Variable [ _ ]) as r) ->
                many [ l, r ]

            BinOp _ l r ->
                many [ l, r ]

            -- Assignments
            Case head branches ->
                List.concatMap (uncurry rightWithoutLeft) branches
                    |> withoutVars (extractVariablesUsed head)

            Let definitions return ->
                List.concatMap (uncurry rightWithoutLeft) definitions

            Lambda head body ->
                extractVariablesUsed body
                    |> withoutVars (List.concatMap extractVariablesUsed head)

            _ ->
                none


{-| Organize let in variables in order based on how they use each other
Example:
a = b
b = 1
Would become:
b = 1
a = b
-}
organizeLetInVariablesOrder : Context -> List ( Expression, Expression ) -> List ( Expression, Expression )
organizeLetInVariablesOrder c expressionList =
    case bubbleSelect (\a b -> not <| isIn a b) expressionList of
        Ok list ->
            list

        Err list ->
            let
                _ =
                    Context.crash c <|
                        "Couldn't find a solution to "
                            ++ toString (list |> List.map Tuple.first)
            in
                []


{-| Returns a name of a variable, or a name of a function being applied
-}
extractName : Context -> Expression -> String
extractName c expression =
    case Helpers.applicationToList expression of
        [ Variable [ name ] ] ->
            name

        [ single ] ->
            Context.crash c (toString single ++ " is not a variable")

        multi ->
            List.head multi
                |> Maybe.map (extractName c)
                |> Maybe.withDefault ""


{-| Returns a list of names of a variable, or a name of a function being applied

a = 1 --> ["a"]
f a b c = 1 --> ["f"]
(a, b, c) = 1 --> ["a", "b", "c"]

-}
extractNamesAssigned : Expression -> List String
extractNamesAssigned expression =
    case Helpers.applicationToList expression of
        [ Variable [ name ] ] ->
            [ name ]

        [ single ] ->
            extractVariablesUsed single

        multi ->
            List.head multi
                |> Maybe.map extractNamesAssigned
                |> Maybe.withDefault []


{-| Extracts only the arguments of an expression (if the expression is a function)
-}
extractArguments : Expression -> List String
extractArguments expression =
    case Helpers.applicationToList expression of
        [ single ] ->
            []

        multi ->
            List.tail multi
                |> Maybe.map (List
Download .txt
gitextract_du00lrkm/

├── .gitattributes
├── .gitignore
├── .gitmodules
├── .npmrc
├── .tool-versions
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE
├── LICENSE
├── Main.elm
├── Makefile
├── README.md
├── book.json
├── bump.sh
├── elchemy
├── elchemy_ex/
│   ├── .gitignore
│   ├── README.md
│   ├── config/
│   │   └── config.exs
│   ├── elm/
│   │   └── Hello.elm
│   ├── elm-package.json
│   ├── mix.exs
│   └── test/
│       ├── elchemy_ex_test.exs
│       └── test_helper.exs
├── elchemy_node.js
├── elm-package.json
├── elmchemy
├── lib/
│   └── mix/
│       ├── mix.exs
│       └── tasks/
│           └── compile.elchemy.ex
├── mix.exs
├── package.json
├── roadmap/
│   ├── BASIC_TYPES.md
│   ├── COMMENTS.md
│   ├── FLAGS.md
│   ├── FUNCTIONS.md
│   ├── INLINING.md
│   ├── INSTALLATION.md
│   ├── INTEROP.md
│   ├── MODULES.md
│   ├── README.md
│   ├── SIDE_EFFECTS.md
│   ├── STRUCTURES.md
│   ├── SUMMARY.md
│   ├── SYNTAX.md
│   ├── TESTING.md
│   ├── TROUBLESHOOTING.md
│   ├── TYPES.md
│   └── TYPE_ALIASES.md
├── src/
│   └── Elchemy/
│       ├── Alias.elm
│       ├── Ast.elm
│       ├── Compiler.elm
│       ├── Context.elm
│       ├── Expression.elm
│       ├── Ffi.elm
│       ├── Function.elm
│       ├── Helpers.elm
│       ├── Meta.elm
│       ├── Operator.elm
│       ├── Selector.elm
│       ├── Statement.elm
│       ├── Type.elm
│       └── Variable.elm
├── templates/
│   ├── Hello.elm
│   ├── elchemy.exs
│   ├── elchemy_test.exs
│   └── elm-package.json
└── tests/
    ├── .gitignore
    ├── Main.elm
    ├── Tests.elm
    ├── UnitTests.elm
    └── elm-package.json
Download .txt
SYMBOL INDEX (29 symbols across 8 files)

FILE: elchemy_ex/mix.exs
  class ElchemyEx.Mixfile (line 1) | defmodule ElchemyEx.Mixfile
    method project (line 4) | def project do
    method application (line 19) | def application do
    method deps (line 26) | defp deps do

FILE: elchemy_ex/test/elchemy_ex_test.exs
  class ElchemyExTest (line 1) | defmodule ElchemyExTest

FILE: elchemy_node.js
  function execute (line 8) | function execute(tree, fullTree, treeAndCommons) {

FILE: lib/mix/mix.exs
  class Elchemy.Mixfile (line 1) | defmodule Elchemy.Mixfile
    method project (line 4) | def project do
    method package (line 18) | defp package do
    method application (line 31) | def application do
    method deps (line 45) | defp deps do

FILE: lib/mix/tasks/compile.elchemy.ex
  class Mix.Tasks.Compile.Elchemy (line 1) | defmodule Mix.Tasks.Compile.Elchemy
    method run (line 4) | def run(_args) do

FILE: mix.exs
  class Elchemy.Mixfile (line 1) | defmodule Elchemy.Mixfile
    method project (line 4) | def project do
    method package (line 18) | defp package do
    method application (line 31) | def application do
    method deps (line 36) | defp deps do

FILE: templates/elchemy.exs
  class ElchemyInit (line 1) | defmodule ElchemyInit
    method init (line 5) | def init(project) do
    method elm_deps (line 22) | def elm_deps() do
    method check_mix_file (line 34) | def check_mix_file(paths) do
    method create_mix_file (line 47) | def create_mix_file(mix_file, app_name, version) do
    method app_info_from_path (line 81) | def app_info_from_path(path) do
    method find! (line 92) | def find!(dir, depth), do: find!([], dir, depth)
    method find! (line 93) | def find!(dirs, dir, 0), do: [dir | dirs]
    method find! (line 94) | def find!(dirs, dir, depth) do
    method parse_app_name (line 102) | def parse_app_name(mix_file) do

FILE: templates/elchemy_test.exs
  class ElchemyTest (line 1) | defmodule ElchemyTest
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (235K chars).
[
  {
    "path": ".gitattributes",
    "chars": 55,
    "preview": "stable/* linguist-vendored\nexample/* linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "chars": 214,
    "preview": ".DS_Store\nelm-stuff/\nnode_modules/\nelchemy.js\noutput.ex\nexample/elm.js\n.#*\n.elchemy*\n*.orig\nelixir-stuff/\nelchemy_ex/lib"
  },
  {
    "path": ".gitmodules",
    "chars": 97,
    "preview": "[submodule \"elchemy-core\"]\n\tpath = elchemy-core\n\turl = https://github.com/wende/elchemy-core.git\n"
  },
  {
    "path": ".npmrc",
    "chars": 23,
    "preview": "tag-version-prefix = \"\""
  },
  {
    "path": ".tool-versions",
    "chars": 24,
    "preview": "elm 0.18.0\nelixir 1.7.4\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1761,
    "preview": "language: elixir\nelixir:\n  - 1.5.0\n  - 1.6.4\n  - 1.7.0\n  - 1.7.1\n  - 1.7.3\notp_release:\n  - 20.0\n  - 21.0\nmatrix:\n  excl"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3222,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "ISSUE_TEMPLATE",
    "chars": 291,
    "preview": "### Check first\nIf anything doesn't work, try\n```\nnpm install -g elchemy\nelchemy clean\nelchemy init\nmix test\n```\n\nTo exp"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2017 Krzysztof Wende\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "Main.elm",
    "chars": 885,
    "preview": "port module Main exposing (main)\n\nimport Html exposing (..)\nimport Html\nimport Elchemy.Compiler as Compiler\nimport Markd"
  },
  {
    "path": "Makefile",
    "chars": 2630,
    "preview": "dev:\n\tpkill -f http-server &\n\techo \"Make sure to install http-server with npm i -g http-server\"\n\telm-make Main.elm --out"
  },
  {
    "path": "README.md",
    "chars": 9868,
    "preview": "\n<p align=\"center\">\n<img src=\"https://github.com/wende/elchemy/blob/master/logo.png?raw=true\" width=\"250\" height=\"250\">\n"
  },
  {
    "path": "book.json",
    "chars": 29,
    "preview": "{\n    \"root\" : \"./roadmap\"\n}\n"
  },
  {
    "path": "bump.sh",
    "chars": 1865,
    "preview": " #!/bin/bash\n\n set -e\n\n if [ -z \"$1\" ]; then\n    echo 'usage ./bump.sh [<newversion> | major | minor | patch | premajor "
  },
  {
    "path": "elchemy",
    "chars": 5962,
    "preview": "#!/bin/bash\n\n\nversion=\"0.8.8\"\n\nif ! $(elm -v 2> /dev/null | grep 0.18 > /dev/null ); then\n    echo \"Elchemy requires elm"
  },
  {
    "path": "elchemy_ex/.gitignore",
    "chars": 527,
    "preview": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up "
  },
  {
    "path": "elchemy_ex/README.md",
    "chars": 511,
    "preview": "# ElchemyEx\n\n**TODO: Add description**\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package"
  },
  {
    "path": "elchemy_ex/config/config.exs",
    "chars": 1129,
    "preview": "# This file is responsible for configuring your application\n# and its dependencies with the aid of the Mix.Config module"
  },
  {
    "path": "elchemy_ex/elm/Hello.elm",
    "chars": 111,
    "preview": "module Hello exposing (..)\n\n\n{-| Prints \"world!\"\n\n    hello == \"world!\"\n-}\nhello : String\nhello =\n    \"world!\"\n"
  },
  {
    "path": "elchemy_ex/elm-package.json",
    "chars": 749,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"helpful summary of your project, less than 80 characters\",\n    \"repository\": \""
  },
  {
    "path": "elchemy_ex/mix.exs",
    "chars": 749,
    "preview": "defmodule ElchemyEx.Mixfile do\n  use Mix.Project\n\n  def project do\n    [\n      app: :elchemy_ex,\n      version: \"0.1.0\","
  },
  {
    "path": "elchemy_ex/test/elchemy_ex_test.exs",
    "chars": 142,
    "preview": "defmodule ElchemyExTest do\n  use ExUnit.Case\n\n  test \"Parsing works\" do\n    assert Ast.parse(\"module A exposing (..)\\na "
  },
  {
    "path": "elchemy_ex/test/test_helper.exs",
    "chars": 15,
    "preview": "ExUnit.start()\n"
  },
  {
    "path": "elchemy_node.js",
    "chars": 1430,
    "preview": "var path = require(\"path\")\nvar fs = require(\"fs\");\n\nvar SOURCE_DIR = process.argv[2]\nvar TARGET_DIR = process.argv[3]\nva"
  },
  {
    "path": "elm-package.json",
    "chars": 631,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"helpful summary of your project, less than 80 characters\",\n    \"repository\": \""
  },
  {
    "path": "elmchemy",
    "chars": 602,
    "preview": "#!/bin/bash\n\necho \"!!! DEPRECATION NOTICE !!!\"\necho \"elmchemy name is deprecated. Use elchemy (without an m) instead\"\nec"
  },
  {
    "path": "lib/mix/mix.exs",
    "chars": 1286,
    "preview": "defmodule Elchemy.Mixfile do\n  use Mix.Project\n\n  def project do\n    [app: :elchemy,\n     name: \"Elchemy Compiler\",\n    "
  },
  {
    "path": "lib/mix/tasks/compile.elchemy.ex",
    "chars": 1393,
    "preview": "defmodule Mix.Tasks.Compile.Elchemy do\n  use Mix.Task\n\n  def run(_args) do\n    project = Mix.Project.config\n    src = pr"
  },
  {
    "path": "mix.exs",
    "chars": 1048,
    "preview": "defmodule Elchemy.Mixfile do\n  use Mix.Project\n\n  def project do\n    [app: :elchemy,\n     name: \"Elchemy Compiler\",\n    "
  },
  {
    "path": "package.json",
    "chars": 880,
    "preview": "{\n  \"name\": \"elchemy\",\n  \"version\": \"0.8.8\",\n  \"description\": \"Write Elixir code using Elm-inspired syntax (elm-make com"
  },
  {
    "path": "roadmap/BASIC_TYPES.md",
    "chars": 874,
    "preview": "# Basic types\n\n\nBecause Elm and Elixir share a lot of common basic types, there is no need to redefine all of them for E"
  },
  {
    "path": "roadmap/COMMENTS.md",
    "chars": 1123,
    "preview": "# Comments\n\nIn Elchemy there is several types of comments.\n\n### Inline comments\n```elm\n-- My comment\n```\n\nWhich are alwa"
  },
  {
    "path": "roadmap/FLAGS.md",
    "chars": 1178,
    "preview": "# Flags\n\nElchemy compiler for purposes of experimenting and stretching the boundaries accepts flags.\n\n#### DO NOT ATTEMP"
  },
  {
    "path": "roadmap/FUNCTIONS.md",
    "chars": 1833,
    "preview": "# Function definition and currying\n\n### Function definition\nTo define a function that gets exported you **need** to decl"
  },
  {
    "path": "roadmap/INLINING.md",
    "chars": 208,
    "preview": "# Inlining Elixir Code\n\n\n#### DO NOT ATTEMPT TO DO THAT UNLESS YOU'RE SURE IT'S THE ONLY WAY\n\nIn Elchemy it's possible t"
  },
  {
    "path": "roadmap/INSTALLATION.md",
    "chars": 1648,
    "preview": "# Installation\n\nYou can install Elchemy using [npm](https://www.npmjs.com/) with\n```\nnpm install -g elchemy\n```\n\nTo inte"
  },
  {
    "path": "roadmap/INTEROP.md",
    "chars": 1735,
    "preview": "# Interop\n\n\n## Calling Elchemy\n\nTo call generated code from Elchemy you don't need anything special.  \nEach function exp"
  },
  {
    "path": "roadmap/MODULES.md",
    "chars": 915,
    "preview": "# Module definition and imports\n\nTo define a module in Elchemy you need to use a\n\n``` elm\nmodule ModuleName exposing (.."
  },
  {
    "path": "roadmap/README.md",
    "chars": 1828,
    "preview": "# Elchemy\n\nThis page lists all of the ideas and solutions behind Elchemy to share the\nideology as well as existing and i"
  },
  {
    "path": "roadmap/SIDE_EFFECTS.md",
    "chars": 164,
    "preview": "# Side Effects\n\nSide Effects are yet to come. You can read in detail about the plans for implementing them here [#297](h"
  },
  {
    "path": "roadmap/STRUCTURES.md",
    "chars": 1106,
    "preview": "# Structs\n\nElchemy represents all the structs as maps, so a struct defined like\n\n``` elm\nhuman : { name : String\n       "
  },
  {
    "path": "roadmap/SUMMARY.md",
    "chars": 679,
    "preview": "# Summary\n\n### Documentation\n* [Table Of Contents](./README.md)\n* [Installation](./INSTALLATION.md)\n* Basics\n    * [Synt"
  },
  {
    "path": "roadmap/SYNTAX.md",
    "chars": 9427,
    "preview": "Elchemy is a functional reactive programming language that compiles to (server-side) Elixir (Erlang VM).\nElchemy is stat"
  },
  {
    "path": "roadmap/TESTING.md",
    "chars": 166,
    "preview": "# Testing\n\nSo far there is no unit testing tool for Elchemy.  \nTo test your code use Elixir's `ExUnit` or see document ["
  },
  {
    "path": "roadmap/TROUBLESHOOTING.md",
    "chars": 670,
    "preview": "# Troubleshooting\n\nGenerally grand amount of problems can be solved with a simple command:\n```\nelchemy clean\n```\n\nIt cle"
  },
  {
    "path": "roadmap/TYPES.md",
    "chars": 1672,
    "preview": "# Union types\n\nElchemy (exactly like Elm) uses [Tagged union types](https://en.wikipedia.org/wiki/Tagged_union) \nWhat it"
  },
  {
    "path": "roadmap/TYPE_ALIASES.md",
    "chars": 731,
    "preview": "# Type Aliases\n\nIn Elchemy Type Aliases are completely virtual constructs that never make it out of the compiler.\nHoweve"
  },
  {
    "path": "src/Elchemy/Alias.elm",
    "chars": 5881,
    "preview": "module Elchemy.Alias exposing (registerAliases, replaceTypeAliases, resolveTypeBody)\n\nimport Ast.Statement exposing (Sta"
  },
  {
    "path": "src/Elchemy/Ast.elm",
    "chars": 5386,
    "preview": "module Elchemy.Ast exposing (foldExpression, walkExpressionInwards, walkExpressionOutwards, walkTypeInwards, walkTypeOut"
  },
  {
    "path": "src/Elchemy/Compiler.elm",
    "chars": 7624,
    "preview": "module Elchemy.Compiler exposing (version, tree)\n\n{-| Module responsible for compiling Elm code to Elixir\n\n@docs version"
  },
  {
    "path": "src/Elchemy/Context.elm",
    "chars": 14668,
    "preview": "module Elchemy.Context\n    exposing\n        ( Alias\n        , AliasType(..)\n        , Commons\n        , Context\n        "
  },
  {
    "path": "src/Elchemy/Expression.elm",
    "chars": 21047,
    "preview": "module Elchemy.Expression exposing (elixirE)\n\nimport Ast.Expression exposing (Expression(..))\nimport Elchemy.Context as "
  },
  {
    "path": "src/Elchemy/Ffi.elm",
    "chars": 8668,
    "preview": "module Elchemy.Ffi exposing (generateFfi)\n\nimport Ast.Expression exposing (Expression(..))\nimport Ast.Statement exposing"
  },
  {
    "path": "src/Elchemy/Function.elm",
    "chars": 7995,
    "preview": "module Elchemy.Function\n    exposing\n        ( functionCurry\n        , genFunctionDefinition\n        , genOverloadedFunc"
  },
  {
    "path": "src/Elchemy/Helpers.elm",
    "chars": 11112,
    "preview": "module Elchemy.Helpers\n    exposing\n        ( (=>)\n        , MaybeUpper(..)\n        , Operator(..)\n        , application"
  },
  {
    "path": "src/Elchemy/Meta.elm",
    "chars": 2848,
    "preview": "module Elchemy.Meta exposing (metaDefinition)\n\n{-| Defines a meta module for macro interactions\n-}\n\nimport Ast.Expressio"
  },
  {
    "path": "src/Elchemy/Operator.elm",
    "chars": 2409,
    "preview": "module Elchemy.Operator exposing (elixirBinop)\n\nimport Ast.Expression exposing (Expression(..))\nimport Elchemy.Context a"
  },
  {
    "path": "src/Elchemy/Selector.elm",
    "chars": 2912,
    "preview": "module Elchemy.Selector exposing (AccessMacro(..), AccessMacroType(..), Selector(..), maybeAccessMacro)\n\nimport Ast.Expr"
  },
  {
    "path": "src/Elchemy/Statement.elm",
    "chars": 20984,
    "preview": "module Elchemy.Statement exposing (elixirS, moduleStatement)\n\nimport Ast\nimport Ast.BinOp exposing (operators)\nimport As"
  },
  {
    "path": "src/Elchemy/Type.elm",
    "chars": 11006,
    "preview": "module Elchemy.Type exposing (elixirT, getExportedTypeNames, hasReturnedType, typeAliasConstructor, typespec, uniontype)"
  },
  {
    "path": "src/Elchemy/Variable.elm",
    "chars": 7385,
    "preview": "module Elchemy.Variable\n    exposing\n        ( extractName\n        , groupByCrossDependency\n        , organizeLetInVaria"
  },
  {
    "path": "templates/Hello.elm",
    "chars": 171,
    "preview": "module Hello exposing (..)\n\n{-| Example module, It says hello if you ask it nicely\n-}\n\n\n{-| Prints \"world!\"\n\n    hello ="
  },
  {
    "path": "templates/elchemy.exs",
    "chars": 2735,
    "preview": "defmodule ElchemyInit do\n\n  @deps_directory_depth 3\n\n  def init(project) do\n    if !project || !project[:deps] do\n      "
  },
  {
    "path": "templates/elchemy_test.exs",
    "chars": 140,
    "preview": "defmodule ElchemyTest do\n  use ExUnit.Case\n  use Elchemy\n  doctest Hello\n\n  test \"Hello\" do\n    assert Hello.hello() == "
  },
  {
    "path": "templates/elm-package.json",
    "chars": 430,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"helpful summary of your project, less than 80 characters\",\n    \"repository\": \""
  },
  {
    "path": "tests/.gitignore",
    "chars": 12,
    "preview": "/elm-stuff/\n"
  },
  {
    "path": "tests/Main.elm",
    "chars": 301,
    "preview": "port module Main exposing (..)\n\nimport Test\nimport Tests\nimport UnitTests\nimport Test.Runner.Node exposing (run, TestPro"
  },
  {
    "path": "tests/Tests.elm",
    "chars": 20877,
    "preview": "module Tests exposing (accessMacros, all, binOps, caseOfs, doctests, fileImports, functions, has, hasFull, letIns, lists"
  },
  {
    "path": "tests/UnitTests.elm",
    "chars": 208,
    "preview": "module UnitTests exposing (..)\n\nimport Test exposing (..)\nimport Expect\n\n\nall : Test\nall =\n    describe \"Test\"\n        ["
  },
  {
    "path": "tests/elm-package.json",
    "chars": 583,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"Sample Elm Test\",\n    \"repository\": \"https://github.com/user/project.git\",\n   "
  }
]

About this extraction

This page contains the full source code of the wende/elmchemy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (215.4 KB), approximately 53.4k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!