├── .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 | [![Build Status](https://travis-ci.org/cartographerio/atlas.svg?branch=develop)](https://travis-ci.org/cartographerio/atlas) 8 | [![Coverage status](https://img.shields.io/codecov/c/github/cartographerio/atlas/develop.svg)](https://codecov.io/github/cartographerio/atlas) 9 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.cartographer/atlas_2.12/badge.svg)](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 | --------------------------------------------------------------------------------