├── .gitignore
├── .travis.yml
├── README.md
├── benchmark
└── src
│ └── main
│ └── scala
│ └── atlas
│ ├── InterpreterBenchmark.scala
│ ├── JythonBenchmark.scala
│ ├── NashornBenchmark.scala
│ └── ScalaBenchmark.scala
├── build.sbt
├── core
└── src
│ ├── main
│ ├── boilerplate
│ │ └── atlas
│ │ │ ├── NativeConstructorBoilerplate.scala.template
│ │ │ ├── NativeDecoderBoilerplate.scala.template
│ │ │ └── Value.scala.template
│ └── scala
│ │ └── atlas
│ │ ├── BasicEnv.scala
│ │ ├── Env.scala
│ │ ├── Error.scala
│ │ ├── Expr.scala
│ │ ├── InfixImpl.scala
│ │ ├── InfixOp.scala
│ │ ├── Interpreter.scala
│ │ ├── Limits.scala
│ │ ├── Macros.scala
│ │ ├── Parser.scala
│ │ ├── PrefixImpl.scala
│ │ ├── PrefixOp.scala
│ │ ├── ScopeChain.scala
│ │ ├── Type.scala
│ │ ├── TypeChecker.scala
│ │ ├── TypeEnv.scala
│ │ ├── ValueDecoder.scala
│ │ ├── ValueEncoder.scala
│ │ ├── package.scala
│ │ └── syntax.scala
│ └── test
│ └── scala
│ └── atlas
│ ├── InterpreterExprSuite.scala
│ ├── InterpreterProgramSuite.scala
│ ├── InterpreterSuite.scala
│ ├── ParserSuite.scala
│ ├── TypeCheckerSuite.scala
│ └── TypeSuite.scala
└── project
├── build.properties
└── plugins.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/scala,sbt,sublimetext,intellij,emacs,vim
3 |
4 | ### Emacs ###
5 | # -*- mode: gitignore; -*-
6 | *~
7 | \#*\#
8 | /.emacs.desktop
9 | /.emacs.desktop.lock
10 | *.elc
11 | auto-save-list
12 | tramp
13 | .\#*
14 |
15 | # Org-mode
16 | .org-id-locations
17 | *_archive
18 |
19 | # flymake-mode
20 | *_flymake.*
21 |
22 | # eshell files
23 | /eshell/history
24 | /eshell/lastdir
25 |
26 | # elpa packages
27 | /elpa/
28 |
29 | # reftex files
30 | *.rel
31 |
32 | # AUCTeX auto folder
33 | /auto/
34 |
35 | # cask packages
36 | .cask/
37 | dist/
38 |
39 | # Flycheck
40 | flycheck_*.el
41 |
42 | # server auth directory
43 | /server/
44 |
45 | # projectiles files
46 | .projectile
47 | projectile-bookmarks.eld
48 |
49 | # directory configuration
50 | .dir-locals.el
51 |
52 | # saveplace
53 | places
54 |
55 | # url cache
56 | url/cache/
57 |
58 | # cedet
59 | ede-projects.el
60 |
61 | # smex
62 | smex-items
63 |
64 | # company-statistics
65 | company-statistics-cache.el
66 |
67 | # anaconda-mode
68 | anaconda-mode/
69 |
70 | ### Intellij ###
71 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
72 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
73 |
74 | # DJG: HACK
75 | .idea/**
76 |
77 | # User-specific stuff:
78 | .idea/**/workspace.xml
79 | .idea/**/tasks.xml
80 | .idea/dictionaries
81 |
82 | # Sensitive or high-churn files:
83 | .idea/**/dataSources/
84 | .idea/**/dataSources.ids
85 | .idea/**/dataSources.xml
86 | .idea/**/dataSources.local.xml
87 | .idea/**/sqlDataSources.xml
88 | .idea/**/dynamic.xml
89 | .idea/**/uiDesigner.xml
90 |
91 | # Gradle:
92 | .idea/**/gradle.xml
93 | .idea/**/libraries
94 |
95 | # CMake
96 | cmake-build-debug/
97 |
98 | # Mongo Explorer plugin:
99 | .idea/**/mongoSettings.xml
100 |
101 | ## File-based project format:
102 | *.iws
103 |
104 | ## Plugin-specific files:
105 |
106 | # IntelliJ
107 | /out/
108 |
109 | # mpeltonen/sbt-idea plugin
110 | .idea_modules/
111 |
112 | # JIRA plugin
113 | atlassian-ide-plugin.xml
114 |
115 | # Cursive Clojure plugin
116 | .idea/replstate.xml
117 |
118 | # Ruby plugin and RubyMine
119 | /.rakeTasks
120 |
121 | # Crashlytics plugin (for Android Studio and IntelliJ)
122 | com_crashlytics_export_strings.xml
123 | crashlytics.properties
124 | crashlytics-build.properties
125 | fabric.properties
126 |
127 | ### Intellij Patch ###
128 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
129 |
130 | # *.iml
131 | # modules.xml
132 | # .idea/misc.xml
133 | # *.ipr
134 |
135 | # Sonarlint plugin
136 | .idea/sonarlint
137 |
138 | ### SBT ###
139 | # Simple Build Tool
140 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
141 |
142 | dist/*
143 | target/
144 | lib_managed/
145 | src_managed/
146 | project/boot/
147 | project/plugins/project/
148 | .history
149 | .cache
150 | .lib/
151 |
152 | ### Scala ###
153 | *.class
154 | *.log
155 |
156 | ### SublimeText ###
157 | # cache files for sublime text
158 | *.tmlanguage.cache
159 | *.tmPreferences.cache
160 | *.stTheme.cache
161 |
162 | # workspace files are user-specific
163 | *.sublime-workspace
164 |
165 | # project files should be checked into the repository, unless a significant
166 | # proportion of contributors will probably not be using SublimeText
167 | # *.sublime-project
168 |
169 | # sftp configuration file
170 | sftp-config.json
171 |
172 | # Package control specific files
173 | Package Control.last-run
174 | Package Control.ca-list
175 | Package Control.ca-bundle
176 | Package Control.system-ca-bundle
177 | Package Control.cache/
178 | Package Control.ca-certs/
179 | Package Control.merged-ca-bundle
180 | Package Control.user-ca-bundle
181 | oscrypto-ca-bundle.crt
182 | bh_unicode_properties.cache
183 |
184 | # Sublime-github package stores a github token in this file
185 | # https://packagecontrol.io/packages/sublime-github
186 | GitHub.sublime-settings
187 |
188 | ### Vim ###
189 | # swap
190 | .sw[a-p]
191 | .*.sw[a-p]
192 | # session
193 | Session.vim
194 | # temporary
195 | .netrwhist
196 | # auto-generated tag files
197 | tags
198 |
199 |
200 | # End of https://www.gitignore.io/api/scala,sbt,sublimetext,intellij,emacs,vim
201 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | scala:
4 | - 2.12.4
5 |
6 | script:
7 | - sbt clean coverage test bench coverageReport
8 |
9 | after_success:
10 | - bash <(curl -s https://codecov.io/bash)
11 |
12 | cache:
13 | directories:
14 | - $HOME/.ivy2/cache
15 | - $HOME/.cache/.coursier
16 | - $HOME/.sbt
17 |
18 | before_cache:
19 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
20 | - find $HOME/.sbt -name "*.lock" -print -delete
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Atlas
2 |
3 | A tiny embedded scripting language implemented in Scala.
4 |
5 | Copyright 2018 Dave Gurnell. Licensed [Apache 2](https://www.apache.org/licenses/LICENSE-2.0).
6 |
7 | [](https://travis-ci.org/cartographerio/atlas)
8 | [](https://codecov.io/github/cartographerio/atlas)
9 | [](https://maven-badges.herokuapp.com/maven-central/io.cartographer/atlas_2.12)
10 |
11 | ## Why?
12 |
13 | Atlas is a super-simple scripting language
14 | created for use in [Cartographer](https://cartographer.io)
15 | to allow us to define fragments of logic over user-defined data types.
16 | It has the following features:
17 |
18 | - simple, concise, expression-oriented syntax;
19 | - Scheme-like functional semantics;
20 | - parser and interpreter written in Scala;
21 | - serializable in compiled and uncompiled forms;
22 | - support for "native functions" written in Scala.
23 |
24 | Atlas is currently a work-in-progress.
25 |
26 | ## Show me some examples!
27 |
28 | Factorials are the "hello world" of scripting languages, right?
29 |
30 | ```ruby
31 | let factorial = n ->
32 | if n <= 1
33 | then 1
34 | else n * factorial(n - 1)
35 |
36 | factorial(10)
37 | ```
38 |
39 | Function bodies are lazily bound
40 | allowing `letrec`-style recursive references:
41 |
42 | ```ruby
43 | let even = n ->
44 | if n == 0 then true else odd(n - 1)
45 |
46 | let odd = n ->
47 | if n == 0 then false else even(n - 1)
48 |
49 | even(10)
50 | ```
51 |
52 | ## Quick language reference
53 |
54 | Basic literals are Javascript-like:
55 |
56 | ```ruby
57 | 'foo' # single-quoted string
58 | "foo" # double-quoted string
59 | 1 # integer
60 | 1.2 # double
61 | true # boolean
62 | null # null
63 |
64 | [a, b] # array
65 | {a:1, b:2} # object
66 | ```
67 |
68 | There are a fixed set of built-in
69 | prefix and infix operators.
70 | In order of decreasing precedence these are:
71 |
72 | ```ruby
73 | !a # boolean not
74 | -a # negation
75 | +a # erm... non-negation
76 |
77 | a*b # multiplication
78 | a/b # floating point division
79 |
80 | a+b # addition
81 | a-b # addition
82 |
83 | a < b # comparisons
84 | a > b # (integer, double, string, or boolean)
85 | a <= b #
86 | a >= b #
87 |
88 | a == b # value equality and
89 | a != b # function reference equality
90 |
91 | a && b # boolean and
92 |
93 | a || b # boolean or
94 | ```
95 |
96 | Function literals are written with the `->` symbol.
97 | Parentheses are optional if there is only one argument:
98 |
99 | ```ruby
100 | (a, b) -> a + b
101 | n -> n + 1
102 | ```
103 |
104 | In addition to literals, variable references,
105 | and infix and prefix operators,
106 | there are several types of expression.
107 |
108 | Function applications look like Javascript:
109 |
110 | ```ruby
111 | max(1, 2)
112 | ```
113 |
114 | as do field references:
115 |
116 | ```ruby
117 | foo.bar.baz
118 | ```
119 |
120 | Conditionals are introduced with
121 | the `if`, `then`, and `else` keywords.
122 | The `else` clause is mandatory.
123 | The result is the value
124 | of the expression in the relevant arm:
125 |
126 | ```ruby
127 | if expr then expr else expr
128 | ```
129 |
130 | Blocks introduce scopes
131 | and allow the definition of intermediate variables.
132 | The result is the value of the final expression:
133 |
134 | ```ruby
135 | do
136 | stmt
137 | stmt
138 | expr
139 | end
140 | ```
141 |
142 | Statements are expressions (evaluated for their side-effects)
143 | or declarations, introduced with the `let` keyword:
144 |
145 | ```ruby
146 | let add = (a, b) -> a + b
147 | let ten = add(3, 7)
148 | ```
149 |
150 | Function bodies can refer to earlier or later bindings
151 | in the block where they are defined,
152 | allowing mutually recursive definitions:
153 |
154 | ```ruby
155 | let even = n ->
156 | if n == 0 then true else odd(n - 1)
157 |
158 | let odd = n ->
159 | if n == 0 then false else even(n - 1)
160 |
161 | even(10)
162 | ```
163 |
164 | Comments are written with the `#` symbol
165 | and run to the end of the line:
166 |
167 | ```ruby
168 | # Calculate a factorial:
169 | let fact = n ->
170 | if n == 1
171 | then 1
172 | else n * fact(n - 1)
173 | ```
174 |
175 | Complete programs have
176 | the same semantics as blocks
177 | but are written without the `do` and `end` keywords.
178 | If the program ends with a statement,
179 | an implicit `null` expression is added to the end:
180 |
181 | ```ruby
182 | let fib = n ->
183 | if n <= 2
184 | then 1
185 | else fib(n - 1) + fib(n - 2)
186 |
187 | fib(10)
188 | ```
189 |
190 | ## Interaction with Scala
191 |
192 | There are two string interpolators
193 | for defining code fragments:
194 | `expr` for expressions
195 | and `prog` for complete programs:
196 |
197 | ```scala
198 | import atlas._
199 | import atlas.syntax._
200 |
201 | val anExpression: Expr =
202 | expr"""
203 | 1 + 2 + 3
204 | """
205 |
206 | val aProgram: Expr =
207 | prog"""
208 | let fib = n ->
209 | if n <= 2
210 | then 1
211 | else fib(n - 1) + fib(n - 2)
212 |
213 | fib(10)
214 | """
215 | ```
216 |
217 | Syntax errors raised by the macros
218 | result in a Scala compilation error.
219 |
220 | You can alternatively use
221 | the `Parser.expr` or `Parser.prog` methods
222 | to parse a regular Scala string:
223 | Syntax errors using the parser result in an `Either`:
224 |
225 | ```scala
226 | val anotherExpression: Either[Parser.Error, Expr] =
227 | Parser.expr("1 + 2 + 3")
228 | ```
229 |
230 | Expressions (and, by extension, programs)
231 | can be evaluated using the `Eval.apply` method.
232 | Runtime errors are captured in an `Either`:
233 |
234 | ```scala
235 | Eval(anExpression) // => Right(IntValue(6))
236 |
237 | Eval(aProgram) // => Right(IntValue(55))
238 | ```
239 |
240 | You can optionally pass an `Env` object to `Eval.apply`
241 | specifying an initial environment:
242 |
243 | ```scala
244 | val program = prog"a + b"
245 | val env = Env.create
246 | .set("a", 10)
247 | .set("b", 32)
248 | Eval(program, env) // => Right(IntValue(42))
249 | ```
250 |
251 | Although `Eval` can internally mutate environments
252 | to enable mutually recursive function bodies,
253 | any environment you pass to `Eval.apply`
254 | should be returned unharmed.
255 |
256 | You can implement "native functions" in Scala:
257 |
258 | ```scala
259 | val program = prog"average(10, 5)"
260 | val env = Env.create
261 | .set("average", native((a: Double, b: Double) => (a + b) / 2))
262 | Eval(program, env) // => Right(DoubleValue(7.5))
263 | ```
264 |
265 | Conversion between Atlas and Scala values
266 | is implemented using a pair of type classes
267 | called `ValueEncoder` and `ValueDecoder`.
268 | These can be used with the `as[A]` and `asValue`
269 | extension methods from `atlas.syntax`:
270 |
271 | ```scala
272 | 123.asValue // => IntValue(123)
273 |
274 | IntValue.as[Int] // => Right(123)
275 | ```
276 |
277 | ## Acknowledgements
278 |
279 | Thanks to Nic Pereira for naming the project and saving us all from "davescript" :)
280 |
--------------------------------------------------------------------------------
/benchmark/src/main/scala/atlas/InterpreterBenchmark.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats._
5 | import cats.data._
6 | import cats.implicits._
7 | import java.util.concurrent.TimeUnit
8 | import org.openjdk.jmh.annotations.{Scope => JmhScope, _}
9 | import scala.concurrent.{Future, Await}
10 | import scala.concurrent.ExecutionContext.Implicits.global
11 | import scala.concurrent.duration._
12 |
13 | @BenchmarkMode(Array(Mode.AverageTime))
14 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
15 | class SyncBenchmark extends InterpreterBenchmark[EitherT[Eval, RuntimeError, ?]] {
16 | def monadError = MonadError[EitherT[Eval, RuntimeError, ?], RuntimeError]
17 |
18 | def unpack[A](either: EitherT[Eval, RuntimeError, A]): A =
19 | either.value.value.right.get
20 | }
21 |
22 | @BenchmarkMode(Array(Mode.AverageTime))
23 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
24 | class AsyncBenchmark extends InterpreterBenchmark[EitherT[Future, RuntimeError, ?]] {
25 | def monadError = MonadError[EitherT[Future, RuntimeError, ?], RuntimeError]
26 |
27 | def unpack[A](eitherT: EitherT[Future, RuntimeError, A]): A =
28 | Await.result(eitherT.value, 60.seconds).right.get
29 | }
30 |
31 | @BenchmarkMode(Array(Mode.AverageTime))
32 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
33 | class IdBenchmark extends InterpreterBenchmark[Either[RuntimeError, ?]] {
34 | def monadError = MonadError[Either[RuntimeError, ?], RuntimeError]
35 |
36 | def unpack[A](either: Either[RuntimeError, A]): A =
37 | either.right.get
38 | }
39 |
40 | abstract class InterpreterBenchmark[F[_]] {
41 | implicit def monadError: MonadError[F, RuntimeError]
42 |
43 | def unpack[A](value: F[A]): A
44 |
45 | def sum(n: Int): Int = {
46 | val program =
47 | prog"""
48 | let sum = n ->
49 | if n <= 1 then 1 else n + sum(n - 1)
50 |
51 | sum(n)
52 | """
53 |
54 | val env =
55 | Env.create[F].set("n", n)
56 |
57 | unpack(Interpreter.evalAs[F, Int](program, env))
58 | }
59 |
60 | def fib(n: Int): Int = {
61 | val program =
62 | prog"""
63 | let fib = n ->
64 | if n <= 1 then 1 else fib(n - 1) + fib(n - 2)
65 |
66 | fib(n)
67 | """
68 |
69 | val env =
70 | Env.create[F].set("n", n)
71 |
72 | unpack(Interpreter.evalAs[F, Int](program, env))
73 | }
74 |
75 |
76 | // @Benchmark def sum0010() = sum(10)
77 | // @Benchmark def sum0025() = sum(25)
78 | // @Benchmark def sum0050() = sum(50)
79 | @Benchmark def sum0100() = sum(100)
80 | // @Benchmark def sum0250() = sum(250)
81 | // @Benchmark def sum0500() = sum(500)
82 | // @Benchmark def sum1000() = sum(1000)
83 |
84 | // @Benchmark def fib005() = fib(5)
85 | // @Benchmark def fib010() = fib(10)
86 | @Benchmark def fib015() = fib(15)
87 | // @Benchmark def fib025() = fib(25)
88 | // @Benchmark def fib050() = fib(50)
89 | // @Benchmark def fib100() = fib(100)
90 | }
--------------------------------------------------------------------------------
/benchmark/src/main/scala/atlas/JythonBenchmark.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import java.io._
4 | import java.util.Properties
5 | import java.util.concurrent.TimeUnit
6 | import org.openjdk.jmh.annotations._
7 | import org.python.core._
8 | import org.python.util.PythonInterpreter
9 | import unindent._
10 |
11 | @BenchmarkMode(Array(Mode.AverageTime))
12 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
13 | class JythonBenchmark {
14 | def createInterpreter: PythonInterpreter = {
15 | val props = new Properties()
16 | props.setProperty("python.import.site", "false")
17 | val systemState = PySystemState.doInitialize(props, new Properties(), Array[String](), null, null)
18 |
19 | new PythonInterpreter(null, systemState)
20 | }
21 |
22 | def sum(n: Int): Unit = {
23 | val interpreter = createInterpreter
24 |
25 | val code =
26 | i"""
27 | def sum(n):
28 | if n <= 1:
29 | return 1
30 | else:
31 | return n + sum(n - 1)
32 |
33 | ans = sum($n)
34 | """
35 |
36 | interpreter.exec(code)
37 | assert(interpreter.get("ans").isInstanceOf[PyInteger])
38 | }
39 |
40 | def fib(n: Int): Unit = {
41 | val interpreter = createInterpreter
42 |
43 | val code =
44 | i"""
45 | def fib(n):
46 | if n <= 1:
47 | return 1
48 | else:
49 | return fib(n - 1) + fib(n - 2)
50 |
51 | ans = fib($n)
52 | """
53 |
54 | // println(code)
55 | interpreter.exec(code)
56 | // println(interpreter.get("ans"))
57 | // assert(interpreter.get("ans").isInstanceOf[PyInteger])
58 | }
59 |
60 | // @Benchmark def sum0010() = sum(10)
61 | // @Benchmark def sum0025() = sum(25)
62 | // @Benchmark def sum0050() = sum(50)
63 | @Benchmark def sum0100() = sum(100)
64 | // @Benchmark def sum0250() = sum(250)
65 | // @Benchmark def sum0500() = sum(500)
66 | // @Benchmark def sum1000() = sum(1000)
67 |
68 | // @Benchmark def fib005() = fib(5)
69 | // @Benchmark def fib010() = fib(10)
70 | @Benchmark def fib015() = fib(15)
71 | // @Benchmark def fib025() = fib(25)
72 | }
73 |
--------------------------------------------------------------------------------
/benchmark/src/main/scala/atlas/NashornBenchmark.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import java.io._
4 | import java.util.Properties
5 | import java.util.concurrent.TimeUnit
6 | import javax.script.ScriptEngine
7 | import jdk.nashorn.api.scripting.NashornScriptEngineFactory
8 | import org.openjdk.jmh.annotations._
9 | import unindent._
10 |
11 | @BenchmarkMode(Array(Mode.AverageTime))
12 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
13 | class NashornBenchmark {
14 | def createInterpreter: ScriptEngine = {
15 | val factory = new NashornScriptEngineFactory()
16 | Option(factory.getScriptEngine("--no-java")).get
17 | }
18 |
19 | def sum(n: Int): Any = {
20 | val interpreter = createInterpreter
21 | val result = interpreter.eval {
22 | i"""
23 | function fact(n) {
24 | return n <= 1
25 | ? 1
26 | : n + fact(n - 1);
27 | }
28 |
29 | fact($n)
30 | """
31 | }
32 |
33 | assert(result.isInstanceOf[java.lang.Double])
34 | }
35 |
36 | def fib(n: Int): Any = {
37 | val interpreter = createInterpreter
38 | val result = interpreter.eval {
39 | i"""
40 | function fib(n) {
41 | return n <= 1
42 | ? 1
43 | : fib(n - 1) + fib(n - 2);
44 | }
45 |
46 | fib($n)
47 | """
48 | }
49 |
50 | assert(result.isInstanceOf[java.lang.Double])
51 | }
52 |
53 | // @Benchmark def sum0010() = sum(10)
54 | // @Benchmark def sum0025() = sum(25)
55 | // @Benchmark def sum0050() = sum(50)
56 | @Benchmark def sum0100() = sum(100)
57 | // @Benchmark def sum0250() = sum(250)
58 | // @Benchmark def sum0500() = sum(500)
59 | // @Benchmark def sum1000() = sum(1000)
60 |
61 | // @Benchmark def fib005() = fib(5)
62 | // @Benchmark def fib010() = fib(10)
63 | @Benchmark def fib015() = fib(15)
64 | // @Benchmark def fib025() = fib(25)
65 | // @Benchmark def fib050() = fib(50)
66 | // @Benchmark def fib100() = fib(100)
67 | }
68 |
--------------------------------------------------------------------------------
/benchmark/src/main/scala/atlas/ScalaBenchmark.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats.implicits._
5 | import java.util.concurrent.TimeUnit
6 | import org.openjdk.jmh.annotations._
7 |
8 | @BenchmarkMode(Array(Mode.AverageTime))
9 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
10 | class ScalaBenchmark {
11 | def sum(n: Int): Int =
12 | if(n <= 1) 1 else n + sum(n - 1)
13 |
14 | def fib(n: Int): Int =
15 | if(n <= 1) 1 else fib(n - 1) + fib(n - 2)
16 |
17 | // @Benchmark def sum0010() = sum(10)
18 | // @Benchmark def sum0025() = sum(25)
19 | // @Benchmark def sum0050() = sum(50)
20 | @Benchmark def sum0100() = sum(100)
21 | // @Benchmark def sum0250() = sum(250)
22 | // @Benchmark def sum0500() = sum(500)
23 | // @Benchmark def sum1000() = sum(1000)
24 |
25 | // @Benchmark def fib005() = fib(5)
26 | // @Benchmark def fib010() = fib(10)
27 | @Benchmark def fib015() = fib(15)
28 | // @Benchmark def fib025() = fib(25)
29 | // @Benchmark def fib050() = fib(50)
30 | // @Benchmark def fib100() = fib(100)
31 | }
32 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | enablePlugins(spray.boilerplate.BoilerplatePlugin)
2 |
3 | organization := "io.cartographer"
4 | version := "0.1.0"
5 | scalaVersion := "2.12.4"
6 |
7 | licenses += ("Apache-2.0", url("http://apache.org/licenses/LICENSE-2.0"))
8 |
9 | lazy val commonCompilerSettings =
10 | addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.4") ++
11 | Seq(scalacOptions ++= Seq(
12 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
13 | "-encoding", "utf-8", // Specify character encoding used by source files.
14 | "-explaintypes", // Explain type errors in more detail.
15 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
16 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
17 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
18 | "-language:higherKinds", // Allow higher-kinded types
19 | "-language:implicitConversions", // Allow definition of implicit functions called views
20 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
21 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
22 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
23 | "-Xfuture", // Turn on future language features.
24 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
25 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
26 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
27 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
28 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
29 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
30 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`.
31 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
32 | "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
33 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
34 | "-Xlint:option-implicit", // Option.apply used implicit view.
35 | "-Xlint:package-object-classes", // Class or object defined in package object.
36 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
37 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
38 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
39 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in env.
40 | "-Xlint:unsound-match", // Pattern match may not be typesafe.
41 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
42 | "-Ypartial-unification", // Enable partial unification in type constructor inference
43 | "-Ywarn-dead-code", // Warn when dead code is identified.
44 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
45 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures.
46 | "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`.
47 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
48 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
49 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
50 | // "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
51 | // "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
52 | // "-Ywarn-unused:locals", // Warn if a local definition is unused.
53 | // "-Ywarn-unused:params", // Warn if a value parameter is unused.
54 | // "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
55 | // "-Ywarn-unused:privates", // Warn if a private member is unused.
56 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
57 | ))
58 |
59 |
60 | lazy val publishSettings = Seq(
61 | publishTo := sonatypePublishTo.value,
62 | pomExtra in Global := {
63 | https://github.com/cartographerio/atlas
64 |
65 | scm:git:github.com/cartographerio/atlas
66 | scm:git:git@github.com:cartographerio/atlas
67 | github.com/cartographerio/atlas
68 |
69 |
70 |
71 | davegurnell
72 | Dave Gurnell
73 | http://davegurnell.com
74 | Cartographer
75 | http://cartographer.io
76 |
77 |
78 | }
79 | )
80 |
81 | lazy val noPublishSettings =
82 | Seq(
83 | publish := {},
84 | publishLocal := {},
85 | publishArtifact := false
86 | )
87 |
88 | lazy val core = project.in(file("core"))
89 | .enablePlugins(spray.boilerplate.BoilerplatePlugin)
90 | .settings(publishSettings)
91 | .settings(commonCompilerSettings)
92 | .settings(
93 | name := "atlas-core",
94 | libraryDependencies ++= Seq(
95 | "org.apache.commons" % "commons-lang3" % "3.2.1",
96 | "com.chuusai" %% "shapeless" % "2.3.3",
97 | "com.davegurnell" %% "unindent" % "1.1.0",
98 | "com.lihaoyi" %% "fastparse" % "1.0.0",
99 | "org.typelevel" %% "cats-core" % "1.0.0",
100 | "io.monix" %% "minitest" % "2.1.1" % Test
101 | ),
102 | testFrameworks += new TestFramework("minitest.runner.Framework"),
103 | )
104 |
105 | lazy val benchmark = project.in(file("benchmark"))
106 | .dependsOn(core)
107 | .enablePlugins(JmhPlugin)
108 | .settings(noPublishSettings)
109 | .settings(commonCompilerSettings)
110 | .settings(
111 | name := "atlas-benchmark",
112 | libraryDependencies ++= Seq(
113 | "com.davegurnell" %% "unindent" % "1.1.0",
114 | "org.python" % "jython" % "2.7.0"
115 | )
116 | )
117 |
118 | lazy val root = project.in(file("."))
119 | .aggregate(core, benchmark)
120 |
121 | addCommandAlias("bench", "benchmark/jmh:run -i 5 -wi 5 -f 1 -t 1")
122 |
--------------------------------------------------------------------------------
/core/src/main/boilerplate/atlas/NativeConstructorBoilerplate.scala.template:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | trait NativeConstructorBoilerplate {
4 | def apply[R: ValueEncoder](func: () => R): Native =
5 | new Native0[R] {
6 | def apply[F[_]]()(implicit interpreter: Interpreter[F], env: Env): F[R] = {
7 | import interpreter._
8 | catchNonFatal(func())
9 | }
10 | }
11 |
12 | [#def apply[[#A1: ValueDecoder#], R: ValueEncoder](func: ([#A1#]) => R): Native =
13 | new Native1[[#A1#], R] {
14 | def apply[F[_]]([#a1: A1#])(implicit interpreter: Interpreter[F], env: Env): F[R] = {
15 | import interpreter._
16 | catchNonFatal(func([#a1#]))
17 | }
18 | }#
19 |
20 | ]
21 | }
--------------------------------------------------------------------------------
/core/src/main/boilerplate/atlas/NativeDecoderBoilerplate.scala.template:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats._
5 | import cats.data._
6 | import cats.implicits._
7 |
8 | trait NativeDecoderBoilerplate {
9 | implicit def native0Decoder[R: ValueEncoder: ValueDecoder]: ValueDecoder[Native0[R]] =
10 | ValueDecoder.pure {
11 | case func: FuncVal =>
12 | Right {
13 | new Native0[R] {
14 | def apply[F[_]]()(implicit interpreter: Interpreter[F], env: Env): F[R] = {
15 | import interpreter._
16 | for {
17 | ans <- evalApp(func, Nil)
18 | res <- liftEither(ans.toScala[R])
19 | } yield res
20 | }
21 | }
22 | }
23 |
24 | case _ =>
25 | Left(RuntimeError("Could not decode value as a function (arity 0)"))
26 | }
27 |
28 | [#implicit def native1Decoder[[#A1: ValueEncoder: ValueDecoder#], R: ValueEncoder: ValueDecoder]: ValueDecoder[Native1[[#A1#], R]] =
29 | ValueDecoder.pure {
30 | case func: FuncVal =>
31 | Right {
32 | new Native1[[#A1#], R] {
33 | def apply[F[_]]([#a1: A1#])(implicit interpreter: Interpreter[F], env: Env): F[R] = {
34 | import interpreter._
35 | val args = List([#a1.toAtlas#])
36 | for {
37 | ans <- evalApp(func, args)
38 | res <- liftEither(ans.toScala[R])
39 | } yield res
40 | }
41 | }
42 | }
43 |
44 | case _ =>
45 | Left(RuntimeError("Could not decode value as a function (arity 1)"))
46 | }#
47 |
48 | ]
49 | }
--------------------------------------------------------------------------------
/core/src/main/boilerplate/atlas/Value.scala.template:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats._
5 | import cats.data._
6 | import cats.implicits._
7 |
8 | sealed abstract class Value
9 |
10 | final case class ObjVal(fields: List[(String, Value)]) extends Value
11 | final case class ArrVal(items: List[Value]) extends Value
12 | final case class StrVal(value: String) extends Value
13 | final case class IntVal(value: Int) extends Value
14 | final case class DblVal(value: Double) extends Value
15 | final case class BoolVal(value: Boolean) extends Value
16 | case object NullVal extends Value
17 |
18 | sealed abstract class FuncVal extends Value
19 |
20 | final case class Closure(func: FuncExpr, env: Env) extends FuncVal {
21 | override def toString: String = s"Closure($func, ${env.scopes.length})"
22 | }
23 |
24 | abstract class Native extends Value {
25 | def run[F[_]](args: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[Value]
26 |
27 | def orElse(that: Native): Native =
28 | OrElseNative(this, that)
29 | }
30 |
31 | object Native extends NativeConstructorBoilerplate
32 | with NativeDecoderBoilerplate
33 |
34 | final case class OrElseNative(a: Native, b: Native) extends Native {
35 | def run[F[_]](args: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[Value] = {
36 | import interpreter._
37 | a.run(args).recoverWith { case error => b.run(args) }
38 | }
39 | }
40 |
41 | abstract class Native0[R: ValueEncoder] extends Native {
42 | def apply[F[_]]()(implicit interpreter: Interpreter[F], env: Env): F[R]
43 |
44 | override def run[F[_]](args: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[Value] = {
45 | import interpreter._
46 | args match {
47 | case Nil =>
48 | apply().map(_.toAtlas)
49 |
50 | case _ =>
51 | fail("Could not execute native function (arity mismatch)")
52 | }
53 | }
54 | }
55 |
56 | [#abstract class Native1[[#A1: ValueDecoder#], R: ValueEncoder] extends Native {
57 | def apply[F[_]]([#a1: A1#, ])(implicit interpreter: Interpreter[F], env: Env): F[R]
58 |
59 | override def run[F[_]](args: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[Value] = {
60 | import interpreter._
61 | args match {
62 | case [#a1 #:: ]:: Nil =>
63 | for {
64 | [#a1 <- liftEither(a1.toScala[A1])#
65 | ]
66 | r <- apply([#a1#, ])
67 | } yield r.toAtlas
68 |
69 | case _ =>
70 | fail("Could not execute native function (arity mismatch)")
71 | }
72 | }
73 | }#
74 |
75 | ]
76 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/BasicEnv.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import cats.implicits._
4 |
5 | object BasicEnv {
6 | private val map: Value =
7 | new Native2[Native1[Value, Value], List[Value], List[Value]] {
8 | def apply[F[_]](func: Native1[Value, Value], list: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[List[Value]] = {
9 | import interpreter._
10 | list.traverse(func(_))
11 | }
12 | }
13 |
14 | private val flatMap: Value =
15 | new Native2[Native1[Value, List[Value]], List[Value], List[Value]] {
16 | def apply[F[_]](func: Native1[Value, List[Value]], list: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[List[Value]] = {
17 | import interpreter._
18 | list.flatTraverse(func(_))
19 | }
20 | }
21 |
22 | private val filter: Value =
23 | new Native2[Native1[Value, Boolean], List[Value], List[Value]] {
24 | def apply[F[_]](func: Native1[Value, Boolean], list: List[Value])(implicit interpreter: Interpreter[F], env: Env): F[List[Value]] = {
25 | import interpreter._
26 | list
27 | .traverse(value => func(value).map(test => if(test) Some(value) else None))
28 | .map(_.flatten)
29 | }
30 | }
31 |
32 | private val flatten: Value =
33 | new Native1[List[List[Value]], List[Value]] {
34 | def apply[F[_]](lists: List[List[Value]])(implicit interpreter: Interpreter[F], env: Env): F[List[Value]] = {
35 | import interpreter._
36 | lists.flatten.pure[F]
37 | }
38 | }
39 |
40 | def basicEnv: Env =
41 | Env.create
42 | .set("map", map)
43 | .set("flatMap", flatMap)
44 | .set("filter", filter)
45 | .set("flatten", flatten)
46 | }
47 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Env.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | object Env {
4 | def create[F[_]]: Env =
5 | ScopeChain.create
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Error.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | sealed abstract class Error extends Product with Serializable
4 |
5 | final case class ParseError(message: String) extends Error
6 |
7 | sealed abstract class TypeError
8 |
9 | object TypeError {
10 | final case class TypeNotFound(name: String) extends TypeError
11 | final case class VariableNotFound(name: String) extends TypeError
12 | final case class TypeMismatch(to: Type, from: Type) extends TypeError
13 |
14 | def typeNotFound(name: String): TypeError =
15 | TypeNotFound(name)
16 |
17 | def variableNotFound(name: String): TypeError =
18 | VariableNotFound(name)
19 |
20 | def typeMismatch(to: Type, from: Type): TypeError =
21 | TypeMismatch(to, from)
22 | }
23 |
24 | final case class RuntimeError(message: String, cause: Option[Throwable] = None) extends Error
25 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Expr.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | sealed abstract class Expr extends Product with Serializable
4 |
5 | final case class RefExpr(id: String) extends Expr
6 |
7 | final case class AppExpr(func: Expr, args: List[Expr]) extends Expr
8 | final case class InfixExpr(op: InfixOp, arg1: Expr, arg2: Expr) extends Expr
9 | final case class PrefixExpr(op: PrefixOp, arg: Expr) extends Expr
10 |
11 | final case class FuncExpr(args: List[FuncArg], retType: Option[Type], body: Expr) extends Expr
12 | final case class FuncArg(argName: String, argType: Option[Type])
13 |
14 | final case class BlockExpr(stmts: List[Stmt], expr: Expr) extends Expr
15 | final case class SelectExpr(expr: Expr, field: String) extends Expr
16 | final case class CondExpr(test: Expr, trueArm: Expr, falseArm: Expr) extends Expr
17 | final case class CastExpr(expr: Expr, asType: Type) extends Expr
18 | final case class ParenExpr(expr: Expr) extends Expr
19 |
20 | final case class ObjExpr(fields: List[(String, Expr)]) extends Expr
21 | final case class ArrExpr(exprs: List[Expr]) extends Expr
22 | final case class StrExpr(value: String) extends Expr
23 | final case class IntExpr(value: Int) extends Expr
24 | final case class DblExpr(value: Double) extends Expr
25 | final case class BoolExpr(value: Boolean) extends Expr
26 | case object NullExpr extends Expr
27 |
28 | sealed abstract class Stmt extends Product with Serializable
29 | final case class LetStmt(varName: String, varType: Option[Type], expr: Expr) extends Stmt
30 | final case class LetTypeStmt(typeName: String, asType: Type) extends Stmt
31 | final case class ExprStmt(expr: Expr) extends Stmt
32 |
33 | sealed abstract class TypeExpr extends Product with Serializable
34 | final case class RefTypeExpr(name: String) extends TypeExpr
35 | final case class FuncTypeExpr(argTypes: List[TypeExpr], resType: Type) extends TypeExpr
36 | final case class UnionTypeExpr(types: List[TypeExpr]) extends TypeExpr
37 | final case class ObjTypeExpr(fields: List[(String, TypeExpr)]) extends TypeExpr
38 | final case class ArrTypeExpr(items: List[TypeExpr]) extends TypeExpr
39 | case object StrTypeExpr extends TypeExpr
40 | case object IntTypeExpr extends TypeExpr
41 | case object DblTypeExpr extends TypeExpr
42 | case object BoolTypeExpr extends TypeExpr
43 | case object NullTypeExpr extends TypeExpr
44 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/InfixImpl.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | trait InfixImpl {
4 | private val add: Native =
5 | Native((a: Int, b: Int) => a + b) orElse
6 | Native((a: Double, b: Double) => a + b) orElse
7 | Native((a: String, b: String) => a + b)
8 |
9 | private val sub: Native =
10 | Native((a: Int, b: Int) => a - b) orElse
11 | Native((a: Double, b: Double) => a - b)
12 |
13 | private val mul: Native =
14 | Native((a: Int, b: Int) => a * b) orElse
15 | Native((a: Double, b: Double) => a * b)
16 |
17 | private val div: Native =
18 | Native((a: Int, b: Int) => 1.0 * a / b) orElse
19 | Native((a: Double, b: Double) => a / b)
20 |
21 | private val and: Native =
22 | Native((a: Boolean, b: Boolean) => a && b)
23 |
24 | private val or: Native =
25 | Native((a: Boolean, b: Boolean) => a || b)
26 |
27 | private val eq: Native =
28 | Native((a: Value, b: Value) => a == b)
29 |
30 | private val ne: Native =
31 | Native((a: Value, b: Value) => a != b)
32 |
33 | private val gt: Native =
34 | Native((a: Int, b: Int) => a > b) orElse
35 | Native((a: Double, b: Double) => a > b) orElse
36 | Native((a: String, b: String) => a > b) orElse
37 | Native((a: Boolean, b: Boolean) => a > b)
38 |
39 | private val lt: Native =
40 | Native((a: Int, b: Int) => a < b) orElse
41 | Native((a: Double, b: Double) => a < b) orElse
42 | Native((a: String, b: String) => a < b) orElse
43 | Native((a: Boolean, b: Boolean) => a < b)
44 |
45 | private val gte: Native =
46 | Native((a: Int, b: Int) => a >= b) orElse
47 | Native((a: Double, b: Double) => a >= b) orElse
48 | Native((a: String, b: String) => a >= b) orElse
49 | Native((a: Boolean, b: Boolean) => a >= b)
50 |
51 | private val lte: Native =
52 | Native((a: Int, b: Int) => a <= b) orElse
53 | Native((a: Double, b: Double) => a <= b) orElse
54 | Native((a: String, b: String) => a <= b) orElse
55 | Native((a: Boolean, b: Boolean) => a <= b)
56 |
57 | private val pos: Native =
58 | Native((a: Int) => a) orElse
59 | Native((a: Double) => a)
60 |
61 | private val neg: Native =
62 | Native((a: Int) => -a) orElse
63 | Native((a: Double) => -a)
64 |
65 | private val not: Native =
66 | Native((a: Boolean) => !a)
67 |
68 | def infixImpl(op: InfixOp): Native =
69 | op match {
70 | case InfixOp.Add => add
71 | case InfixOp.Sub => sub
72 | case InfixOp.Mul => mul
73 | case InfixOp.Div => div
74 | case InfixOp.And => and
75 | case InfixOp.Or => or
76 | case InfixOp.Eq => eq
77 | case InfixOp.Ne => ne
78 | case InfixOp.Gt => gt
79 | case InfixOp.Lt => lt
80 | case InfixOp.Gte => gte
81 | case InfixOp.Lte => lte
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/InfixOp.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | sealed abstract class InfixOp(val id: String) extends Product with Serializable
4 |
5 | object InfixOp {
6 | case object Add extends InfixOp("+")
7 | case object Sub extends InfixOp("-")
8 | case object Mul extends InfixOp("*")
9 | case object Div extends InfixOp("/")
10 | case object And extends InfixOp("&&")
11 | case object Or extends InfixOp("||")
12 | case object Eq extends InfixOp("==")
13 | case object Ne extends InfixOp("!=")
14 | case object Gt extends InfixOp(">")
15 | case object Lt extends InfixOp("<")
16 | case object Gte extends InfixOp(">=")
17 | case object Lte extends InfixOp("<=")
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Interpreter.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats._
5 | import cats.data._
6 | import cats.implicits._
7 | import scala.concurrent.{ExecutionContext, Future}
8 | import scala.util.control.NonFatal
9 |
10 | object Interpreter {
11 | implicit def apply[F[_]](implicit monadError: MonadError[F, RuntimeError]): Interpreter[F] =
12 | new Interpreter[F]
13 |
14 | def evalAs[F[_], A](expr: Expr, env: Env = Env.create)(implicit interpreter: Interpreter[F], dec: ValueDecoder[A]): F[A] = {
15 | import interpreter._
16 | evalExpr(expr)(env.push).flatMap(value => liftEither(value.toScala))
17 | }
18 |
19 | def eval[F[_]](expr: Expr, env: Env = Env.create)(implicit interpreter: Interpreter[F]): F[Value] = {
20 | import interpreter._
21 | evalExpr(expr)(env.push)
22 | }
23 |
24 | val sync: Interpreter[EitherT[Eval, RuntimeError, ?]] =
25 | new Interpreter
26 |
27 | def async(implicit ec: ExecutionContext): Interpreter[EitherT[Future, RuntimeError, ?]] =
28 | new Interpreter
29 | }
30 |
31 | class Interpreter[F[_]](implicit val monadError: MonadError[F, RuntimeError]) extends InfixImpl with PrefixImpl {
32 | def evalExpr(expr: Expr)(implicit env: Env): F[Value] =
33 | expr match {
34 | case expr: RefExpr => evalRef(expr)
35 | case expr: AppExpr => evalApp(expr)
36 | case expr: InfixExpr => evalInfix(expr)
37 | case expr: PrefixExpr => evalPrefix(expr)
38 | case expr: FuncExpr => evalFunc(expr)
39 | case expr: BlockExpr => evalBlock(expr)
40 | case expr: SelectExpr => evalSelect(expr)
41 | case expr: CondExpr => evalCond(expr)
42 | case CastExpr(expr, _) => evalExpr(expr)
43 | case ParenExpr(expr) => evalExpr(expr)
44 | case expr: ObjExpr => evalObj(expr)
45 | case expr: ArrExpr => evalArr(expr)
46 | case StrExpr(value) => pure(StrVal(value))
47 | case IntExpr(value) => pure(IntVal(value))
48 | case DblExpr(value) => pure(DblVal(value))
49 | case BoolExpr(value) => pure(BoolVal(value))
50 | case NullExpr => pure(NullVal)
51 | }
52 |
53 | def evalRef(ref: RefExpr)(implicit env: Env): F[Value] =
54 | getVariable(ref.id)
55 |
56 | def evalApp(apply: AppExpr)(implicit env: Env): F[Value] =
57 | for {
58 | func <- evalExpr(apply.func)
59 | args <- apply.args.traverse(evalExpr)
60 | ans <- evalApp(func, args)
61 | } yield ans
62 |
63 | def evalApp(func: Value, args: List[Value])(implicit env: Env): F[Value] =
64 | func match {
65 | case closure : Closure => applyClosure(closure, args)
66 | case native : Native => applyNative(native, args)
67 | case value => fail(s"Cannot call $value")
68 | }
69 |
70 | def evalInfix(infix: InfixExpr)(implicit env: Env): F[Value] =
71 | for {
72 | arg1 <- evalExpr(infix.arg1)
73 | arg2 <- evalExpr(infix.arg2)
74 | ans <- applyNative(infixImpl(infix.op), List(arg1, arg2))
75 | } yield ans
76 |
77 | def evalPrefix(prefix: PrefixExpr)(implicit env: Env): F[Value] =
78 | for {
79 | arg <- evalExpr(prefix.arg)
80 | ans <- applyNative(prefixImpl(prefix.op), List(arg))
81 | } yield ans
82 |
83 | def evalFunc(func: FuncExpr)(implicit env: Env): F[Value] =
84 | pure(Closure(func, env))
85 |
86 | def evalBlock(block: BlockExpr)(implicit env0: Env): F[Value] = {
87 | val env1 = env0.push
88 | for {
89 | _ <- evalStmts(block.stmts)(env1)
90 | ans <- evalExpr(block.expr)(env1)
91 | } yield ans
92 | }
93 |
94 | def evalStmts(stmts: List[Stmt])(implicit env: Env): F[Unit] =
95 | stmts.foldLeft(pure(()))((a, b) => a.flatMap(_ => evalStmt(b)))
96 |
97 | def evalStmt(stmt: Stmt)(implicit env: Env): F[Unit] =
98 | stmt match {
99 | case stmt: ExprStmt => evalExprStmt(stmt)
100 | case stmt: LetStmt => evalLetStmt(stmt)
101 | case stmt: LetTypeStmt => pure(())
102 | }
103 |
104 | def evalLetStmt(stmt: LetStmt)(implicit env: Env): F[Unit] =
105 | for {
106 | value <- evalExpr(stmt.expr)
107 | _ <- setVariable(stmt.varName, value)
108 | } yield ()
109 |
110 | def evalExprStmt(stmt: ExprStmt)(implicit env: Env): F[Unit] =
111 | evalExpr(stmt.expr).map(_ => ())
112 |
113 | def evalSelect(select: SelectExpr)(implicit env: Env): F[Value] =
114 | for {
115 | value <- evalExpr(select)
116 | result <- evalSelect(value, select.field)
117 | } yield result
118 |
119 | def evalSelect(value: Value, id: String)(implicit env: Env): F[Value] =
120 | value match {
121 | case ObjVal(fields) =>
122 | fields.collectFirst { case (n, v) if n == id => v } match {
123 | case Some(value) => pure(value)
124 | case None => fail(s"Field not found: $id")
125 | }
126 |
127 | case other =>
128 | fail(s"Could not select field '$id' from $other")
129 | }
130 |
131 | def evalCond(cond: CondExpr)(implicit env: Env): F[Value] =
132 | for {
133 | test <- evalExpr(cond.test)
134 | result <- test match {
135 | case BoolVal(true) => evalExpr(cond.trueArm)
136 | case BoolVal(false) => evalExpr(cond.falseArm)
137 | case _ => fail(s"Conditional test was not a Boolean")
138 | }
139 | } yield result
140 |
141 | def evalObj(obj: ObjExpr)(implicit env: Env): F[Value] =
142 | obj.fields.traverse { case (n, e) => evalExpr(e).map(v => (n, v)) }.map(ObjVal)
143 |
144 | def evalArr(arr: ArrExpr)(implicit env: Env): F[Value] =
145 | arr.exprs.traverse(evalExpr).map(ArrVal)
146 |
147 | def applyClosure(closure: Closure, args: List[Value])(implicit env: Env): F[Value] = {
148 | val env1 = closure.env.push
149 | for {
150 | _ <- setVariables(closure.func.args.map(_.argName).zip(args))(env1)
151 | ans <- evalExpr(closure.func.body)(env1)
152 | } yield ans
153 | }
154 |
155 | def applyNative(native: Native, args: List[Value])(implicit env: Env): F[Value] =
156 | native.run(args)(this, env)
157 |
158 | // Environment helpers ------------------------
159 |
160 | def getVariable(name: String)(implicit env: Env): F[Value] =
161 | env.get(name) match {
162 | case Some(value) => pure(value)
163 | case None => fail(s"Not in scope: $name")
164 | }
165 |
166 | def setVariable(name: String, value: Value)(implicit env: Env): F[Unit] =
167 | pure(env.destructiveSet(name, value))
168 |
169 | def setVariables(bindings: Seq[(String, Value)])(implicit env: Env): F[Unit] =
170 | pure(env.destructiveSetAll(bindings))
171 |
172 | // Error handling helpers ---------------------
173 |
174 | def pure[A](value: A): F[A] =
175 | value.pure[F]
176 |
177 | def fail[A](error: RuntimeError): F[A] =
178 | error.raiseError[F, A]
179 |
180 | def fail[A](msg: String, cause: Option[Throwable] = None): F[A] =
181 | fail(RuntimeError(msg, cause))
182 |
183 | def liftEither[A](either: Either[RuntimeError, A]): F[A] =
184 | either.fold(_.raiseError[F, A], _.pure[F])
185 |
186 | def catchNonFatal[A](body: => A): F[A] =
187 | try {
188 | pure(body)
189 | } catch { case NonFatal(exn) =>
190 | fail("Error executing native code", Some(exn))
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Limits.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | case class Limits(
4 | startTime: Long,
5 | pc: Int = 0,
6 | pcLimit: Option[Int] = None,
7 | runtimeLimit: Option[Long] = None
8 | )
9 |
10 | object Limits {
11 | def create: Limits =
12 | Limits(System.currentTimeMillis)
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Macros.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import fastparse.all._
4 |
5 | import scala.reflect.macros.blackbox
6 |
7 | class Macros(val c: blackbox.Context) {
8 | import c.universe.{Expr => _, Type => _, TypeRef => _, _}
9 |
10 | def exprMacro(args: Tree *): Tree =
11 | macroImpl[Expr](Parser.parsers.exprToEnd, args)
12 |
13 | def progMacro(args: Tree *): Tree =
14 | macroImpl[Expr](Parser.parsers.progToEnd, args)
15 |
16 | private def macroImpl[A](parser: Parser[A], args: Seq[Tree])(implicit lift: Liftable[A]): Tree = {
17 | if(args.nonEmpty) {
18 | c.abort(c.enclosingPosition, "String interpolation not supported!")
19 | } else {
20 | c.prefix.tree match {
21 | case q"$_($_(..$partTrees))" =>
22 | parser.parse(formatCode(partTrees)) match {
23 | case Parsed.Success(value, _) =>
24 | lift(value)
25 |
26 | case failure: Parsed.Failure =>
27 | c.abort(c.enclosingPosition, s"Error parsing atlas code: $failure\n${failure.extra.traced}")
28 | }
29 | }
30 | }
31 | }
32 |
33 | // Adapted from UnindentMacros.transform(...) in davegurnell/unindent:
34 | private def formatCode(partTrees: Seq[Tree]): String = {
35 | import scala.util.matching.Regex.Match
36 |
37 | val prefixRegex = """^\n""".r
38 | val suffixRegex = """\n[ \t]*$""".r
39 | val indentRegex = """\n[ \t]+""".r
40 |
41 | val parts = partTrees.map { case Literal(Constant(part: String)) => part }
42 |
43 | val numParts = parts.length
44 |
45 | val minIndent = parts
46 | .flatMap(indentRegex.findAllIn)
47 | .map(_.length)
48 | .foldLeft(Int.MaxValue)(math.min)
49 |
50 | parts.zipWithIndex.map {
51 | case (part, index) =>
52 | // De-indent newlines:
53 | var ans = indentRegex.replaceAllIn(part, (m: Match) =>
54 | "\n" + (" " * (m.group(0).length - minIndent)))
55 |
56 | // Strip any initial newline from the beginning of the string:
57 | if(index == 0) {
58 | ans = prefixRegex.replaceFirstIn(ans, "")
59 | }
60 |
61 | // Strip any final newline from the end of the string:
62 | if(index == numParts - 1) {
63 | ans = suffixRegex.replaceFirstIn(ans, "")
64 | }
65 |
66 | ans
67 | }.mkString
68 | }
69 |
70 | val pkg = q"_root_.atlas"
71 |
72 | implicit lazy val exprLiftable: Liftable[Expr] =
73 | new Liftable[Expr] {
74 | def apply(expr: Expr): Tree =
75 | expr match {
76 | case RefExpr(id) => q"$pkg.RefExpr($id)"
77 | case AppExpr(func, args) => q"$pkg.AppExpr($func, $args)"
78 | case InfixExpr(op, arg1, arg2) => q"$pkg.InfixExpr($op, $arg1, $arg2)"
79 | case PrefixExpr(op, arg) => q"$pkg.PrefixExpr($op, $arg)"
80 | case FuncExpr(args, rType, body) => q"$pkg.FuncExpr($args, $rType, $body)"
81 | case BlockExpr(stmts, expr) => q"$pkg.BlockExpr($stmts, $expr)"
82 | case SelectExpr(expr, ref) => q"$pkg.SelectExpr($expr, $ref)"
83 | case CondExpr(test, arm1, arm2) => q"$pkg.CondExpr($test, $arm1, $arm2)"
84 | case CastExpr(expr, tpe) => q"$pkg.CastExpr($expr, $tpe)"
85 | case ParenExpr(expr) => q"$pkg.ParenExpr($expr)"
86 | case ObjExpr(fields) => q"$pkg.ObjExpr($fields)"
87 | case ArrExpr(items) => q"$pkg.ArrExpr($items)"
88 | case StrExpr(value) => q"$pkg.StrExpr($value)"
89 | case IntExpr(value) => q"$pkg.IntExpr($value)"
90 | case DblExpr(value) => q"$pkg.DblExpr($value)"
91 | case BoolExpr(value) => q"$pkg.BoolExpr($value)"
92 | case NullExpr => q"$pkg.NullExpr"
93 | }
94 | }
95 |
96 | implicit lazy val stmtLiftable: Liftable[Stmt] =
97 | new Liftable[Stmt] {
98 | def apply(stmt: Stmt): Tree =
99 | stmt match {
100 | case ExprStmt(expr) => q"$pkg.ExprStmt($expr)"
101 | case LetStmt(name, tpe, expr) => q"$pkg.LetStmt($name, $tpe, $expr)"
102 | case LetTypeStmt(name, tpe) => q"$pkg.LetTypeStmt($name, $tpe)"
103 | }
104 | }
105 |
106 | implicit lazy val argLiftable: Liftable[FuncArg] =
107 | new Liftable[FuncArg] {
108 | def apply(arg: FuncArg): Tree =
109 | arg match {
110 | case FuncArg(name, tpe) => q"$pkg.FuncArg($name, $tpe)"
111 | }
112 | }
113 |
114 | implicit lazy val prefixOpLiftable: Liftable[PrefixOp] =
115 | new Liftable[PrefixOp] {
116 | def apply(op: PrefixOp): Tree =
117 | op match {
118 | case PrefixOp.Not => q"$pkg.PrefixOp.Not"
119 | case PrefixOp.Pos => q"$pkg.PrefixOp.Pos"
120 | case PrefixOp.Neg => q"$pkg.PrefixOp.Neg"
121 | }
122 | }
123 |
124 | implicit lazy val infixOpLiftable: Liftable[InfixOp] =
125 | new Liftable[InfixOp] {
126 | def apply(op: InfixOp): Tree =
127 | op match {
128 | case InfixOp.Add => q"$pkg.InfixOp.Add"
129 | case InfixOp.Sub => q"$pkg.InfixOp.Sub"
130 | case InfixOp.Mul => q"$pkg.InfixOp.Mul"
131 | case InfixOp.Div => q"$pkg.InfixOp.Div"
132 | case InfixOp.And => q"$pkg.InfixOp.And"
133 | case InfixOp.Or => q"$pkg.InfixOp.Or"
134 | case InfixOp.Eq => q"$pkg.InfixOp.Eq"
135 | case InfixOp.Ne => q"$pkg.InfixOp.Ne"
136 | case InfixOp.Gt => q"$pkg.InfixOp.Gt"
137 | case InfixOp.Lt => q"$pkg.InfixOp.Lt"
138 | case InfixOp.Gte => q"$pkg.InfixOp.Gte"
139 | case InfixOp.Lte => q"$pkg.InfixOp.Lte"
140 | }
141 | }
142 |
143 | implicit lazy val typeLiftable: Liftable[Type] =
144 | new Liftable[Type] {
145 | def apply(tpe: Type): Tree =
146 | tpe match {
147 | // case TypeVar(id) => q"$pkg.TypeVar($id)"
148 | case TypeRef(name) => q"$pkg.TypeRef($name)"
149 | case FuncType(aTypes, rType) => q"$pkg.FuncType($aTypes, $rType)"
150 | case UnionType(types) => q"$pkg.UnionType($types)"
151 | case ObjType(fTypes) => q"$pkg.ObjType($fTypes)"
152 | case ArrType(iType) => q"$pkg.ArrType($iType)"
153 | case StrType => q"$pkg.StrType"
154 | case IntType => q"$pkg.IntType"
155 | case DblType => q"$pkg.DblType"
156 | case BoolType => q"$pkg.BoolType"
157 | case NullType => q"$pkg.NullType"
158 | }
159 | }
160 |
161 | implicit def listLiftable[A: Liftable]: Liftable[List[A]] =
162 | new Liftable[List[A]] {
163 | def apply(list: List[A]): Tree =
164 | q"_root_.scala.List(..$list)"
165 | }
166 |
167 | implicit def setLiftable[A: Liftable]: Liftable[Set[A]] =
168 | new Liftable[Set[A]] {
169 | def apply(list: Set[A]): Tree =
170 | q"_root_.scala.collection.immutable.Set(..$list)"
171 | }
172 |
173 | implicit def pairLiftable[A: Liftable]: Liftable[(String, A)] =
174 | new Liftable[(String, A)] {
175 | def apply(field: (String, A)): Tree = {
176 | val (name, expr) = field
177 | q"($name, $expr)"
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Parser.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import fastparse.all._
4 | import fastparse.parsers.Combinators.Rule
5 |
6 | object Parser {
7 | object parsers extends AllParsers
8 |
9 | case class Error(failure: Parsed.Failure)
10 |
11 | def expr(code: String): Either[Error, Expr] =
12 | parse(parsers.exprToEnd, code)
13 |
14 | def prog(code: String): Either[Error, Expr] =
15 | parse(parsers.progToEnd, code)
16 |
17 | private def parse[A](parser: Parser[A], code: String): Either[Error, A] = {
18 | parser.parse(code) match {
19 | case failure: Parsed.Failure =>
20 | Left(Error(failure))
21 |
22 | case Parsed.Success(value, _) =>
23 | Right(value)
24 | }
25 | }
26 | }
27 |
28 | trait AllParsers {
29 | val alpha: Parser[Unit] =
30 | P(CharIn("_$", 'a' to 'z', 'A' to 'Z'))
31 |
32 | val digit: Parser[Unit] =
33 | P(CharIn('0' to '9'))
34 |
35 | val hexDigit: Parser[Unit] =
36 | P(CharIn('0' to '9', 'a' to 'f', 'A' to 'F'))
37 |
38 | val identStart: Parser[Unit] =
39 | P(alpha)
40 |
41 | val identCont: Parser[Unit] =
42 | P(identStart | digit)
43 |
44 | val newline: Parser[Unit] =
45 | P("\n" | "\r\n" | "\r" | "\f" | ";")
46 |
47 | val whitespace: Parser[Unit] =
48 | P(" " | "\t")
49 |
50 | val lineComment: Parser[Unit] =
51 | P("#" ~ CharsWhile(_ != '\n', min = 0) ~ &(newline | End))
52 |
53 | val comment: Parser[Unit] =
54 | P(lineComment)
55 |
56 | val escape: Parser[Unit] =
57 | P("\\" ~ ((!(newline | hexDigit) ~ AnyChar) | (hexDigit.rep(min = 1, max = 6) ~ whitespace.?)))
58 |
59 | val nl: Parser[Unit] =
60 | P(whitespace.rep ~ comment.? ~ newline ~ (whitespace | newline).rep)
61 |
62 | val ws: Parser[Unit] =
63 | P((whitespace | comment | newline).rep)
64 |
65 | val letKw: Parser[Unit] =
66 | P("let" ~ !identCont)
67 |
68 | val letTypeKw: Parser[Unit] =
69 | P("type" ~ !identCont)
70 |
71 | val doKw: Parser[Unit] =
72 | P("do" ~ !identCont)
73 |
74 | val endKw: Parser[Unit] =
75 | P("end" ~ !identCont)
76 |
77 | val ifKw: Parser[Unit] =
78 | P("if" ~ !identCont)
79 |
80 | val thenKw: Parser[Unit] =
81 | P("then" ~ !identCont)
82 |
83 | val elseKw: Parser[Unit] =
84 | P("else" ~ !identCont)
85 |
86 | val falseKw: Parser[Unit] =
87 | P("false" ~ !identCont)
88 |
89 | val trueKw: Parser[Unit] =
90 | P("true" ~ !identCont)
91 |
92 | val nullKw: Parser[Unit] =
93 | P("null" ~ !identCont)
94 |
95 | val keyword: Parser[Unit] =
96 | P(letKw | letTypeKw | doKw | endKw | ifKw | thenKw | elseKw | falseKw | trueKw | nullKw)
97 |
98 | val ident: Parser[String] =
99 | P(!keyword ~ identStart ~ identCont.rep).!
100 |
101 | val intTypeKw: Parser[Unit] =
102 | P("Int" ~ !identCont)
103 |
104 | val dblTypeKw: Parser[Unit] =
105 | P("Real" ~ !identCont)
106 |
107 | val strTypeKw: Parser[Unit] =
108 | P("String" ~ !identCont)
109 |
110 | val boolTypeKw: Parser[Unit] =
111 | P("Boolean" ~ !identCont)
112 |
113 | val nullTypeKw: Parser[Unit] =
114 | P("Null" ~ !identCont)
115 |
116 | val typeKw: Parser[Unit] =
117 | P(intTypeKw | dblTypeKw | strTypeKw | boolTypeKw | nullTypeKw)
118 |
119 | // In increasing order of precedence:
120 | val infixOps: List[Parser[InfixOp]] = {
121 | import InfixOp._
122 | def op(id: String, op: InfixOp): Parser[InfixOp] =
123 | Rule(id, () => id).map(_ => op)
124 | List(
125 | op("||", Or),
126 | op("&&", And),
127 | op("==", Eq) | op("!=", Ne),
128 | op("<=", Lte) | op(">=", Gte) | op("<", Lt) | op(">", Gt),
129 | op("+", Add) | op("-", Sub),
130 | op("*", Mul) | op("/", Div)
131 | )
132 | }
133 |
134 | val prefixOp: Parser[PrefixOp] = {
135 | import PrefixOp._
136 | def op(id: String, op: PrefixOp): Parser[PrefixOp] =
137 | Rule(id, () => id).map(_ => op)
138 | op("+", Pos) | op("-", Neg) | op("!", Not)
139 | }
140 |
141 | val booleanToken: Parser[String] =
142 | P((trueKw | falseKw).!)
143 |
144 | val intToken: Parser[String] =
145 | P((CharIn("+-").? ~ digit.rep(1)).!)
146 |
147 | val doubleToken: Parser[String] = {
148 | val sign = P(CharIn("+-"))
149 | val wholeOnly = P(digit.rep(1))
150 | val wholeAndFraction = P(digit.rep(1) ~ "." ~/ digit.rep(0))
151 | val fractionOnly = P("." ~/ digit.rep(1))
152 | val exponent = P(CharIn("eE") ~/ sign.? ~ digit.rep(1))
153 | P((sign.? ~ (fractionOnly ~ exponent.? | wholeAndFraction ~ exponent.? | wholeOnly ~ exponent)).!)
154 | }
155 |
156 | val stringToken: Parser[String] = {
157 | val newline = P("\n" | "\r\n" | "\r" | "\f")
158 | val escape =
159 | P("\\" ~ ((!(newline | hexDigit) ~ AnyChar) | (hexDigit.rep(min = 1, max = 6) ~ whitespace.?)))
160 | def unescaped(quote: String) =
161 | P((!(quote | "\\" | newline) ~ AnyChar) | escape | ("\\" ~ newline))
162 | def unescape(value: String) =
163 | org.apache.commons.lang3.StringEscapeUtils.unescapeJava(value)
164 | def quoted(quote: String) =
165 | P(quote ~ (escape | unescaped(quote)).rep.!.map(unescape) ~ quote)
166 | val doubleQuoted = quoted("\'")
167 | val singleQuoted = quoted("\"")
168 | P(doubleQuoted | singleQuoted)
169 | }
170 |
171 | def ref(p: Parser[Unit]): Parser[RefExpr] =
172 | P(p.!.map(RefExpr))
173 |
174 | // -----
175 |
176 | def toEnd[A](parser: Parser[A]): Parser[A] =
177 | P(ws ~ parser ~ ws ~ End)
178 |
179 | val exprToEnd: Parser[Expr] =
180 | P(toEnd(expr))
181 |
182 | val progToEnd: Parser[Expr] =
183 | P(toEnd(prog))
184 |
185 | // -----
186 |
187 | val prog: Parser[Expr] =
188 | P(stmt.rep(min = 1, sep = nl)).flatMap(validateBlock)
189 |
190 | // -----
191 |
192 | val tpe: Parser[Type] =
193 | P(funcType)
194 |
195 | // -----
196 |
197 | val parenFuncTypeHit: Parser[Type] =
198 | P("(" ~ ws ~ tpe.rep(sep = ws ~ "," ~ ws) ~ ws ~ ")" ~ ws ~ "->" ~ ws ~ tpe)
199 | .map { case (argTypes, resType) => FuncType(argTypes.toList, resType) }
200 |
201 | val noParenFuncTypeHit: Parser[Type] =
202 | P(atomicType ~ ws ~ "->" ~ ws ~ tpe)
203 | .map { case (argType, resType) => FuncType(List(argType), resType) }
204 |
205 | val funcType: Parser[Type] =
206 | P(parenFuncTypeHit | noParenFuncTypeHit | unionType)
207 |
208 | // -----
209 |
210 | val unionType: Parser[Type] =
211 | P(nullableType ~ (ws ~ "|" ~ ws ~ tpe).rep).map {
212 | case (head, tail) =>
213 | tail.foldLeft(head)(Type.union)
214 | }
215 |
216 | // -----
217 |
218 | val nullableType: Parser[Type] =
219 | P(parenType ~ (ws ~ "?".!).rep).map {
220 | case (tpe, Nil) => tpe
221 | case (tpe, qns) => tpe.?
222 | }
223 |
224 | // -----
225 |
226 | val parenTypeHit: Parser[Type] =
227 | P("(" ~ ws ~ tpe ~ ws ~ ")")
228 |
229 | val parenTypeMiss: Parser[Type] =
230 | P(atomicType)
231 |
232 | val parenType: Parser[Type] =
233 | P(parenTypeHit | parenTypeMiss)
234 |
235 | // -----
236 |
237 | val intType: Parser[Type] =
238 | P(intTypeKw).map(_ => IntType)
239 |
240 | val dblType: Parser[Type] =
241 | P(dblTypeKw).map(_ => DblType)
242 |
243 | val strType: Parser[Type] =
244 | P(strTypeKw).map(_ => StrType)
245 |
246 | val boolType: Parser[Type] =
247 | P(boolTypeKw).map(_ => BoolType)
248 |
249 | val nullType: Parser[Type] =
250 | P(nullTypeKw).map(_ => NullType)
251 |
252 | val typeRef: Parser[Type] =
253 | P(ident).map(TypeRef)
254 |
255 | val atomicType: Parser[Type] =
256 | P(intType | dblType | strType | boolType | nullType | typeRef)
257 |
258 | // -----
259 |
260 | val stmt: Parser[Stmt] =
261 | P(letStmt | letTypeStmt | exprStmt)
262 |
263 | // -----
264 |
265 | val letStmt: Parser[Stmt] =
266 | P(letKw ~ ws ~/ ident ~ ws ~ (":" ~ ws ~ tpe ~ ws).? ~ "=" ~ ws ~/ expr)
267 | .map { case (name, tpe, expr) => LetStmt(name, tpe, expr) }
268 |
269 | val letTypeStmt: Parser[Stmt] =
270 | P(letTypeKw ~ ws ~/ ident ~ ws ~ "=" ~ ws ~/ tpe)
271 | .map { case (name, tpe) => LetTypeStmt(name, tpe) }
272 |
273 | val exprStmt: Parser[Stmt] =
274 | P(expr).map(ExprStmt)
275 |
276 | // -----
277 |
278 | val expr: Parser[Expr] =
279 | P(block)
280 |
281 | // -----
282 |
283 | def validateBlock(stmts: Seq[Stmt]): Parser[Expr] =
284 | (stmts.init, stmts.last) match {
285 | case (stmts, last: ExprStmt) => Pass.map(_ => BlockExpr(stmts.toList, last.expr))
286 | case (stmts, last: LetStmt) => Fail
287 | case (stmts, last: LetTypeStmt) => Fail
288 | }
289 |
290 | val blockHit: Parser[Expr] =
291 | P(doKw ~ ws ~ stmt.rep(min = 1, sep = nl) ~ ws ~ endKw)
292 | .flatMap(validateBlock)
293 |
294 | val block: Parser[Expr] =
295 | P(blockHit | cond)
296 |
297 | // -----
298 |
299 | val condHit: Parser[Expr] =
300 | P(ifKw ~ ws ~ expr ~ ws ~ thenKw ~ ws ~ expr ~ ws ~ elseKw ~ ws ~ expr)
301 | .map { case (test, ifTrue, ifFalse) => CondExpr(test, ifTrue, ifFalse) }
302 |
303 | val cond: Parser[Expr] =
304 | P(condHit | infix)
305 |
306 | // -----
307 |
308 | def createInfix(op: Parser[InfixOp], subExpr: Parser[Expr]): Parser[Expr] =
309 | P(subExpr ~ (ws ~ op ~ ws ~ subExpr).rep).map {
310 | case (head, tail) =>
311 | tail.foldLeft(head) { (a, pair) =>
312 | val (op, b) = pair
313 | InfixExpr(op, a, b)
314 | }
315 | }
316 |
317 | val infix: Parser[Expr] =
318 | P(infixOps.foldRight(cast)(createInfix))
319 |
320 | // -----
321 |
322 | val cast: Parser[Expr] =
323 | P(prefix ~ (ws ~ ":" ~ ws ~ tpe).rep)
324 | .map { case (head, tail) => tail.foldLeft(head)(CastExpr) }
325 |
326 | // -----
327 |
328 | val prefixHit: Parser[Expr] =
329 | P(prefixOp ~ ws ~ prefix)
330 | .map { case (op, arg) => PrefixExpr(op, arg) }
331 |
332 | val prefix: Parser[Expr] =
333 | P(prefixHit | select)
334 |
335 | // -----
336 |
337 | val select: Parser[Expr] =
338 | P(apply ~ (ws ~ "." ~ ws ~ ident).rep)
339 | .map { case (head, tail) => tail.foldLeft(head)(SelectExpr) }
340 |
341 | // -----
342 |
343 | val applyHit: Parser[Expr] =
344 | P(ident ~ ws ~ "(" ~/ ws ~ expr.rep(sep = ws ~ "," ~ ws) ~ ws ~ ")")
345 | .map { case (name, args) => AppExpr(RefExpr(name), args.toList) }
346 |
347 | val apply: Parser[Expr] =
348 | P(applyHit | func)
349 |
350 | // -----
351 |
352 | val funcArg: Parser[FuncArg] =
353 | P(ident ~ (ws ~ ":" ~ ws ~ tpe).?)
354 | .map { case (name, tpe) => FuncArg(name, tpe) }
355 |
356 | val parenFuncHit: Parser[Expr] =
357 | P("(" ~ ws ~ funcArg.rep(sep = ws ~ "," ~ ws) ~ ws ~ ")" ~ ws ~ (":" ~ ws ~ parenType ~ ws).? ~ "->" ~ ws ~/ expr)
358 | .map { case (args, rType, expr) => FuncExpr(args.toList, rType, expr) }
359 |
360 | val noParenFuncHit: Parser[Expr] =
361 | P(ident ~ ws ~ "->" ~ ws ~/ expr)
362 | .map { case (name, expr) => FuncExpr(List(FuncArg(name, None)), None, expr) }
363 |
364 | val func: Parser[Expr] =
365 | P(parenFuncHit | noParenFuncHit | obj)
366 |
367 | // -----
368 |
369 | val field: Parser[(String, Expr)] =
370 | P((ident | stringToken) ~ ws ~/ ":" ~ ws ~/ expr)
371 |
372 | val objHit: Parser[Expr] =
373 | P("{" ~ ws ~/ field.rep(sep = ws ~ "," ~ ws.~/) ~ ws ~ "}".~/)
374 | .map(_.toList)
375 | .map(ObjExpr)
376 |
377 | val obj: Parser[Expr] =
378 | P(objHit | arr)
379 |
380 | // -----
381 |
382 | val arrHit: Parser[Expr] =
383 | P("[" ~ ws ~/ expr.rep(sep = ws ~ "," ~/ ws) ~ ws ~ "]".~/)
384 | .map(_.toList)
385 | .map(ArrExpr)
386 |
387 | val arr: Parser[Expr] =
388 | P(arrHit | paren)
389 |
390 | // -----
391 |
392 | val parenHit: Parser[Expr] =
393 | P("(" ~ ws ~ expr ~ ws ~ ")")
394 |
395 | val paren: Parser[Expr] =
396 | P(parenHit | atom)
397 |
398 | // -----
399 |
400 | val atom: Parser[Expr] =
401 | P(str | double | int | trueExpr | falseExpr | nullExpr | ref)
402 |
403 | val str: Parser[Expr] =
404 | P(stringToken).map(StrExpr)
405 |
406 | val double: Parser[Expr] =
407 | P(doubleToken).map(_.toDouble).map(DblExpr)
408 |
409 | val int: Parser[Expr] =
410 | P(intToken).map(_.toInt).map(IntExpr)
411 |
412 | val trueExpr: Parser[Expr] =
413 | P(trueKw).map(_ => BoolExpr(true))
414 |
415 | val falseExpr: Parser[Expr] =
416 | P(falseKw).map(_ => BoolExpr(false))
417 |
418 | val nullExpr: Parser[Expr] =
419 | P(nullKw).map(_ => NullExpr)
420 |
421 | val ref: Parser[Expr] =
422 | P(ident.map(RefExpr))
423 | }
424 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/PrefixImpl.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | trait PrefixImpl {
4 | private val pos: Native =
5 | Native((a: Int) => a) orElse
6 | Native((a: Double) => a)
7 |
8 | private val neg: Native =
9 | Native((a: Int) => -a) orElse
10 | Native((a: Double) => -a)
11 |
12 | private val not: Native =
13 | Native((a: Boolean) => !a)
14 |
15 | def prefixImpl(op: PrefixOp): Native =
16 | op match {
17 | case PrefixOp.Pos => pos
18 | case PrefixOp.Neg => neg
19 | case PrefixOp.Not => not
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/PrefixOp.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | sealed abstract class PrefixOp(val id: String) extends Product with Serializable
4 |
5 | object PrefixOp {
6 | case object Not extends PrefixOp("!")
7 | case object Pos extends PrefixOp("+")
8 | case object Neg extends PrefixOp("-")
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/ScopeChain.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | final case class Scope[K, V](var bindings: Map[K, V]) {
4 | def get(id: K): Option[V] =
5 | bindings.collectFirst { case (`id`, value) => value }
6 |
7 | def set(id: K, value: V): Scope[K, V] =
8 | Scope(bindings + ((id, value)))
9 |
10 | def destructiveSet(id: K, value: V): Unit =
11 | bindings = bindings + ((id, value))
12 |
13 | def destructiveSetAll(bindings: Seq[(K, V)]): Unit =
14 | bindings.foreach { case (id, value) => destructiveSet(id, value) }
15 | }
16 |
17 | final case class ScopeChain[K, V](scopes: List[Scope[K, V]]) {
18 | def get(id: K): Option[V] = {
19 | def loop(scopes: List[Scope[K, V]]): Option[V] =
20 | scopes match {
21 | case head :: tail => head.get(id).orElse(loop(tail))
22 | case Nil => None
23 | }
24 | loop(scopes)
25 | }
26 |
27 | def set(id: K, value: V): ScopeChain[K, V] =
28 | ScopeChain(scopes.head.set(id, value) :: scopes.tail)
29 |
30 | def destructiveSet(id: K, value: V): Unit =
31 | scopes.head.destructiveSet(id, value)
32 |
33 | def destructiveSetAll(bindings: Seq[(K, V)]): Unit =
34 | scopes.head.destructiveSetAll(bindings)
35 |
36 | def push: ScopeChain[K, V] =
37 | ScopeChain(Scope.create[K, V] :: scopes)
38 |
39 | def pop: ScopeChain[K, V] =
40 | ScopeChain(scopes.tail)
41 | }
42 |
43 | object Scope {
44 | def create[K, V]: Scope[K, V] =
45 | Scope(Map.empty[K, V])
46 | }
47 |
48 | object ScopeChain {
49 | def create[K, V]: ScopeChain[K, V] =
50 | ScopeChain(List(Scope.create))
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/Type.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import cats.{Monoid, Semigroup, Show}
4 | import cats.implicits._
5 |
6 | sealed abstract class Type extends Product with Serializable {
7 | def |(that: Type): Type =
8 | Type.union(this, that)
9 |
10 | def ? : Type =
11 | Type.union(this, NullType)
12 | }
13 |
14 | // final case class TypeVar(id: Int) extends Type
15 | final case class TypeRef(name: String) extends Type
16 |
17 | final case class FuncType(argTypes: List[Type], returnType: Type) extends Type
18 | final case class UnionType(types: Set[Type]) extends Type
19 |
20 | final case class ObjType(fieldTypes: List[(String, Type)]) extends Type
21 | final case class ArrType(itemType: Type) extends Type
22 |
23 | case object StrType extends Type
24 | case object IntType extends Type
25 | case object DblType extends Type
26 | case object BoolType extends Type
27 | case object NullType extends Type
28 |
29 | object Type extends TypeFunctions with TypeInstances
30 |
31 | trait TypeFunctions {
32 | val emptyUnion: Type =
33 | UnionType(Set())
34 |
35 | def union(a: Type, b: Type): Type =
36 | (a, b) match {
37 | case ( a , b ) if a == b => a
38 | case ( a , Type.emptyUnion ) => a
39 | case ( Type.emptyUnion , b ) => b
40 | case ( UnionType(as) , UnionType(bs) ) => UnionType(as ++ bs)
41 | case ( UnionType(as) , b ) => UnionType(as + b)
42 | case ( a , UnionType(bs) ) => UnionType(bs + a)
43 | case ( a , b ) => UnionType(Set(a, b))
44 | }
45 |
46 | def unionAll(types: Seq[Type]): Type =
47 | types.foldLeft(UnionType(Set()) : Type)(Type.union)
48 |
49 | def isAssignable(to: Type, from: Type): Boolean =
50 | (to, from) match {
51 | case ( a , b ) if a == b => true
52 | case ( a , UnionType(bs) ) => bs.forall(isAssignable(a, _))
53 | case ( UnionType(as) , b ) => as.exists(isAssignable(_, b))
54 | case ( a , b ) => false
55 | }
56 | }
57 |
58 | trait TypeInstances {
59 | // implicit val typeVarOrdering: Ordering[TypeVar] =
60 | // Ordering.by[TypeVar, Int](_.id)
61 |
62 | implicit val typeOrdering: Ordering[Type] =
63 | Ordering.fromLessThan[Type] {
64 | // case (a: TypeVar, b: TypeVar) => a.id < b.id
65 | // case (a, b: TypeVar) => true
66 | // case (a: TypeVar, b) => false
67 | case (a, b) => a.toString < b.toString
68 | }
69 | }
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/TypeChecker.scala:
--------------------------------------------------------------------------------
1 | // package atlas
2 |
3 | // import cats.MonadError
4 | // import cats.data.StateT
5 | // import cats.instances.either._
6 | // import cats.instances.list._
7 | // import cats.syntax.all._
8 |
9 | // object TypeChecker {
10 | // type Out[A] = Either[TypeError, A]
11 |
12 | // type Step[A] = TypeStep[Out, A]
13 |
14 | // def check(expr: Expr, env: TypeEnv = TypeEnv.create): Out[Type] =
15 | // checkExpr(expr).runA((0, env))
16 |
17 | // def checkExpr(expr: Expr): Step[Type] =
18 | // expr match {
19 | // case expr: RefExpr => checkRef(expr)
20 | // case expr: AppExpr => checkApp(expr)
21 | // case expr: InfixExpr => checkInfix(expr)
22 | // case expr: PrefixExpr => checkPrefix(expr)
23 | // case expr: FuncExpr => checkFunc(expr)
24 | // case expr: BlockExpr => checkBlock(expr)
25 | // case expr: SelectExpr => checkSelect(expr)
26 | // case expr: CondExpr => checkCond(expr)
27 | // case expr: CastExpr => checkCast(expr)
28 | // case ParenExpr(expr) => checkExpr(expr)
29 | // case expr: ObjExpr => checkObj(expr)
30 | // case expr: ArrExpr => checkArr(expr)
31 | // case StrExpr(value) => pure(StrType)
32 | // case IntExpr(value) => pure(IntType)
33 | // case DblExpr(value) => pure(DblType)
34 | // case BoolExpr(value) => pure(BoolType)
35 | // case NullExpr => pure(NullType)
36 | // }
37 |
38 | // def checkRef(ref: RefExpr): Step[Type] =
39 | // getType(ref.id)
40 |
41 | // def checkApp(apply: AppExpr): Step[Type] =
42 | // ???
43 |
44 | // def checkInfix(infix: InfixExpr): Step[Type] =
45 | // ???
46 |
47 | // def checkPrefix(prefix: PrefixExpr): Step[Type] =
48 | // ???
49 |
50 | // def checkFunc(func: FuncExpr): Step[Type] =
51 | // pushScope {
52 | // def checkFuncArg(arg: FuncArg): Step[Unit] =
53 | // arg.argType match {
54 | // case Some(tpe) => setType(arg.argName, tpe)
55 | // case None => genType.flatMap(setType(arg.argName, _))
56 | // }
57 |
58 | // for {
59 | // args <- func.args.traverse(checkFuncArg)
60 | // expr <- checkExpr(func.body)
61 | // ans <- func.retType.fold(pure(expr))(assertAssignable(_, expr))
62 | // } yield ans
63 | // }
64 |
65 | // def checkBlock(block: BlockExpr): Step[Type] =
66 | // for {
67 | // _ <- checkStmts(block.stmts)
68 | // ans <- checkExpr(block.expr)
69 | // } yield ans
70 |
71 | // def checkStmts(stmts: List[Stmt]): Step[Unit] =
72 | // stmts.traverse(checkStmt).map(_ => ())
73 |
74 | // def checkStmt(stmt: Stmt): Step[Unit] =
75 | // stmt match {
76 | // case stmt: ExprStmt => checkExpr(stmt.expr).map(_ => ())
77 | // case stmt: LetStmt => checkLetStmt(stmt)
78 | // case stmt: LetTypeStmt => checkLetTypeStmt(stmt)
79 | // }
80 |
81 | // def checkExprStmt(stmt: ExprStmt): Step[Unit] =
82 | // checkExpr(stmt.expr).map(_ => ())
83 |
84 | // def checkLetStmt(stmt: LetStmt): Step[Unit] =
85 | // for {
86 | // expr <- checkExpr(stmt.expr)
87 | // tpe <- stmt.varType.fold(pure(expr))(assertAssignable(_, expr))
88 | // _ <- setType(stmt.varName, tpe)
89 | // } yield ()
90 |
91 | // def checkLetTypeStmt(stmt: LetTypeStmt): Step[Unit] =
92 | // aliasType(stmt.typeName, stmt.asType)
93 |
94 | // def checkSelect(select: SelectExpr): Step[Type] =
95 | // ???
96 |
97 | // def checkCond(cond: CondExpr): Step[Type] =
98 | // for {
99 | // test <- checkExpr(cond.test)
100 | // _ <- assertAssignable(BoolType, test)
101 | // trueArm <- checkExpr(cond.trueArm)
102 | // falseArm <- checkExpr(cond.falseArm)
103 | // } yield Type.union(trueArm, falseArm)
104 |
105 | // def checkCast(cast: CastExpr): Step[Type] =
106 | // for {
107 | // expr <- checkExpr(cast.expr)
108 | // tpe <- assertAssignable(cast.asType, expr)
109 | // } yield tpe
110 |
111 | // def checkObj(obj: ObjExpr): Step[Type] =
112 | // ???
113 |
114 | // def checkArr(arr: ArrExpr): Step[Type] =
115 | // arr.exprs.traverse(checkExpr).map(types => ArrType(Type.unionAll(types)))
116 |
117 | // // Resolving, unifying, and checking types ----
118 |
119 | // /** Check we can assign to type `a` with a value of type `b`. */
120 | // def assertAssignable(a: Type, b: Type): Step[Type] =
121 | // for {
122 | // a <- resolveType(a)
123 | // b <- resolveType(b)
124 | // ans <- if(Type.isAssignable(a, b)) {
125 | // pure(a)
126 | // } else {
127 | // fail(TypeError.typeMismatch(a, b))
128 | // }
129 | // } yield ans
130 |
131 | // /** Unify `a` and `b`, setting type variables as necessary. */
132 | // def unify(a: Type, b: Type): Step[Type] =
133 | // (a, b) match {
134 | // case (a: TypeRef, b: TypeRef) => resolveType(b).flatMap(unify(a, _))
135 | // case (a: TypeRef, b ) => aliasType(a.name, b).map(_ => b)
136 | // case (a , b: TypeRef) => aliasType(b.name, a).map(_ => a)
137 | // case (a , b ) => assertAssignable(a, b).map(_ => a)
138 | // }
139 |
140 | // def resolveType(tpe: Type): Step[Type] =
141 | // tpe match {
142 | // case ref: TypeRef =>
143 | // inspectEnv(env => env.get(s"type:${ref.name}").pure[Out])
144 | // .flatMap[Type, (Int, TypeEnv)] {
145 | // case Some(tpe) => resolveType(tpe)
146 | // case None => fail(TypeError.typeNotFound(ref.name))
147 | // }
148 |
149 | // case FuncType(args, res) =>
150 | // for {
151 | // args <- args.traverse(resolveType)
152 | // res <- resolveType(res)
153 | // } yield FuncType(args, res)
154 |
155 | // case UnionType(types) =>
156 | // for {
157 | // types <- types.toList.traverse(resolveType)
158 | // } yield UnionType(types.toSet)
159 |
160 | // case tpe: Type =>
161 | // pure(tpe)
162 | // }
163 |
164 | // def aliasType(typeName: String, tpe: Type): Step[Unit] =
165 | // inspectEnv(env => env.destructiveSet(s"type:$typeName", tpe).pure[Out])
166 |
167 | // def genType: Step[TypeRef] =
168 | // nextTypeId(id => TypeRef(s"!$id").pure[Out])
169 |
170 | // def getType(varName: String): Step[Type] =
171 | // inspectEnv(env => env.get(s"var:$varName").pure[Out])
172 | // .flatMap[Type, (Int, TypeEnv)] {
173 | // case Some(tpe) => pure(tpe)
174 | // case None => fail(TypeError.variableNotFound(varName))
175 | // }
176 | // .flatMap(resolveType)
177 |
178 | // def setType(varName: String, value: Type): Step[Unit] =
179 | // inspectEnv(env => env.destructiveSet(s"var:$varName", value).pure[Out])
180 |
181 | // // Environment primitives ---------------------
182 |
183 | // def pushScope[A](body: Step[A]): Step[A] =
184 | // for {
185 | // _ <- updateEnv(_.push.pure[Out])
186 | // ans <- body
187 | // _ <- updateEnv(_.pop.pure[Out])
188 | // } yield ans
189 |
190 | // def inspectEnv[A](func: TypeEnv => Out[A]): Step[A] =
191 | // StateT.inspectF { case (nextId, env) => func(env) }
192 |
193 | // def updateEnv(func: TypeEnv => Out[TypeEnv]): Step[Unit] =
194 | // StateT.modifyF { case (nextId, env) => func(env).map(env => (nextId, env)) }
195 |
196 | // def nextTypeId[A](func: Int => Out[A]): Step[A] =
197 | // StateT.apply { case (nextId, env) => func(nextId).map(a => ((nextId + 1, env), a)) }
198 |
199 | // // Success/failure primitives -----------------
200 |
201 | // def pure[A](value: A): Step[A] =
202 | // value.pure[Step]
203 |
204 | // def fail[A](error: TypeError): Step[A] =
205 | // error.raiseError[Step, A]
206 | // }
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/TypeEnv.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | object TypeEnv {
4 | def create: TypeEnv =
5 | ScopeChain.create
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/ValueDecoder.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import cats.implicits._
4 |
5 | trait ValueDecoder[A] {
6 | def apply(value: Value): Either[RuntimeError, A]
7 | }
8 |
9 | object ValueDecoder extends ValueDecoderFunctions
10 | with ValueDecoderInstances
11 |
12 | trait ValueDecoderFunctions {
13 | def apply[A](implicit instance: ValueDecoder[A]): ValueDecoder[A] =
14 | instance
15 |
16 | def pure[A](func: Value => Either[RuntimeError, A]): ValueDecoder[A] =
17 | new ValueDecoder[A] {
18 | def apply(arg: Value): Either[RuntimeError, A] =
19 | func(arg)
20 | }
21 | }
22 |
23 | trait ValueDecoderInstances {
24 | self: ValueDecoderFunctions =>
25 |
26 | implicit val value: ValueDecoder[Value] =
27 | pure(Right(_))
28 |
29 | implicit val unit: ValueDecoder[Unit] =
30 | pure {
31 | case NullVal => Right(())
32 | case value => Left(RuntimeError(s"Could not decode unit: $value"))
33 | }
34 |
35 | implicit val boolean: ValueDecoder[Boolean] =
36 | pure {
37 | case BoolVal(value) => Right(value)
38 | case value => Left(RuntimeError(s"Could not decode boolean: $value"))
39 | }
40 |
41 | implicit val int: ValueDecoder[Int] =
42 | pure {
43 | case IntVal(value) => Right(value)
44 | case value => Left(RuntimeError(s"Could not decode int: $value"))
45 | }
46 |
47 | implicit val double: ValueDecoder[Double] =
48 | pure {
49 | case IntVal(value) => Right(value * 1.0)
50 | case DblVal(value) => Right(value)
51 | case value => Left(RuntimeError(s"Could not decode double: $value"))
52 | }
53 |
54 | implicit val string: ValueDecoder[String] =
55 | pure {
56 | case StrVal(value) => Right(value)
57 | case value => Left(RuntimeError(s"Could not decode string: $value"))
58 | }
59 |
60 | implicit def list[A](implicit dec: ValueDecoder[A]): ValueDecoder[List[A]] =
61 | pure {
62 | case ArrVal(values) => values.traverse(dec.apply)
63 | case value => Left(RuntimeError(s"Could not decode list: $value"))
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/ValueEncoder.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | trait ValueEncoder[A] {
4 | def apply(value: A): Value
5 | }
6 |
7 | object ValueEncoder extends ValueEncoderFunctions
8 | with ValueEncoderInstances
9 |
10 | trait ValueEncoderFunctions {
11 | def apply[A](implicit enc: ValueEncoder[A]): ValueEncoder[A] =
12 | enc
13 |
14 | def pure[A](func: A => Value): ValueEncoder[A] =
15 | new ValueEncoder[A] {
16 | def apply(arg: A): Value =
17 | func(arg)
18 | }
19 | }
20 |
21 | trait ValueEncoderInstances {
22 | self: ValueEncoderFunctions =>
23 |
24 | implicit def value[A <: Value]: ValueEncoder[A] =
25 | pure(value => value)
26 |
27 | implicit val unit: ValueEncoder[Unit] =
28 | pure(value => NullVal)
29 |
30 | implicit val boolean: ValueEncoder[Boolean] =
31 | pure(value => BoolVal(value))
32 |
33 | implicit val int: ValueEncoder[Int] =
34 | pure(value => IntVal(value))
35 |
36 | implicit val double: ValueEncoder[Double] =
37 | pure(value => DblVal(value))
38 |
39 | implicit val string: ValueEncoder[String] =
40 | pure(value => StrVal(value))
41 |
42 | implicit def list[A](implicit enc: ValueEncoder[A]): ValueEncoder[List[A]] =
43 | pure(list => ArrVal(list.map(enc.apply)))
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/package.scala:
--------------------------------------------------------------------------------
1 | import cats.data.StateT
2 |
3 | package object atlas {
4 | type Env = ScopeChain[String, Value]
5 | type TypeEnv = ScopeChain[String, Type]
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/main/scala/atlas/syntax.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | object syntax {
4 | implicit class EnvOps[F[_]](val chain: ScopeChain[String, Value]) extends AnyVal {
5 | def set[A](id: String, value: A)(implicit enc: ValueEncoder[A]): Env =
6 | chain.set(id, enc(value))
7 |
8 | def destructiveSet[A](id: String, value: A)(implicit enc: ValueEncoder[A]): Unit =
9 | chain.destructiveSet(id, enc(value))
10 |
11 | def destructiveSetAll[A](bindings: Seq[(String, A)])(implicit enc: ValueEncoder[A]): Unit =
12 | chain.destructiveSetAll(bindings.map { case (n, a) => (n, enc(a)) })
13 | }
14 |
15 | implicit class ValueEncoderOps[A](val a: A) extends AnyVal {
16 | def toAtlas[F[_]](implicit enc: ValueEncoder[A]): Value =
17 | enc(a)
18 | }
19 |
20 | implicit class ValueDecoderOps[F[_]](val value: Value) extends AnyVal {
21 | def toScala[A](implicit dec: ValueDecoder[A]): Either[RuntimeError, A] =
22 | dec(value)
23 | }
24 |
25 | implicit class AtlasStringOps(val ctx: StringContext) extends AnyVal {
26 | def expr(args: Any *): Expr =
27 | macro Macros.exprMacro
28 |
29 | def prog(args: Any *): Expr =
30 | macro Macros.progMacro
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/InterpreterExprSuite.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import cats.{Eval, MonadError}
5 | import cats.data.EitherT
6 | import cats.implicits._
7 | import minitest._
8 |
9 | import scala.concurrent.ExecutionContext.Implicits.global
10 | import scala.concurrent.duration._
11 | import scala.concurrent.{Await, Future}
12 |
13 | object SyncInterpreterExprSuite extends InterpreterExprSuite[EitherT[Eval, RuntimeError, ?]] {
14 | def toEither[A](either: EitherT[Eval, RuntimeError, A]): Either[RuntimeError, A] =
15 | either.value.value
16 | }
17 |
18 | object AsyncInterpreterExprSuite extends InterpreterExprSuite[EitherT[Future, RuntimeError, ?]] {
19 | def toEither[A](eitherT: EitherT[Future, RuntimeError, A]): Either[RuntimeError, A] =
20 | Await.result(eitherT.value, 1.second)
21 | }
22 |
23 | abstract class InterpreterExprSuite[F[_]](implicit monad: MonadError[F, RuntimeError]) extends InterpreterSuite[F] {
24 | test("literal") {
25 | assertSuccess(
26 | expr"true",
27 | true
28 | )
29 | }
30 |
31 | test("infix") {
32 | assertSuccess(
33 | expr"1 + 2 + 3",
34 | 6
35 | )
36 | }
37 |
38 | test("variable reference") {
39 | assertSuccess(
40 | expr"foo",
41 | true,
42 | Env.create[F].set("foo", true),
43 | )
44 | }
45 |
46 | test("variable not in env") {
47 | assertFailure(
48 | expr"foo",
49 | RuntimeError("Not in scope: foo")
50 | )
51 | }
52 |
53 | test("function application") {
54 | val code = expr"""
55 | add(mul(a, b), mul(4, 5))
56 | """
57 |
58 | val env = Env.create[F]
59 | .set("add", Native((a: Int, b: Int) => a + b))
60 | .set("mul", Native((a: Int, b: Int) => a * b))
61 | .set("a", 2)
62 | .set("b", 3)
63 |
64 | val expected = 26
65 |
66 | assertSuccess(code, expected, env)
67 | }
68 |
69 | test("lexical scoping") {
70 | val code = expr"""
71 | do
72 | let a = 123
73 | let bound = () -> a
74 | do
75 | let a = 321
76 | bound()
77 | end
78 | end
79 | """
80 |
81 | val expected = 123
82 |
83 | assertSuccess(code, expected)
84 | }
85 |
86 | test("object literals") {
87 | import atlas.syntax._
88 |
89 | val code = expr"""
90 | {
91 | foo: 1 + 1,
92 | bar: 'a' + 'b',
93 | baz: [ 1 + 2, 3 + 4]
94 | }
95 | """
96 |
97 | val expected = ObjVal(List(
98 | "foo" -> 2.toAtlas[F],
99 | "bar" -> "ab".toAtlas[F],
100 | "baz" -> List(3, 7).toAtlas[F]
101 | ))
102 |
103 | assertSuccess(code, expected)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/InterpreterProgramSuite.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import cats.{Eval, MonadError}
4 | import cats.data.EitherT
5 | import cats.implicits._
6 | import minitest._
7 | import syntax._
8 |
9 | import scala.concurrent.ExecutionContext.Implicits.global
10 | import scala.concurrent.duration._
11 | import scala.concurrent.{Await, Future}
12 |
13 | object SyncInterpreterProgramSuite extends InterpreterProgramSuite[EitherT[Eval, RuntimeError, ?]] {
14 | def toEither[A](either: EitherT[Eval, RuntimeError, A]): Either[RuntimeError, A] =
15 | either.value.value
16 | }
17 |
18 | object AsyncInterpreterProgramSuite extends InterpreterProgramSuite[EitherT[Future, RuntimeError, ?]] {
19 | def toEither[A](eitherT: EitherT[Future, RuntimeError, A]): Either[RuntimeError, A] =
20 | Await.result(eitherT.value, 1.second)
21 | }
22 |
23 | abstract class InterpreterProgramSuite[F[_]](implicit monad: MonadError[F, RuntimeError]) extends InterpreterSuite[F] {
24 | test("recursive odd/even") {
25 | val code = prog"""
26 | let even = n -> if n == 0 then true else odd(n - 1)
27 | let odd = n -> if n == 0 then false else even(n - 1)
28 |
29 | even(10)
30 | """
31 | val expected = true
32 |
33 | assertSuccess(code, expected)
34 | }
35 |
36 | test("factorial") {
37 | val prog = prog"""
38 | let factorial = n ->
39 | if n <= 1
40 | then 1
41 | else n * factorial(n - 1)
42 |
43 | factorial(10)
44 | """
45 |
46 | val expected = (1 to 10).foldLeft(1)(_ * _)
47 |
48 | assertSuccess(prog, expected)
49 | }
50 |
51 | test("fib") {
52 | val prog = prog"""
53 | let fib = n ->
54 | if n <= 2
55 | then 1
56 | else fib(n - 1) + fib(n - 2)
57 |
58 | fib(10)
59 | """
60 |
61 | val expected = 55
62 |
63 | assertSuccess(prog, expected)
64 | }
65 |
66 | test("map, filter, flatten") {
67 | val prog = prog"""
68 | let inBounds = n ->
69 | n > 1 && n < 20
70 |
71 | let double = n ->
72 | [ n, n * 2 ]
73 |
74 | let values =
75 | [1, 5, 7]
76 |
77 | filter(inBounds, flatten(map(double, values)))
78 | """
79 |
80 | val env = BasicEnv.basicEnv
81 |
82 | val expected = List(2, 5, 10, 7, 14)
83 |
84 | assertSuccess(prog, expected, env)
85 | }
86 |
87 | test("comments") {
88 | val prog = prog"""
89 | # Comment
90 | let# Comment
91 | double# Comment
92 | =# Comment
93 | n# Comment
94 | -># Comment
95 | n# Comment
96 | *# Comment
97 | 2# Comment
98 | double# Comment
99 | (# Comment
100 | 21# Comment
101 | )# Comment
102 | # Comment
103 | """
104 |
105 | val expected = 42
106 |
107 | assertSuccess(prog, expected)
108 | }
109 |
110 | test("native functions") {
111 | val prog = prog"""average(10, 5)"""
112 |
113 | val env = Env.create
114 | .set("average", Native((a: Double, b: Double) => (a + b) / 2))
115 |
116 | val expected = 7.5
117 |
118 | assertSuccess(prog, expected, env)
119 | }
120 |
121 | test("native functions with exceptions") {
122 | val prog = prog"""
123 | average(10, 5)
124 | """
125 |
126 | val exn = new Exception("Badness")
127 |
128 | val env = Env.create
129 | .set("average", Native { (a: Double, b: Double) =>
130 | if(a > b) throw exn
131 | 0
132 | })
133 |
134 | val expected = RuntimeError("Error executing native code", Some(exn))
135 |
136 | assertFailure(prog, expected, env)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/InterpreterSuite.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import cats.MonadError
4 | import cats.implicits._
5 | import minitest._
6 |
7 | abstract class InterpreterSuite[F[_]](implicit monad: MonadError[F, RuntimeError]) extends SimpleTestSuite {
8 | def assertSuccess[A](expr: Expr, expected: A, env: Env = Env.create)(implicit enc: ValueEncoder[A]): Unit =
9 | assertEquals(toEither(Interpreter.eval[F](expr, env)), Right(enc(expected)))
10 |
11 | def assertFailure(expr: Expr, expected: RuntimeError, env: Env = Env.create): Unit =
12 | assertEquals(toEither(Interpreter.eval[F](expr, env)), Left(expected))
13 |
14 | def toEither[A](value: F[A]): Either[RuntimeError, A]
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/ParserSuite.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import fastparse.all._
4 | import minitest._
5 | import unindent._
6 |
7 | object UnitParserSuite extends SimpleTestSuite with AllParsers with ParserSuiteHelpers {
8 | test("digit") {
9 | object assert extends Assertions(digit)
10 |
11 | assert.complete("1", ())
12 | assert.failure("A", 0)
13 | }
14 |
15 | test("hexDigit") {
16 | object assert extends Assertions(hexDigit)
17 |
18 | assert.complete("1", ())
19 | assert.complete("A", ())
20 | assert.complete("f", ())
21 | assert.failure("G", 0)
22 | }
23 |
24 | test("newline") {
25 | object assert extends Assertions(newline)
26 |
27 | assert.complete("\r", ())
28 | assert.complete("\n", ())
29 | assert.complete("\r\n", ())
30 | assert.complete("\f", ())
31 | assert.partial("\n\r", (), 1)
32 | }
33 |
34 | test("whitespace") {
35 | object assert extends Assertions(whitespace)
36 |
37 | assert.complete(" ", ())
38 | assert.complete("\t", ())
39 | assert.failure("\r\n", 0)
40 | assert.failure("\n\r", 0)
41 | }
42 |
43 | test("ws") {
44 | object assert extends Assertions(ws)
45 |
46 | assert.complete(" \t \t ", ())
47 | assert.complete(" \n \n ", ())
48 | assert.complete(" \n \n \n ", ())
49 | }
50 |
51 | test("comment") {
52 | object assert extends Assertions(comment)
53 |
54 | assert.complete("# This is a comment", ())
55 | assert.partial("# This is a comment\n# So is this", (), 19)
56 | }
57 |
58 | test("escape") {
59 | object assert extends Assertions(escape)
60 |
61 | assert.complete("\\n", ())
62 | assert.complete("\\\\", ())
63 | assert.complete("\\\"", ())
64 | assert.failure("\\\n", 1)
65 | }
66 | }
67 |
68 | object TokenParserSuite extends SimpleTestSuite with AllParsers with ParserSuiteHelpers {
69 | test("boolean") {
70 | object assert extends Assertions(booleanToken)
71 |
72 | assert.complete("true", "true")
73 | assert.complete("false", "false")
74 | assert.partial("true false", "true", 4)
75 | assert.failure("truefalse", 0)
76 | assert.failure("maybe", 0)
77 | }
78 |
79 | test("intNumber") {
80 | object assert extends Assertions(intToken)
81 |
82 | assert.complete("123", "123")
83 | assert.partial("123.", "123", 3)
84 | assert.failure(".123", 0)
85 | assert.partial("123.456", "123", 3)
86 | assert.complete("+123", "+123")
87 | assert.failure("-.123", 1)
88 | assert.partial("123e456", "123", 3)
89 | assert.failure("-.123E-456", 1)
90 | assert.failure("letters", 0)
91 | }
92 |
93 | test("realNumber") {
94 | object assert extends Assertions(doubleToken)
95 |
96 | assert.failure("123", 0)
97 | assert.complete("123.", "123.")
98 | assert.complete(".123", ".123")
99 | assert.complete("123.456", "123.456")
100 | assert.failure("+123", 1)
101 | assert.complete("-.123", "-.123")
102 | assert.complete("123e456", "123e456")
103 | assert.complete("-.123E-456", "-.123E-456")
104 | assert.failure("letters", 0)
105 | }
106 |
107 | test("string") {
108 | object assert extends Assertions(stringToken)
109 |
110 | assert.complete("""'dave'""", "dave")
111 | assert.complete(""""has"""", "has")
112 | assert.complete(""""'escaped'"""", """'escaped'""")
113 | assert.complete("""'"escaped"'""", """"escaped"""")
114 | assert.partial("""'"abc'"def"""", """"abc""", 6)
115 | }
116 |
117 | test("ident") {
118 | object assert extends Assertions(ident)
119 |
120 | assert.complete("dave", "dave")
121 | assert.failure("if", 2)
122 | assert.complete("ifdave", "ifdave")
123 | assert.partial("dave was here", "dave", 4)
124 | }
125 | }
126 |
127 | object TypeParserSuite extends SimpleTestSuite with AllParsers with ParserSuiteHelpers {
128 | import PrefixOp._
129 | import InfixOp._
130 |
131 | object assert extends Assertions(tpe)
132 |
133 | test("literal") {
134 | assert.complete("Int", IntType)
135 | assert.complete("Real", DblType)
136 | assert.complete("String", StrType)
137 | assert.complete("Boolean", BoolType)
138 | assert.complete("Null", NullType)
139 | }
140 |
141 | test("ref") {
142 | assert.complete("A", TypeRef("A"))
143 | }
144 |
145 | test("func") {
146 | assert.complete(
147 | "Int -> String",
148 | FuncType(List(IntType), StrType))
149 |
150 | assert.complete(
151 | "(Int, String) -> Boolean",
152 | FuncType(List(IntType, StrType), BoolType))
153 |
154 | assert.complete(
155 | "Int -> String -> Boolean",
156 | FuncType(
157 | List(IntType),
158 | FuncType(
159 | List(StrType),
160 | BoolType)))
161 |
162 | assert.complete(
163 | "(Int, Real -> Int) -> (String, Real -> String) -> Boolean",
164 | FuncType(
165 | List(IntType, FuncType(List(DblType), IntType)),
166 | FuncType(
167 | List(StrType, FuncType(List(DblType), StrType)),
168 | BoolType)))
169 | }
170 |
171 | test("union") {
172 | assert.complete(
173 | "Boolean | String",
174 | BoolType | StrType)
175 |
176 | // TODO: Should function types be higher or lower precedence than union types?
177 | assert.complete(
178 | "Int -> Boolean | String",
179 | FuncType(List(IntType), BoolType | StrType))
180 |
181 | // TODO: Should function types be higher or lower precedence than union types?
182 | assert.complete(
183 | "(Int -> Boolean) | String",
184 | FuncType(List(IntType), BoolType) | StrType)
185 | }
186 |
187 | test("nullable") {
188 | assert.complete(
189 | "(String?)",
190 | StrType.?)
191 |
192 | assert.complete(
193 | "(String -> Int?)",
194 | FuncType(List(StrType), IntType.?))
195 |
196 | assert.complete(
197 | "(A?, B?) -> C?",
198 | FuncType(List(TypeRef("A").?, TypeRef("B").?), TypeRef("C").?))
199 |
200 | assert.complete(
201 | "(A? | B?)",
202 | TypeRef("A").? | TypeRef("B").?)
203 |
204 | assert.complete(
205 | "(Foo | Bar)?",
206 | TypeRef("Foo") | TypeRef("Bar") | NullType)
207 | }
208 | }
209 |
210 | object ExprParserSuite extends SimpleTestSuite with AllParsers with ParserSuiteHelpers {
211 | import PrefixOp._
212 | import InfixOp._
213 |
214 | object assert extends Assertions(expr)
215 |
216 | test("null") {
217 | assert.complete("null", NullExpr)
218 | }
219 |
220 | test("boolean") {
221 | assert.complete("true", BoolExpr(true))
222 | assert.complete("false", BoolExpr(false))
223 | }
224 |
225 | test("number") {
226 | assert.complete("123", IntExpr(123))
227 | assert.complete("123.456", DblExpr(123.456))
228 | assert.partial("123 . 456", IntExpr(123), 3)
229 | }
230 |
231 | test("string") {
232 | assert.complete("'dave'", StrExpr("dave"))
233 | assert.complete("\"dave\"", StrExpr("dave"))
234 | assert.complete("'\"dave\"'", StrExpr("\"dave\""))
235 | assert.complete("\"'dave'\"", StrExpr("'dave'"))
236 | }
237 |
238 | test("array") {
239 | assert.complete(
240 | "[ 1, 2 + 3, 4 ]",
241 | ArrExpr(List(IntExpr(1), InfixExpr(Add, IntExpr(2), IntExpr(3)), IntExpr(4))))
242 |
243 | assert.complete(
244 | "[1,2+3,4]",
245 | ArrExpr(List(IntExpr(1), InfixExpr(Add, IntExpr(2), IntExpr(3)), IntExpr(4))))
246 |
247 | assert.complete(
248 | "[ null , [ true && false ] , false ]",
249 | ArrExpr(List(NullExpr, ArrExpr(List(InfixExpr(And, BoolExpr(true), BoolExpr(false)))), BoolExpr(false))))
250 |
251 | assert.complete(
252 | "[null,[true&&false],false]",
253 | ArrExpr(List(NullExpr, ArrExpr(List(InfixExpr(And, BoolExpr(true), BoolExpr(false)))), BoolExpr(false))))
254 | }
255 |
256 | test("object") {
257 | assert.complete(
258 | "{ foo : null , \"'bar'\" : 1 + 2 , baz : true && false}",
259 | ObjExpr(List(
260 | "foo" -> NullExpr,
261 | "'bar'" -> InfixExpr(Add, IntExpr(1), IntExpr(2)),
262 | "baz" -> InfixExpr(And, BoolExpr(true), BoolExpr(false)))))
263 |
264 | assert.complete(
265 | "{foo:null,\"'bar'\":1+2,baz:true&&false}",
266 | ObjExpr(List(
267 | "foo" -> NullExpr,
268 | "'bar'" -> InfixExpr(Add, IntExpr(1), IntExpr(2)),
269 | "baz" -> InfixExpr(And, BoolExpr(true), BoolExpr(false)))))
270 | }
271 |
272 | test("ref") {
273 | assert.complete("i", RefExpr("i"))
274 | assert.failure("if", 0)
275 | assert.complete("iff", RefExpr("iff"))
276 | }
277 |
278 | test("cond") {
279 | assert.complete(
280 | "if a then b else c",
281 | CondExpr(
282 | RefExpr("a"),
283 | RefExpr("b"),
284 | RefExpr("c")))
285 |
286 | assert.complete(
287 | "ifathenbelsec",
288 | RefExpr("ifathenbelsec"))
289 |
290 | assert.complete(
291 | "if(a)then(b)else(c)",
292 | CondExpr(
293 | RefExpr("a"),
294 | RefExpr("b"),
295 | RefExpr("c")))
296 |
297 | assert.complete(
298 | "if a > b then c + d else e + f",
299 | CondExpr(
300 | InfixExpr(Gt, RefExpr("a"), RefExpr("b")),
301 | InfixExpr(Add, RefExpr("c"), RefExpr("d")),
302 | InfixExpr(Add, RefExpr("e"), RefExpr("f"))))
303 | }
304 |
305 | test("cast") {
306 | assert.complete(
307 | "123 : Int",
308 | CastExpr(IntExpr(123), IntType))
309 |
310 | assert.complete(
311 | "123 + 234: Int",
312 | InfixExpr(
313 | InfixOp.Add,
314 | IntExpr(123),
315 | CastExpr(IntExpr(234), IntType)))
316 |
317 | assert.complete(
318 | "(123 + 234): Int",
319 | CastExpr(
320 | InfixExpr(
321 | InfixOp.Add,
322 | IntExpr(123),
323 | IntExpr(234)),
324 | IntType))
325 |
326 | assert.complete(
327 | "123 : Int | String",
328 | CastExpr(
329 | IntExpr(123),
330 | IntType | StrType))
331 |
332 | assert.complete(
333 | "123 : (Int | String)",
334 | CastExpr(
335 | IntExpr(123),
336 | IntType | StrType))
337 | }
338 |
339 | test("call") {
340 | assert.complete(
341 | "add ( a , b , c )",
342 | AppExpr(RefExpr("add"), List(RefExpr("a"), RefExpr("b"), RefExpr("c"))))
343 |
344 | assert.complete(
345 | "add(a,b,c)",
346 | AppExpr(RefExpr("add"), List(RefExpr("a"), RefExpr("b"), RefExpr("c"))))
347 | }
348 |
349 | test("paren") {
350 | assert.complete("( a )", RefExpr("a"))
351 | assert.complete("(a)", RefExpr("a"))
352 | }
353 |
354 | test("prefix") {
355 | assert.complete("- a", PrefixExpr(Neg, RefExpr("a")))
356 | assert.complete("+a", PrefixExpr(Pos, RefExpr("a")))
357 | assert.complete("!a", PrefixExpr(Not, RefExpr("a")))
358 |
359 | assert.complete(
360 | "+ a + + b",
361 | InfixExpr(
362 | Add,
363 | PrefixExpr(Pos, RefExpr("a")),
364 | PrefixExpr(Pos, RefExpr("b"))))
365 | }
366 |
367 | test("infix") {
368 | assert.complete("a || b", InfixExpr(Or, RefExpr("a"), RefExpr("b")))
369 | assert.complete("a && b", InfixExpr(And, RefExpr("a"), RefExpr("b")))
370 | assert.complete("a == b", InfixExpr(Eq, RefExpr("a"), RefExpr("b")))
371 | assert.complete("a != b", InfixExpr(Ne, RefExpr("a"), RefExpr("b")))
372 | assert.complete("a > b", InfixExpr(Gt, RefExpr("a"), RefExpr("b")))
373 | assert.complete("a < b", InfixExpr(Lt, RefExpr("a"), RefExpr("b")))
374 | assert.complete("a >= b", InfixExpr(Gte, RefExpr("a"), RefExpr("b")))
375 | assert.complete("a <= b", InfixExpr(Lte, RefExpr("a"), RefExpr("b")))
376 | assert.complete("a + b", InfixExpr(Add, RefExpr("a"), RefExpr("b")))
377 | assert.complete("a - b", InfixExpr(Sub, RefExpr("a"), RefExpr("b")))
378 | assert.complete("a * b", InfixExpr(Mul, RefExpr("a"), RefExpr("b")))
379 | assert.complete("a / b", InfixExpr(Div, RefExpr("a"), RefExpr("b")))
380 |
381 | assert.complete("a||b", InfixExpr(Or, RefExpr("a"), RefExpr("b")))
382 | assert.complete("a&&b", InfixExpr(And, RefExpr("a"), RefExpr("b")))
383 | assert.complete("a==b", InfixExpr(Eq, RefExpr("a"), RefExpr("b")))
384 | assert.complete("a!=b", InfixExpr(Ne, RefExpr("a"), RefExpr("b")))
385 | assert.complete("a>b", InfixExpr(Gt, RefExpr("a"), RefExpr("b")))
386 | assert.complete("a=b", InfixExpr(Gte, RefExpr("a"), RefExpr("b")))
388 | assert.complete("a<=b", InfixExpr(Lte, RefExpr("a"), RefExpr("b")))
389 | assert.complete("a+b", InfixExpr(Add, RefExpr("a"), RefExpr("b")))
390 | assert.complete("a-b", InfixExpr(Sub, RefExpr("a"), RefExpr("b")))
391 | assert.complete("a*b", InfixExpr(Mul, RefExpr("a"), RefExpr("b")))
392 | assert.complete("a/b", InfixExpr(Div, RefExpr("a"), RefExpr("b")))
393 |
394 | assert.complete(
395 | "a + b + c",
396 | InfixExpr(
397 | Add,
398 | InfixExpr(
399 | Add,
400 | RefExpr("a"),
401 | RefExpr("b")),
402 | RefExpr("c")))
403 |
404 | assert.complete(
405 | "a * b + c",
406 | InfixExpr(
407 | Add,
408 | InfixExpr(
409 | Mul,
410 | RefExpr("a"),
411 | RefExpr("b")),
412 | RefExpr("c")))
413 |
414 | assert.complete(
415 | "a + b * c",
416 | InfixExpr(
417 | Add,
418 | RefExpr("a"),
419 | InfixExpr(
420 | Mul,
421 | RefExpr("b"),
422 | RefExpr("c"))))
423 |
424 | assert.complete(
425 | "( a + b ) * c",
426 | InfixExpr(
427 | Mul,
428 | InfixExpr(
429 | Add,
430 | RefExpr("a"),
431 | RefExpr("b")),
432 | RefExpr("c")))
433 |
434 | assert.complete(
435 | "a * (b + c)",
436 | InfixExpr(
437 | Mul,
438 | RefExpr("a"),
439 | InfixExpr(
440 | Add,
441 | RefExpr("b"),
442 | RefExpr("c"))))
443 |
444 | assert.complete(
445 | "a <= b && c >= d",
446 | InfixExpr(
447 | And,
448 | InfixExpr(
449 | Lte,
450 | RefExpr("a"),
451 | RefExpr("b")),
452 | InfixExpr(
453 | Gte,
454 | RefExpr("c"),
455 | RefExpr("d"))))
456 |
457 | assert.complete(
458 | "(a) + (b)",
459 | InfixExpr(
460 | Add,
461 | RefExpr("a"),
462 | RefExpr("b")))
463 |
464 | assert.complete(
465 | "+a + +b",
466 | InfixExpr(
467 | Add,
468 | PrefixExpr(Pos, RefExpr("a")),
469 | PrefixExpr(Pos, RefExpr("b"))))
470 | }
471 |
472 | test("select") {
473 | assert.complete(
474 | "a . b",
475 | SelectExpr(RefExpr("a"), "b"))
476 |
477 | assert.complete(
478 | "a.b.c",
479 | SelectExpr(SelectExpr(RefExpr("a"), "b"), "c"))
480 |
481 | assert.complete(
482 | "a.b+c.d",
483 | InfixExpr(
484 | Add,
485 | SelectExpr(RefExpr("a"), "b"),
486 | SelectExpr(RefExpr("c"), "d")))
487 | }
488 |
489 | test("block") {
490 | assert.complete(
491 | "do a end",
492 | BlockExpr(Nil, RefExpr("a")))
493 |
494 | assert.complete(
495 | "doaend",
496 | RefExpr("doaend"))
497 |
498 | assert.complete(
499 | i"""do a
500 | b end
501 | """,
502 | BlockExpr(
503 | List(ExprStmt(RefExpr("a"))),
504 | RefExpr("b")))
505 |
506 | assert.failure(
507 | "do let a = 1 end",
508 | 16)
509 |
510 | assert.complete(
511 | i"""
512 | do
513 | a
514 | b
515 | c
516 | end
517 | """,
518 | BlockExpr(
519 | List(
520 | ExprStmt(RefExpr("a")),
521 | ExprStmt(RefExpr("b"))),
522 | RefExpr("c")))
523 |
524 | assert.complete(
525 | "do;a;b;c;end",
526 | BlockExpr(
527 | List(
528 | ExprStmt(RefExpr("a")),
529 | ExprStmt(RefExpr("b"))),
530 | RefExpr("c")))
531 |
532 | assert.failure("do a b c end", 0)
533 | }
534 |
535 | test("func") {
536 | assert.complete(
537 | "( a, b ) -> a + b",
538 | FuncExpr(
539 | List(FuncArg("a", None), FuncArg("b", None)),
540 | None,
541 | InfixExpr(Add, RefExpr("a"), RefExpr("b"))))
542 | assert.complete(
543 | "( a , b : String ) : Int -> a + b",
544 | FuncExpr(
545 | List(FuncArg("a", None), FuncArg("b", Some(StrType))),
546 | Some(IntType),
547 | InfixExpr(Add, RefExpr("a"), RefExpr("b"))))
548 |
549 | assert.complete(
550 | "(a:Int,b):Real->a+b",
551 | FuncExpr(
552 | List(FuncArg("a", Some(IntType)), FuncArg("b", None)),
553 | Some(DblType),
554 | InfixExpr(Add, RefExpr("a"), RefExpr("b"))))
555 |
556 | assert.complete(
557 | "a -> b -> a + b",
558 | FuncExpr(
559 | List(FuncArg("a", None)),
560 | None,
561 | FuncExpr(
562 | List(FuncArg("b", None)),
563 | None,
564 | InfixExpr(Add, RefExpr("a"), RefExpr("b")))))
565 |
566 | assert.complete(
567 | "a -> b -> a.c + b.d",
568 | FuncExpr(
569 | List(FuncArg("a", None)),
570 | None,
571 | FuncExpr(
572 | List(FuncArg("b", None)),
573 | None,
574 | InfixExpr(Add,
575 | SelectExpr(RefExpr("a"), "c"),
576 | SelectExpr(RefExpr("b"), "d")))))
577 | }
578 | }
579 |
580 | object StmtParserSuite extends SimpleTestSuite with AllParsers with ParserSuiteHelpers {
581 | object assert extends Assertions(stmt)
582 |
583 | test("let") {
584 | assert.complete("let a = b", LetStmt("a", None, RefExpr("b")))
585 |
586 | assert.complete("let a = b -> c", LetStmt(
587 | "a",
588 | None,
589 | FuncExpr(
590 | List(FuncArg("b", None)),
591 | None,
592 | RefExpr("c"))))
593 |
594 | assert.complete("let a: Int = b", LetStmt(
595 | "a",
596 | Some(IntType),
597 | RefExpr("b")))
598 |
599 | assert.complete(
600 | i"""
601 | let add = ( a, b ) -> a + b
602 | """,
603 | LetStmt(
604 | "add",
605 | None,
606 | FuncExpr(
607 | List(FuncArg("a", None), FuncArg("b", None)),
608 | None,
609 | InfixExpr(InfixOp.Add, RefExpr("a"), RefExpr("b")))))
610 | }
611 |
612 | test("expr") {
613 | assert.complete(
614 | "a + b",
615 | ExprStmt(InfixExpr(InfixOp.Add, RefExpr("a"), RefExpr("b"))))
616 | }
617 | }
618 |
619 | trait ParserSuiteHelpers {
620 | self: SimpleTestSuite =>
621 |
622 | class Assertions[A](parser: P[A]) {
623 | def complete(input: String, expected: A): Unit =
624 | partial(input, expected, input.length)
625 |
626 | def partial(input: String, expected: A, index: Int): Unit =
627 | parser.parse(input) match {
628 | case Parsed.Success(actual, n) =>
629 | assertEquals(actual, expected)
630 | assertEquals(n, index)
631 | case Parsed.Failure(_, _, _) =>
632 | fail(s"Could not parse input: [$input]")
633 | }
634 |
635 | def failure(input: String, index: Int): Unit =
636 | parser.parse(input) match {
637 | case Parsed.Success(value, _) =>
638 | fail(s"Expected parsing to fail: $input => $value")
639 | case Parsed.Failure(_, i, _) =>
640 | assertEquals(i, index)
641 | }
642 | }
643 | }
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/TypeCheckerSuite.scala:
--------------------------------------------------------------------------------
1 | // package atlas
2 |
3 | // import atlas.syntax._
4 | // import cats.{Eval, MonadError}
5 | // import cats.data.EitherT
6 | // import cats.implicits._
7 | // import minitest._
8 |
9 | // object TypeCheckerSuite extends SimpleTestSuite {
10 | // test("lit") {
11 | // assertSuccess(expr"true", BoolType)
12 | // assertSuccess(expr"123", IntType)
13 | // assertSuccess(expr"123.0", DblType)
14 | // assertSuccess(expr"'Dave'", StrType)
15 | // assertSuccess(expr"null", NullType)
16 | // }
17 |
18 | // test("let / ref") {
19 | // assertSuccess(
20 | // prog"""
21 | // let x = true
22 | // x
23 | // """,
24 | // BoolType)
25 |
26 | // assertSuccess(
27 | // prog"""
28 | // let x = 123
29 | // x
30 | // """,
31 | // IntType)
32 |
33 | // assertSuccess(
34 | // prog"""
35 | // let x = 123.0
36 | // x
37 | // """,
38 | // DblType)
39 |
40 | // assertSuccess(
41 | // prog"""
42 | // let x = 'Dave'
43 | // x
44 | // """,
45 | // StrType)
46 |
47 | // assertSuccess(
48 | // prog"""
49 | // let x: Int | String = 'Dave'
50 | // x
51 | // """,
52 | // IntType | StrType)
53 |
54 | // assertSuccess(
55 | // prog"""
56 | // let x: Int | String = if true then 'Dave' else 123
57 | // x
58 | // """,
59 | // IntType | StrType)
60 |
61 | // assertFailure(
62 | // prog"""
63 | // let x: Int | String = true
64 | // x
65 | // """,
66 | // TypeError.typeMismatch(IntType | StrType, BoolType))
67 |
68 | // assertSuccess(
69 | // prog"""
70 | // let x = null
71 | // x
72 | // """,
73 | // NullType)
74 |
75 | // assertSuccess(
76 | // prog"""
77 | // let x: String? = null
78 | // x
79 | // """,
80 | // StrType.?)
81 |
82 | // assertFailure(
83 | // prog"""
84 | // let x: String = null
85 | // x
86 | // """,
87 | // TypeError.typeMismatch(StrType, NullType))
88 | // }
89 |
90 | // test("type let") {
91 | // assertSuccess(
92 | // prog"""
93 | // type A = Int
94 | // type B = String
95 | // type C = A -> B
96 | // type D = (A, B) -> A
97 | // let x: (C | D)? = null
98 | // x
99 | // """,
100 | // FuncType(List(IntType), StrType) | FuncType(List(IntType, StrType), IntType).?
101 | // )
102 |
103 | // assertSuccess(
104 | // prog"""
105 | // type A = Int
106 | // let x: A? = null
107 | // x
108 | // """,
109 | // IntType.?
110 | // )
111 | // }
112 |
113 | // test("func") {
114 | // assertSuccess(
115 | // expr"""(a: Int, b: String) -> if true then a else b""",
116 | // IntType | StrType)
117 |
118 | // assertSuccess(
119 | // expr"""(a: Int, b: String): (Int | String) -> if true then a else b""",
120 | // IntType | StrType)
121 |
122 | // assertSuccess(
123 | // expr"""(a: Int, b: String): (Int? | String) -> if true then a else b""",
124 | // IntType | StrType | NullType)
125 |
126 | // assertFailure(
127 | // expr"""(a: Int, b: String) -> if true then a else c""",
128 | // TypeError.variableNotFound("c"))
129 |
130 | // assertFailure(
131 | // expr"""(a: A, b: String) -> if true then a else b""",
132 | // TypeError.typeNotFound("A"))
133 | // }
134 |
135 | // test("cond") {
136 | // assertSuccess(prog"""if true then 123 else 456""", IntType)
137 | // assertSuccess(prog"""if true then 123 else 'Dave'""", IntType | StrType)
138 | // assertSuccess(prog"""if true then 123 else null""", IntType.?)
139 | // assertFailure(prog"""if 123 then 123 else null""", TypeError.typeMismatch(BoolType, IntType))
140 | // }
141 |
142 | // test("cast") {
143 | // assertSuccess(expr"""123 : Int | String""", IntType | StrType)
144 | // assertFailure(expr"""(if true then 123 else 'Dave') : Int""", TypeError.typeMismatch(IntType, IntType | StrType))
145 | // }
146 |
147 | // test("array") {
148 | // assertSuccess(expr"""[123, 234, 345]""", ArrType(IntType))
149 | // assertSuccess(expr"""[123, true, 'Dave']""", ArrType(IntType | BoolType | StrType))
150 | // assertSuccess(expr"""[]""", ArrType(Type.emptyUnion))
151 | // }
152 |
153 | // def assertSuccess(expr: Expr, expected: Type): Unit =
154 | // assertEquals(TypeChecker.check(expr), Right(expected))
155 |
156 | // def assertFailure(expr: Expr, expected: TypeError): Unit =
157 | // assertEquals(TypeChecker.check(expr), Left(expected))
158 | // }
159 |
--------------------------------------------------------------------------------
/core/src/test/scala/atlas/TypeSuite.scala:
--------------------------------------------------------------------------------
1 | package atlas
2 |
3 | import atlas.syntax._
4 | import minitest._
5 |
6 | object TypeSuite extends SimpleTestSuite {
7 | test("isAssignable") {
8 | assert(Type.isAssignable(IntType, IntType))
9 |
10 | assert(Type.isAssignable(IntType | StrType, IntType))
11 | assert(Type.isAssignable(IntType | StrType, StrType))
12 |
13 | assert(!Type.isAssignable(IntType, IntType | StrType))
14 | assert(!Type.isAssignable(StrType, IntType | StrType))
15 | }
16 | }
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.1.1
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1")
2 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
4 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.1")
5 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.3")
6 |
--------------------------------------------------------------------------------