├── .gitignore ├── Procfile ├── build.sbt ├── example-apis ├── PriorityQueue.java ├── RichestPeopleRememberer.java ├── example.java └── example.rb ├── project ├── assembly.sbt ├── build.properties └── plugins.sbt ├── readme.md ├── src ├── main │ ├── java │ │ ├── MagicMultiset.java │ │ ├── PriorityQueue.java │ │ └── SimpleCAIOperationMemoizer.java │ ├── resources │ │ ├── ace.js │ │ ├── frontend.js │ │ ├── index.html │ │ ├── jquery.js │ │ ├── mode-java.js │ │ ├── mode-ruby.js │ │ └── theme-monokai.js │ └── scala │ │ ├── Optimizer.scala │ │ ├── ast_renderers │ │ └── RubyOutputter.scala │ │ ├── big_o │ │ └── BigO.scala │ │ ├── cas │ │ ├── BinaryOperatorApplications.scala │ │ ├── CasBinaryOperator.scala │ │ ├── DodgierCasFunction.scala │ │ ├── MathExp.scala │ │ ├── Multiset.scala │ │ └── Name.scala │ │ ├── external_interfaces │ │ └── ExternalInterfaces.scala │ │ ├── finatra_server │ │ ├── CompilationController.scala │ │ ├── CompilationRequest.scala │ │ └── CompilationServer.scala │ │ ├── helpers │ │ ├── UnorderedPair.scala │ │ └── VariableNameGenerator.scala │ │ ├── java_parser │ │ ├── JavaParserWrapper.scala │ │ └── JavaToAst.scala │ │ ├── java_transpiler │ │ ├── AstBuilder.scala │ │ ├── AstModifier.scala │ │ ├── InternalTypeError.scala │ │ ├── JavaBinaryOperator.scala │ │ ├── JavaClass.scala │ │ ├── JavaExpression.scala │ │ ├── JavaExpressionOrQuery.scala │ │ ├── JavaFieldDeclaration.scala │ │ ├── JavaMathHelper.scala │ │ ├── JavaMethodDeclaration.scala │ │ ├── JavaStatement.scala │ │ ├── JavaType.scala │ │ ├── MagicMultiset.scala │ │ ├── QueryActualizer.scala │ │ ├── VariableScopeDetails.scala │ │ └── queries │ │ │ ├── JavaContext.scala │ │ │ ├── LimitByClause.scala │ │ │ ├── OrderByClause.scala │ │ │ ├── Reduction.scala │ │ │ ├── UnorderedQuery.scala │ │ │ └── WhereClause.scala │ │ └── useful_data_structures │ │ ├── JavaAbbreviations.scala │ │ ├── UnorderedDataStructureLibrary.scala │ │ ├── UsefulDataStructureHelper.scala │ │ ├── UsefulUnorderedDataStructure.scala │ │ └── data_structure_library │ │ ├── ConstantSizeHeapFactory.scala │ │ ├── GroupMemoizerFactory.scala │ │ ├── MonoidKDTreeFactory.scala │ │ └── MonoidMemoizerFactory.scala └── test │ └── scala │ ├── cas │ ├── ExpressionTests.scala │ └── GenericExpressionTests.scala │ └── java_transpiler │ └── JavaMethodParsingSpecs.scala └── thoughts └── examples.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | .idea/* 15 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: target/universal/stage/bin/finatra-hello-world -- -admin.port=:0 -http.port=:$PORT -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.packager.archetypes.JavaAppPackaging 2 | import NativePackagerKeys._ 3 | 4 | name := "data-structure-maker" 5 | 6 | version := "1.0" 7 | 8 | scalaVersion := "2.11.5" 9 | 10 | resolvers ++= List( 11 | Resolver.sonatypeRepo("releases"), 12 | "Finatra Repo" at "http://twitter.github.com/finatra", 13 | "Twitter Maven" at "http://maven.twttr.com", 14 | "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/" 15 | ) 16 | 17 | libraryDependencies ++= List( 18 | "com.github.javaparser" % "javaparser-core" % "2.1.0", 19 | "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test", 20 | "org.scalacheck" %% "scalacheck" % "1.12.4" % "test", 21 | "org.scalaj" %% "scalaj-http" % "1.1.5", 22 | "com.twitter" %% "finatra" % "2.0.0", 23 | "com.twitter.finatra" %% "finatra-http" % "2.0.0.M2", 24 | "com.twitter.finatra" %% "finatra-logback" % "2.0.0.M2", 25 | "com.twitter.inject" %% "inject-server" % "2.0.0.M2" % "test", 26 | "com.twitter.inject" %% "inject-app" % "2.0.0.M2" % "test", 27 | "com.twitter.inject" %% "inject-core" % "2.0.0.M2" % "test", 28 | "com.twitter.inject" %% "inject-modules" % "2.0.0.M2" % "test", 29 | "com.twitter.finatra" %% "finatra-http" % "2.0.0.M2" % "test" classifier "tests", 30 | "com.twitter.inject" %% "inject-server" % "2.0.0.M2" % "test" classifier "tests", 31 | "com.twitter.inject" %% "inject-app" % "2.0.0.M2" % "test" classifier "tests", 32 | "com.twitter.inject" %% "inject-core" % "2.0.0.M2" % "test" classifier "tests", 33 | "com.twitter.inject" %% "inject-modules" % "2.0.0.M2" % "test" classifier "tests" 34 | ) 35 | 36 | mainClass in (Compile, run) := Some("Optimizer") 37 | 38 | ivyScala := ivyScala.value map { _.copy(overrideScalaVersion = true) } 39 | 40 | -------------------------------------------------------------------------------- /example-apis/PriorityQueue.java: -------------------------------------------------------------------------------- 1 | public class PriorityQueue { 2 | class Item { 3 | int id; 4 | int priority; 5 | } 6 | 7 | MagicMultiset queue = new MagicMultiset(); 8 | 9 | int insertItem(Item item) { 10 | queue.insert(item); 11 | } 12 | 13 | Item getCheapest() { 14 | return queue.limitBy(x -> x.priority, 1).head; 15 | } 16 | 17 | void popCheapest() { 18 | Item cheapest = getCheapest(); 19 | queue.remove(cheapest); 20 | return cheapest; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example-apis/RichestPeopleRememberer.java: -------------------------------------------------------------------------------- 1 | public class RichestPeopleRememberer { 2 | class Person { 3 | int id; 4 | int income; 5 | String nationality; 6 | } 7 | 8 | MagicMultiset people = new MagicMultiset(); 9 | 10 | int insertPerson(Person person) { 11 | people.insert(person); 12 | } 13 | 14 | Item getRichest(String nationality) { 15 | return people.filter(x -> x.nationality == nationality).limitBy(x -> - x.income, 10); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example-apis/example.java: -------------------------------------------------------------------------------- 1 | public class Example { 2 | class Person { 3 | String gender; 4 | int age; 5 | int income; 6 | } 7 | 8 | MagicMultiset people = new MagicMultiset(); 9 | 10 | int getAverageIncomeByAgeAndGender(int age, String gender) { 11 | int totalIncome = people.filter(x -> x.age == age && x.gender == gender) 12 | .sum(x -> x.income); 13 | int numberOfPeople = people.filter(x -> x.age == age && x.gender == gender).count(); 14 | 15 | return totalIncome / numberOfPeople; 16 | } 17 | 18 | int insertPerson(Person person) { 19 | people.insert(person); 20 | } 21 | 22 | void removePerson(Person person) { 23 | people.remove(person); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example-apis/example.rb: -------------------------------------------------------------------------------- 1 | class Example 2 | class Person 3 | def initialize(age, income) 4 | @age = age 5 | 6 | @income = income 7 | end 8 | end 9 | 10 | def initialize 11 | @stuff = MagicMultiset.new 12 | end 13 | 14 | def getAverageIncomeOfOverFifties 15 | totalIncome = @foo 16 | numberOfPeople = @bar 17 | 18 | (totalIncome * (numberOfPeople**-1)) 19 | end 20 | 21 | def insertPerson(age, income) 22 | insertInto_stuff(age, income) 23 | end 24 | 25 | def insertInto_stuff(age, income) 26 | @foo = (@foo + item.income) if item.age > 50 27 | @bar = (@bar + 1) if item.age > 50 28 | 29 | @stuff.insert(age, income) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /project/assembly.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.7 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | 4 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.3") 5 | 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ruining the coding interview 2 | 3 | *This code is very much a work in progress. You can get a simple demo from running it with `sbt run`: by default this optimizes the code in `example-apis/example.java`.* 4 | 5 | *I presented this project at Scala By The Bay on August 15th, 2015. You can watch a video of my talk [here](https://www.youtube.com/watch?v=oPFga7eg3Uw&feature=youtu.be), or read my slides [here](https://docs.google.com/presentation/d/1G0gkzDejLqmC8KhGMXDr3CEYym8vwHxrMShkw1ps4x0/edit?usp=sharing).* 6 | 7 | When I was interviewing for my current job, I spent a lot of time learning about different data structures and algorithms, and when you’d want to use one over another. You know, standard stuff. It takes log(n) time to insert items into a binary search tree, but then you can search for them in log(n) time. It takes constant time to append an item to an array, but looking for the item then takes linear time. So when you’re deciding whether to use an array or a binary search tree, you have to look at what operations you need to support, and how regularly you’re going to call the different operations, and then you want to choose the data structure which minimizes your cost. 8 | 9 | Other problems kind of have this structure too. Here’s a simple dynamic programming question: Given a list of the price of a stock on different days, and the restriction that you have to buy before you sell, choose the pair of dates on which you want to buy and sell to maximize your profit. 10 | 11 | The simple implementation is to run over all pairs of dates and choose the pair with the largest price difference. This takes quadratic time. It’s a better idea to loop over the list once, keeping track of the lowest price you’ve seen so far and the best profit opportunity you’ve seen so far. This implementation runs in linear time. 12 | 13 | We can look at this in a similar kind of way. Why is the second way faster? You can basically look at it as a way of speeding up the query “What is the lowest price occurring in the first n places in the array?”. This query is worth speeding up, because it takes linear time and we call it a linear number of times. 14 | 15 | So the way that you solve algorithms problems is you figure out the operations which need to be called most regularly, and you figure out what to precompute to speed them up. 16 | 17 | (Some algorithms problems don’t fit into this structure. This structure only describes the core of the algorithm question in the case where you know how to write a trivial solution to the problem, and the goal of the interview question is to come up with a faster implementation.) 18 | 19 | Compilers do all sorts of optimizations which humans don’t want to. For example, if you write something in C like 20 | 21 | int mathyThing(int k) { 22 | int total = 0; 23 | int i; 24 | 25 | for (i = 0; i < 10; i++) { 26 | total += i * k * 3 27 | } 28 | } 29 | 30 | your compiler will notice the repeated k * 3 multiplication and move that outside the loop. The compiler *spotted a slow operation and moved it outside a critical loop*. There’s that pattern again. 31 | 32 | So my project is to make optimizing compilers at a higher level than that: at the level of choosing data structures and algorithms, rather than at the level of choosing where to compute loop invariants. This is obviously a highly ambitious project. 33 | 34 | For this presentation I’m just going to be talking about the part of this I’ve implemented. Programmers should be able to use high level data structures like multisets and have a compiler decide what data structures the multisets should be using under the hood. I’m trying to make that happen. 35 | 36 | Here’s the only problem. My adventures into automatically composing data structures lead me to a wide variety of complicated data structures, which I find fascinating but I feel very little desire to actually implement. So while I’m interested in the question of what data structures you should use, I’m not actually interested in doing all the hard work which I’d need to do to actually generate usable code. In real life, you’re not allowed to just say “and now you use a heap here”, but in coding interviews you are. 37 | 38 | So for now, I’m not trying to improve programming. I just want to ruin the coding interview. 39 | 40 | 41 | ## Data structure optimizer 42 | 43 | Okay, so how are we going to do this? 44 | 45 | For the moment, I'm only going to talk you through how I'm doing this for unordered data structures, because they're simpler and I've done more work implementing them. 46 | 47 | (I’ve ended up with this project compiling Java to Ruby. You might point out that this is kind of weird for a Scala project, and you’d be right. One reason I’m currently going Java to Ruby is that Java has explicit types, which makes it easier for me to reason about, and Ruby doesn’t, which makes it easier for me to output it.) 48 | 49 | Choosing the data structures happens over several stages, just like a normal compiler. I'll walk you through how they fit together. 50 | 51 | But first, what exactly do I mean by a "data structure"? Here are a few examples: 52 | 53 | * If you want to store the total income of everyone who is in your collection, you can just store a single field with the total, and you can increase this by the income of the new person whenever someone is added, and you can decrease it by the income of the new person whenever someone is removed. 54 | * You can put all of the people in your collection in a binary search tree ordered by income. Every time someone is added to your main collection, you insert them into this BST, and every time someone is removed, you remove them. 55 | * If you don't need to be able to remove people, you could have a min heap with the ten richest users stored in it. Whenever you try to insert a new user, you see if they're richer than the root of the min heap, and if so you insert them into the heap. 56 | 57 | So the essence of what I mean by "data structure" has: 58 | 59 | * some fields, where it stores a nice representation of some part of your data 60 | * if it allows insertion, some code which needs to get executed when you insert a new item 61 | * if it allows removal, some code which needs to get executed when you remove an item 62 | * some code that should be called to get the answer to queries. 63 | 64 | Each of these corresponds to a method of the `UsefulUnorderedDataStructure` class: 65 | 66 | abstract class UsefulUnorderedDataStructure(query: UnorderedQuery) { 67 | def insertionFragment: Option[List[JavaStatement]] 68 | def removalFragment: Option[List[JavaStatement]] 69 | def fields: List[JavaFieldDeclaration] 70 | def queryCode: JavaExpressionOrQuery 71 | } 72 | 73 | 74 | Okay, so here’s an example of a simple Java class. It stores a list of people. You can add people to it, and whenever you want you can ask it to tell you the average income of people who are older than fifty: 75 | 76 | public class Example { 77 | class Person { 78 | public Person(int age, int income) { 79 | this.age = age; 80 | this.income = income; 81 | } 82 | 83 | int age; 84 | int income; 85 | } 86 | 87 | MagicMultiset stuff; 88 | 89 | int averageIncomeOfOverFifties() { 90 | int totalIncome = stuff.filter(x -> x.age > 50) 91 | .sum(x -> x.income); 92 | int numberOfPeople = stuff.filter(x -> x.age > 50).sum(x -> 1); 93 | 94 | return totalIncome / numberOfPeople; 95 | } 96 | 97 | int insertItem(int age, int income) { 98 | stuff.insert(age, income); 99 | } 100 | } 101 | 102 | (This example totally works, by the way.) 103 | 104 | If we naively translate this to Ruby, by using the unoptimizing Java to Ruby transpiler in this code, we end up with the following Ruby: 105 | 106 | class Example 107 | class Person 108 | def initialize(age, income) 109 | @age = age 110 | 111 | @income = income 112 | end 113 | end 114 | 115 | def initialize 116 | @stuff = MagicMultiset.new 117 | end 118 | 119 | def getAverageIncomeOfOverFifties 120 | totalIncome = stuff.select(->(x) { x.age > 50 }).map(->(x) { x.income }).inject(0, ->(x, y) { (x + y) }) 121 | numberOfPeople = stuff.select(->(x) { x.age > 50 }).map(->(_x) { 1 }).inject(0, ->(x, y) { (x + y) }) 122 | 123 | (totalIncome * (numberOfPeople**-1)) 124 | end 125 | 126 | def insertPerson(age, income) 127 | insertInto_stuff(age, income) 128 | end 129 | 130 | def insertInto_stuff(age, income) 131 | @stuff.insert(age, income) 132 | end 133 | end 134 | 135 | With that implementation, `getAverageIncomeOfOverFifties` takes linear time. We can do better. 136 | 137 | Here's the main method of my project, in `Optimizer.scala`: 138 | 139 | def main(args: Array[String]) { 140 | val javaSource = Source.fromFile(args.headOption.getOrElse("example-apis/example.java")).getLines().mkString("\n") 141 | 142 | val javaClass = JavaParserWrapper.parseJavaClassToAst(javaSource) 143 | 144 | val optimizedClass = optimize(javaClass) 145 | 146 | val rubyCode = RubyOutputter.outputClass(optimizedClass) 147 | 148 | println(rubyCode) 149 | } 150 | 151 | So we get a file, parse it to Java, optimize it, then output the class to Ruby. This pipeline is attractive to me because it suggests that it should be easy to output to other languages than Ruby. 152 | 153 | Nothing that exciting happens in the `JavaParserWrapper.parseJavaClassToAst` method, except me tearing my hair out over all the `null`s in the Java library I'm interfacing with. And the `RubyOutputter.outputClass` method is marginally interesting, but not the focus of this project. So let's look at how the `Optimizer.optimize` method works: 154 | 155 | 156 | def optimize(jc: JavaClass): JavaClass = { 157 | val querified = jc.querify() 158 | 159 | val auxiliaryDataStructures = querified.queries().map({ (x) => 160 | x -> UnorderedDataStructureLibrary.getBestStructureForClass(x, querified) 161 | }).toMap 162 | 163 | querified.actualizeQueries(auxiliaryDataStructures) 164 | } 165 | 166 | ### Querification 167 | 168 | In the first stage of the process, we take lines like 169 | 170 | stuff.filter(x -> x.age > 50).sum(x -> x.income) 171 | 172 | and convert them into something pretty similar to SQL queries--that expression might look like 173 | 174 | SELECT SUM(income) FROM stuff WHERE age > 50 175 | 176 | SQL-style queries have a bunch of nice properties. For example, we might want to observe that `list.filter(predicate1).filter(predicate2)` is the same as `list.filter(predicate2).filter(predicate1)`. The process of converting these expressions to specialised `UnorderedQuery` objects (as defined in `UnorderedQuery.scala`) allows this kind of logic to happen. 177 | 178 | So that's what the `querify` method does. Here's an, uh, artist's impression of what our Java class looks like after querification: 179 | 180 | class Example 181 | class Person 182 | def initialize(age, income) 183 | @age = age 184 | 185 | @income = income 186 | end 187 | end 188 | 189 | def initialize 190 | @stuff = MagicMultiset.new 191 | end 192 | 193 | def getAverageIncomeOfOverFifties 194 | totalIncome = UnorderedQuery(JavaVariable(stuff),Set(WhereClause(x -> x.age > 50)),None,Some(Reduction[0, (x -> x.income), (x, y -> (x + y))])) 195 | numberOfPeople = UnorderedQuery(JavaVariable(stuff),Set(WhereClause(x,JavaFieldAccess(JavaVariable(x),age),JavaMath(50),false)),None,Some(Reduction[0, (x -> 1), (x, y -> (x + y))])) 196 | 197 | (totalIncome * (numberOfPeople**-1)) 198 | end 199 | 200 | def insertPerson(age, income) 201 | stuff.insert(age, income) 202 | end 203 | end 204 | 205 | 206 | ### Finding auxiliary data structures 207 | 208 | Now that we've got all the queries we want our data sets to be able to quickly answer, we need to choose appropriate data structures. 209 | 210 | To do this, we need to pay attention to the modification methods which are being called on our data structures, because there are some data structures which you can use only on immutable sets, and some which allow insertion but not removal, and some which allow both. 211 | 212 | For example, if we want to keep track of the minimum age of a person in our data structure and we didn't allow removal, we'd just store the minimum age we'd seen so far, and then update it whenever we saw a younger person. Sadly, this doesn't work if we're allowed to remove people: after the youngest person is removed, you need to linear search for the next youngest person. If you need removal in this situation, you'd probably want to be using a heap. 213 | 214 | The auxiliary data structures are chosen by the `UnorderedDataStructureLibrary`. Here's how this works: 215 | 216 | helpfulStructures 217 | .flatMap { _.tryToCreate(query) } 218 | .filter(_._1.insertionFragment.isDefined || ! requiresInsert) 219 | .filter(_._1.removalFragment.isDefined || ! requiresRemove) 220 | .sortBy(_._2) 221 | .headOption 222 | .map(_._1) 223 | 224 | We start out with the list of all the data structures we know about. 225 | 226 | `tryToCreate` tries to initialize the data structure with a given query, and if successful returns data structure and the Big O complexity which the query will take using that structure. If the query is wildly inappropriate for the data structure, like trying to use a frequency histogram to store an average, it returns None. 227 | 228 | Next, if we need to be able to insert, we filter out the data structures which can't deal with insertion. Same with removal. 229 | 230 | Next, we sort by the Big O complexity which the data structure needs to run a given query. 231 | 232 | Finally, we take the head of this list and throw away the time complexity. 233 | 234 | In our running example, we end up with two separate `MonoidMemoizer`s, one to store the running total of income for people older than 50 and one to store the running count of people older than 50. 235 | 236 | 237 | ### Actualizing queries 238 | 239 | Now, we need to turn our queries into actual Java method calls. This basically works by mapping over the Java class and looking for queries, then turning each query into the right code. 240 | 241 | We also make, if required, an insertion and/or removal method for each multiset we're dealing with. This just involves concatenating all of the auxiliary data structures' insertion and removal code into one method. The logic for this lives in the `MagicMultiset` class. 242 | 243 | And that's the optimization done. Here's the final Ruby output: 244 | 245 | class Example 246 | class Person 247 | def initialize(age, income) 248 | @age = age 249 | 250 | @income = income 251 | end 252 | end 253 | 254 | def initialize 255 | @stuff = MagicMultiset.new 256 | end 257 | 258 | def getAverageIncomeOfOverFifties 259 | totalIncome = @foo 260 | numberOfPeople = @bar 261 | 262 | (totalIncome * (numberOfPeople**-1)) 263 | end 264 | 265 | def insertPerson(age, income) 266 | insertInto_stuff(age, income) 267 | end 268 | 269 | def insertInto_stuff(age, income) 270 | @foo = (@foo + item.income) 271 | @bar = (@bar + 1) 272 | 273 | @stuff.insert(age, income) 274 | end 275 | end 276 | 277 | ## Where this project is, and where it is going 278 | 279 | I plan to work on this pretty much all the time I'm not at my real job or sleeping until my presentation at Scala By The Bay on Saturday. So the level of progress now is hopefully pretty different to where it will be on Saturday. 280 | 281 | I have a lot of work left to do. 282 | 283 | - One of the components in this project is a computer algebra system (you can see it in the `cas` package). I've put a lot of time into that component, and probably need to put a lot more in. In particular, its solving abilities are currently embarrasingly limited. 284 | - There should be an `OrderedQuery` class as well as an `UnorderedQuery` class, to make my system able to answer questions about arrays and stacks and queues etc. 285 | - I want to write more implementations of "useful data structures". 286 | - I want to make my outputter target more languages. 287 | - Instead of choosing auxiliary data structures based on Big O complexity, this system should choose them based on a numerical estimate of query time. 288 | - Automatic benchmarking! Automatic unit testing! 289 | - I want to put automatic dynamic programming in here too 290 | 291 | The system is working with the shape I intend it to. It's at least a proof of concept. 292 | 293 | Ruining the coding interview will be hard, but I think I have a shot. 294 | 295 | ## Contributing 296 | 297 | I'm interested in getting people to help me with this. Things are currently changing too fast for me to reasonably expect anyone else to be able to do anything. Eventually I'll come up with a way to make it easier for people to help me. 298 | 299 | ## Server usage 300 | 301 | $ curl --request POST --header 'Content-Type: application/json; charset=UTF-8' --data '{"contents": "class Blah {}"}' localhost:8888/compile 302 | class Blah 303 | end 304 | 305 | ## Information on the code 306 | 307 | $ find . -name '*.scala' | xargs wc -l | sort -r 308 | 3121 total 309 | 327 ./src/main/scala/cas/MathExp.scala 310 | 262 ./src/main/scala/cas/BinaryOperatorApplications.scala 311 | 157 ./src/main/scala/java_transpiler/JavaExpression.scala 312 | 143 ./src/main/scala/java_transpiler/queries/UnorderedQuery.scala 313 | 137 ./src/main/scala/ast_renderers/RubyOutputter.scala 314 | 132 ./src/test/scala/cas/ExpressionTests.scala 315 | 125 ./src/main/scala/java_transpiler/JavaClass.scala 316 | 108 ./src/main/scala/java_transpiler/queries/WhereClause.scala 317 | 102 ./src/main/scala/cas/CasBinaryOperator.scala 318 | 91 ./src/main/scala/java_transpiler/JavaStatement.scala 319 | 89 ./src/main/scala/java_transpiler/JavaMethodDeclaration.scala 320 | 83 ./src/test/scala/cas/GenericExpressionTests.scala 321 | 83 ./src/main/scala/java_transpiler/queries/Reduction.scala 322 | 80 ./src/main/scala/java_transpiler/JavaType.scala 323 | 73 ./src/main/scala/big_o/BigO.scala 324 | 68 ./src/main/scala/useful_data_structures/UsefulUnorderedDataStructure.scala 325 | 65 ./src/main/scala/data_structure_handlers/TestLoadingJavaDataStructure.scala 326 | 63 ./src/main/scala/java_transpiler/AstModifier.scala 327 | 62 ./src/main/scala/useful_data_structures/data_structure_library/GroupMemoizerFactory.scala 328 | 56 ./src/main/scala/java_transpiler/queries/LimitByClause.scala 329 | 56 ./src/main/scala/java_transpiler/MagicMultiset.scala 330 | 53 ./src/main/scala/java_parser/JavaParserWrapper.scala 331 | 50 ./src/main/scala/useful_data_structures/data_structure_library/MonoidMemoizerFactory.scala 332 | 49 ./src/main/scala/useful_data_structures/data_structure_library/PriorityQueueFactory.scala 333 | 42 ./src/main/scala/data_structure_handlers/GenericDataStructureForMultiset.scala 334 | 41 ./src/main/scala/finatra_server/CompilationController.scala 335 | 37 ./src/main/scala/java_transpiler/JavaMathHelper.scala 336 | 37 ./src/main/scala/cas/DodgierCasFunction.scala 337 | 37 ./src/main/scala/Optimizer.scala 338 | 36 ./src/main/scala/java_parser/JavaToAst.scala 339 | 32 ./src/main/scala/cas/Multiset.scala 340 | 31 ./src/main/scala/useful_data_structures/UnorderedDataStructureLibrary.scala 341 | 31 ./src/main/scala/java_transpiler/JavaExpressionOrQuery.scala 342 | 29 ./src/main/scala/useful_data_structures/UsefulDataStructureHelper.scala 343 | 28 ./src/main/scala/java_transpiler/JavaFieldDeclaration.scala 344 | 26 ./src/main/scala/java_transpiler/QueryActualizer.scala 345 | 25 ./src/test/scala/java_transpiler/JavaMethodParsingSpecs.scala 346 | 23 ./src/main/scala/finatra_server/CompilationServer.scala 347 | 20 ./src/main/scala/data_structure_handlers/ChimeraMultisetClass.scala 348 | 19 ./src/main/scala/data_structure_handlers/MutatingMethodImplementation.scala 349 | 18 ./src/main/scala/helpers/VariableNameGenerator.scala 350 | 18 ./src/main/scala/helpers/UnorderedPair.scala 351 | 17 ./src/main/scala/external_interfaces/ExternalInterfaces.scala 352 | 12 ./src/main/scala/java_transpiler/queries/OrderByClause.scala 353 | 12 ./src/main/scala/java_transpiler/AstBuilder.scala 354 | 7 ./src/main/scala/java_transpiler/queries/JavaContext.scala 355 | 7 ./src/main/scala/java_transpiler/JavaBinaryOperator.scala 356 | 7 ./src/main/scala/finatra_server/HiRequest.scala 357 | 5 ./src/main/scala/java_transpiler/VariableScopeDetails.scala 358 | 5 ./src/main/scala/java_transpiler/InternalTypeError.scala 359 | 5 ./src/main/scala/cas/Name.scala 360 | -------------------------------------------------------------------------------- /src/main/java/MagicMultiset.java: -------------------------------------------------------------------------------- 1 | public class MagicMultiset { 2 | String[] fields; 3 | boolean allowsInsert; 4 | boolean allowsDelete; 5 | 6 | public MagicMultiset(String[] fields, boolean allowsInsert, boolean allowsDelete) { 7 | this.fields = fields; 8 | } 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/PriorityQueue.java: -------------------------------------------------------------------------------- 1 | //public class PriorityQueue { 2 | // class Item { 3 | // int priority; 4 | // int id; 5 | // } 6 | // 7 | // MagicMultiset stuff = new MagicMultiset(true, true); 8 | // 9 | // int getIdOfCheapest() { 10 | // return stuff.orderDescendingBy(x -> x.priority).first.id; 11 | // } 12 | // 13 | // int insertItem(int priority, int id) { 14 | // stuff.insert(priority, id); 15 | // } 16 | // 17 | // int popCheapest() { 18 | // int cheapest = getIdOfCheapest(); 19 | // stuff.remove(cheapest); 20 | // return cheapest; 21 | // } 22 | //} 23 | -------------------------------------------------------------------------------- /src/main/java/SimpleCAIOperationMemoizer.java: -------------------------------------------------------------------------------- 1 | import big_o.*; 2 | import java_transpiler.queries.UnorderedQuery; 3 | 4 | import java.util.function.BiFunction; 5 | 6 | // question: how should I handle constants which are macros vs fields which should actually be stored? 7 | public class SimpleCAIOperationMemoizer { 8 | BigO timeForInsert = Constant.time(); 9 | BigO timeForRemove = Constant.time(); 10 | 11 | int storedValue; 12 | int startValue; 13 | BiFunction combiner; 14 | 15 | // so much todo 16 | public SimpleCAIOperationMemoizer(BiFunction combiner, int startValue) { 17 | this.combiner = combiner; 18 | this.startValue = startValue; 19 | } 20 | 21 | void initialize() { 22 | this.storedValue = startValue; 23 | } 24 | 25 | void beforeInsert(int item) { 26 | this.storedValue = this.combiner.apply(this.storedValue, item); 27 | } 28 | 29 | void afterInsert() {} 30 | 31 | BigO timeForQuery(UnorderedQuery query) { 32 | if (query.mbReduction() == null) { 33 | return Logarithmic.time(); 34 | } else { 35 | return null; 36 | } 37 | } 38 | 39 | int query() { 40 | return this.storedValue; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/frontend.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var editor = ace.edit("editor"); 3 | 4 | editor.setValue('public class RichestPeopleRememberer {\n class Person {\n int id;\n int income;\n String nationality;\n }\n\n MagicMultiset people = new MagicMultiset();\n\n int insertPerson(Person person) {\n people.insert(person);\n }\n\n Item getRichest(String nationality) {\n return people.filter(x -> x.nationality == nationality).limitBy(x -> - x.income, 10);\n }\n}\n'); 5 | editor.setTheme("ace/theme/monokai"); 6 | editor.getSession().setMode("ace/mode/java"); 7 | editor.gotoLine(0); 8 | editor.getSession().setUseWrapMode(true); 9 | 10 | var output = ace.edit("output"); 11 | output.setTheme("ace/theme/monokai"); 12 | output.getSession().setMode("ace/mode/ruby"); 13 | output.setReadOnly(true); 14 | output.getSession().setUseWrapMode(true); 15 | 16 | $("#compile").on("click", function() { 17 | $("#compile").text("I am compiling!"); 18 | $.ajax("/compile", 19 | { 20 | method: "POST", 21 | data: JSON.stringify({"contents": editor.getValue(), "optimization": $("#optimization").prop('checked')}), 22 | headers: { 23 | 'Content-Type': "application/json, charset=UTF-8", 24 | }, 25 | success: function (response) { 26 | output.setValue(response); 27 | output.gotoLine(0); 28 | $("#compile").text("compile!"); 29 | } 30 | } 31 | ); 32 | }) 33 | }); 34 | -------------------------------------------------------------------------------- /src/main/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 47 | 48 | cool optimizing transpiler 49 | 50 | 51 | 52 |

buck's cool optimizing transpiler

53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 |

output

61 | 62 |
63 | 64 |
65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/main/resources/mode-ruby.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/ruby_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) { 2 | "use strict"; 3 | 4 | var oop = require("../lib/oop"); 5 | var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; 6 | var constantOtherSymbol = exports.constantOtherSymbol = { 7 | token : "constant.other.symbol.ruby", // symbol 8 | regex : "[:](?:[A-Za-z_]|[@$](?=[a-zA-Z0-9_]))[a-zA-Z0-9_]*[!=?]?" 9 | }; 10 | 11 | var qString = exports.qString = { 12 | token : "string", // single line 13 | regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" 14 | }; 15 | 16 | var qqString = exports.qqString = { 17 | token : "string", // single line 18 | regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 19 | }; 20 | 21 | var tString = exports.tString = { 22 | token : "string", // backtick string 23 | regex : "[`](?:(?:\\\\.)|(?:[^'\\\\]))*?[`]" 24 | }; 25 | 26 | var constantNumericHex = exports.constantNumericHex = { 27 | token : "constant.numeric", // hex 28 | regex : "0[xX][0-9a-fA-F](?:[0-9a-fA-F]|_(?=[0-9a-fA-F]))*\\b" 29 | }; 30 | 31 | var constantNumericFloat = exports.constantNumericFloat = { 32 | token : "constant.numeric", // float 33 | regex : "[+-]?\\d(?:\\d|_(?=\\d))*(?:(?:\\.\\d(?:\\d|_(?=\\d))*)?(?:[eE][+-]?\\d+)?)?\\b" 34 | }; 35 | 36 | var RubyHighlightRules = function() { 37 | 38 | var builtinFunctions = ( 39 | "abort|Array|assert|assert_equal|assert_not_equal|assert_same|assert_not_same|" + 40 | "assert_nil|assert_not_nil|assert_match|assert_no_match|assert_in_delta|assert_throws|" + 41 | "assert_raise|assert_nothing_raised|assert_instance_of|assert_kind_of|assert_respond_to|" + 42 | "assert_operator|assert_send|assert_difference|assert_no_difference|assert_recognizes|" + 43 | "assert_generates|assert_response|assert_redirected_to|assert_template|assert_select|" + 44 | "assert_select_email|assert_select_rjs|assert_select_encoded|css_select|at_exit|" + 45 | "attr|attr_writer|attr_reader|attr_accessor|attr_accessible|autoload|binding|block_given?|callcc|" + 46 | "caller|catch|chomp|chomp!|chop|chop!|defined?|delete_via_redirect|eval|exec|exit|" + 47 | "exit!|fail|Float|flunk|follow_redirect!|fork|form_for|form_tag|format|gets|global_variables|gsub|" + 48 | "gsub!|get_via_redirect|host!|https?|https!|include|Integer|lambda|link_to|" + 49 | "link_to_unless_current|link_to_function|link_to_remote|load|local_variables|loop|open|open_session|" + 50 | "p|print|printf|proc|putc|puts|post_via_redirect|put_via_redirect|raise|rand|" + 51 | "raw|readline|readlines|redirect?|request_via_redirect|require|scan|select|" + 52 | "set_trace_func|sleep|split|sprintf|srand|String|stylesheet_link_tag|syscall|system|sub|sub!|test|" + 53 | "throw|trace_var|trap|untrace_var|atan2|cos|exp|frexp|ldexp|log|log10|sin|sqrt|tan|" + 54 | "render|javascript_include_tag|csrf_meta_tag|label_tag|text_field_tag|submit_tag|check_box_tag|" + 55 | "content_tag|radio_button_tag|text_area_tag|password_field_tag|hidden_field_tag|" + 56 | "fields_for|select_tag|options_for_select|options_from_collection_for_select|collection_select|" + 57 | "time_zone_select|select_date|select_time|select_datetime|date_select|time_select|datetime_select|" + 58 | "select_year|select_month|select_day|select_hour|select_minute|select_second|file_field_tag|" + 59 | "file_field|respond_to|skip_before_filter|around_filter|after_filter|verify|" + 60 | "protect_from_forgery|rescue_from|helper_method|redirect_to|before_filter|" + 61 | "send_data|send_file|validates_presence_of|validates_uniqueness_of|validates_length_of|" + 62 | "validates_format_of|validates_acceptance_of|validates_associated|validates_exclusion_of|" + 63 | "validates_inclusion_of|validates_numericality_of|validates_with|validates_each|" + 64 | "authenticate_or_request_with_http_basic|authenticate_or_request_with_http_digest|" + 65 | "filter_parameter_logging|match|get|post|resources|redirect|scope|assert_routing|" + 66 | "translate|localize|extract_locale_from_tld|caches_page|expire_page|caches_action|expire_action|" + 67 | "cache|expire_fragment|expire_cache_for|observe|cache_sweeper|" + 68 | "has_many|has_one|belongs_to|has_and_belongs_to_many" 69 | ); 70 | 71 | var keywords = ( 72 | "alias|and|BEGIN|begin|break|case|class|def|defined|do|else|elsif|END|end|ensure|" + 73 | "__FILE__|finally|for|gem|if|in|__LINE__|module|next|not|or|private|protected|public|" + 74 | "redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield" 75 | ); 76 | 77 | var buildinConstants = ( 78 | "true|TRUE|false|FALSE|nil|NIL|ARGF|ARGV|DATA|ENV|RUBY_PLATFORM|RUBY_RELEASE_DATE|" + 79 | "RUBY_VERSION|STDERR|STDIN|STDOUT|TOPLEVEL_BINDING" 80 | ); 81 | 82 | var builtinVariables = ( 83 | "\$DEBUG|\$defout|\$FILENAME|\$LOAD_PATH|\$SAFE|\$stdin|\$stdout|\$stderr|\$VERBOSE|" + 84 | "$!|root_url|flash|session|cookies|params|request|response|logger|self" 85 | ); 86 | 87 | var keywordMapper = this.$keywords = this.createKeywordMapper({ 88 | "keyword": keywords, 89 | "constant.language": buildinConstants, 90 | "variable.language": builtinVariables, 91 | "support.function": builtinFunctions, 92 | "invalid.deprecated": "debugger" // TODO is this a remnant from js mode? 93 | }, "identifier"); 94 | 95 | this.$rules = { 96 | "start" : [ 97 | { 98 | token : "comment", 99 | regex : "#.*$" 100 | }, { 101 | token : "comment", // multi line comment 102 | regex : "^=begin(?:$|\\s.*$)", 103 | next : "comment" 104 | }, { 105 | token : "string.regexp", 106 | regex : "[/](?:(?:\\[(?:\\\\]|[^\\]])+\\])|(?:\\\\/|[^\\]/]))*[/]\\w*\\s*(?=[).,;]|$)" 107 | }, 108 | 109 | [{ 110 | regex: "[{}]", onMatch: function(val, state, stack) { 111 | this.next = val == "{" ? this.nextState : ""; 112 | if (val == "{" && stack.length) { 113 | stack.unshift("start", state); 114 | return "paren.lparen"; 115 | } 116 | if (val == "}" && stack.length) { 117 | stack.shift(); 118 | this.next = stack.shift(); 119 | if (this.next.indexOf("string") != -1) 120 | return "paren.end"; 121 | } 122 | return val == "{" ? "paren.lparen" : "paren.rparen"; 123 | }, 124 | nextState: "start" 125 | }, { 126 | token : "string.start", 127 | regex : /"/, 128 | push : [{ 129 | token : "constant.language.escape", 130 | regex : /\\(?:[nsrtvfbae'"\\]|c.|C-.|M-.(?:\\C-.)?|[0-7]{3}|x[\da-fA-F]{2}|u[\da-fA-F]{4})/ 131 | }, { 132 | token : "paren.start", 133 | regex : /\#{/, 134 | push : "start" 135 | }, { 136 | token : "string.end", 137 | regex : /"/, 138 | next : "pop" 139 | }, { 140 | defaultToken: "string" 141 | }] 142 | }, { 143 | token : "string.start", 144 | regex : /`/, 145 | push : [{ 146 | token : "constant.language.escape", 147 | regex : /\\(?:[nsrtvfbae'"\\]|c.|C-.|M-.(?:\\C-.)?|[0-7]{3}|x[\da-fA-F]{2}|u[\da-fA-F]{4})/ 148 | }, { 149 | token : "paren.start", 150 | regex : /\#{/, 151 | push : "start" 152 | }, { 153 | token : "string.end", 154 | regex : /`/, 155 | next : "pop" 156 | }, { 157 | defaultToken: "string" 158 | }] 159 | }, { 160 | token : "string.start", 161 | regex : /'/, 162 | push : [{ 163 | token : "constant.language.escape", 164 | regex : /\\['\\]/ 165 | }, { 166 | token : "string.end", 167 | regex : /'/, 168 | next : "pop" 169 | }, { 170 | defaultToken: "string" 171 | }] 172 | }], 173 | 174 | { 175 | token : "text", // namespaces aren't symbols 176 | regex : "::" 177 | }, { 178 | token : "variable.instance", // instance variable 179 | regex : "@{1,2}[a-zA-Z_\\d]+" 180 | }, { 181 | token : "support.class", // class name 182 | regex : "[A-Z][a-zA-Z_\\d]+" 183 | }, 184 | 185 | constantOtherSymbol, 186 | constantNumericHex, 187 | constantNumericFloat, 188 | 189 | { 190 | token : "constant.language.boolean", 191 | regex : "(?:true|false)\\b" 192 | }, { 193 | token : keywordMapper, 194 | regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" 195 | }, { 196 | token : "punctuation.separator.key-value", 197 | regex : "=>" 198 | }, { 199 | stateName: "heredoc", 200 | onMatch : function(value, currentState, stack) { 201 | var next = value[2] == '-' ? "indentedHeredoc" : "heredoc"; 202 | var tokens = value.split(this.splitRegex); 203 | stack.push(next, tokens[3]); 204 | return [ 205 | {type:"constant", value: tokens[1]}, 206 | {type:"string", value: tokens[2]}, 207 | {type:"support.class", value: tokens[3]}, 208 | {type:"string", value: tokens[4]} 209 | ]; 210 | }, 211 | regex : "(<<-?)(['\"`]?)([\\w]+)(['\"`]?)", 212 | rules: { 213 | heredoc: [{ 214 | onMatch: function(value, currentState, stack) { 215 | if (value === stack[1]) { 216 | stack.shift(); 217 | stack.shift(); 218 | this.next = stack[0] || "start"; 219 | return "support.class"; 220 | } 221 | this.next = ""; 222 | return "string"; 223 | }, 224 | regex: ".*$", 225 | next: "start" 226 | }], 227 | indentedHeredoc: [{ 228 | token: "string", 229 | regex: "^ +" 230 | }, { 231 | onMatch: function(value, currentState, stack) { 232 | if (value === stack[1]) { 233 | stack.shift(); 234 | stack.shift(); 235 | this.next = stack[0] || "start"; 236 | return "support.class"; 237 | } 238 | this.next = ""; 239 | return "string"; 240 | }, 241 | regex: ".*$", 242 | next: "start" 243 | }] 244 | } 245 | }, { 246 | regex : "$", 247 | token : "empty", 248 | next : function(currentState, stack) { 249 | if (stack[0] === "heredoc" || stack[0] === "indentedHeredoc") 250 | return stack[0]; 251 | return currentState; 252 | } 253 | }, { 254 | token : "string.character", 255 | regex : "\\B\\?." 256 | }, { 257 | token : "keyword.operator", 258 | regex : "!|\\$|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|instanceof|new|delete|typeof|void)" 259 | }, { 260 | token : "paren.lparen", 261 | regex : "[[({]" 262 | }, { 263 | token : "paren.rparen", 264 | regex : "[\\])}]" 265 | }, { 266 | token : "text", 267 | regex : "\\s+" 268 | } 269 | ], 270 | "comment" : [ 271 | { 272 | token : "comment", // closing comment 273 | regex : "^=end(?:$|\\s.*$)", 274 | next : "start" 275 | }, { 276 | token : "comment", // comment spanning whole line 277 | regex : ".+" 278 | } 279 | ] 280 | }; 281 | 282 | this.normalizeRules(); 283 | }; 284 | 285 | oop.inherits(RubyHighlightRules, TextHighlightRules); 286 | 287 | exports.RubyHighlightRules = RubyHighlightRules; 288 | }); 289 | 290 | ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(require, exports, module) { 291 | "use strict"; 292 | 293 | var Range = require("../range").Range; 294 | 295 | var MatchingBraceOutdent = function() {}; 296 | 297 | (function() { 298 | 299 | this.checkOutdent = function(line, input) { 300 | if (! /^\s+$/.test(line)) 301 | return false; 302 | 303 | return /^\s*\}/.test(input); 304 | }; 305 | 306 | this.autoOutdent = function(doc, row) { 307 | var line = doc.getLine(row); 308 | var match = line.match(/^(\s*\})/); 309 | 310 | if (!match) return 0; 311 | 312 | var column = match[1].length; 313 | var openBracePos = doc.findMatchingBracket({row: row, column: column}); 314 | 315 | if (!openBracePos || openBracePos.row == row) return 0; 316 | 317 | var indent = this.$getIndent(doc.getLine(openBracePos.row)); 318 | doc.replace(new Range(row, 0, row, column-1), indent); 319 | }; 320 | 321 | this.$getIndent = function(line) { 322 | return line.match(/^\s*/)[0]; 323 | }; 324 | 325 | }).call(MatchingBraceOutdent.prototype); 326 | 327 | exports.MatchingBraceOutdent = MatchingBraceOutdent; 328 | }); 329 | 330 | ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(require, exports, module) { 331 | "use strict"; 332 | 333 | var oop = require("../../lib/oop"); 334 | var Behaviour = require("../behaviour").Behaviour; 335 | var TokenIterator = require("../../token_iterator").TokenIterator; 336 | var lang = require("../../lib/lang"); 337 | 338 | var SAFE_INSERT_IN_TOKENS = 339 | ["text", "paren.rparen", "punctuation.operator"]; 340 | var SAFE_INSERT_BEFORE_TOKENS = 341 | ["text", "paren.rparen", "punctuation.operator", "comment"]; 342 | 343 | var context; 344 | var contextCache = {}; 345 | var initContext = function(editor) { 346 | var id = -1; 347 | if (editor.multiSelect) { 348 | id = editor.selection.index; 349 | if (contextCache.rangeCount != editor.multiSelect.rangeCount) 350 | contextCache = {rangeCount: editor.multiSelect.rangeCount}; 351 | } 352 | if (contextCache[id]) 353 | return context = contextCache[id]; 354 | context = contextCache[id] = { 355 | autoInsertedBrackets: 0, 356 | autoInsertedRow: -1, 357 | autoInsertedLineEnd: "", 358 | maybeInsertedBrackets: 0, 359 | maybeInsertedRow: -1, 360 | maybeInsertedLineStart: "", 361 | maybeInsertedLineEnd: "" 362 | }; 363 | }; 364 | 365 | var getWrapped = function(selection, selected, opening, closing) { 366 | var rowDiff = selection.end.row - selection.start.row; 367 | return { 368 | text: opening + selected + closing, 369 | selection: [ 370 | 0, 371 | selection.start.column + 1, 372 | rowDiff, 373 | selection.end.column + (rowDiff ? 0 : 1) 374 | ] 375 | }; 376 | }; 377 | 378 | var CstyleBehaviour = function() { 379 | this.add("braces", "insertion", function(state, action, editor, session, text) { 380 | var cursor = editor.getCursorPosition(); 381 | var line = session.doc.getLine(cursor.row); 382 | if (text == '{') { 383 | initContext(editor); 384 | var selection = editor.getSelectionRange(); 385 | var selected = session.doc.getTextRange(selection); 386 | if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) { 387 | return getWrapped(selection, selected, '{', '}'); 388 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 389 | if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) { 390 | CstyleBehaviour.recordAutoInsert(editor, session, "}"); 391 | return { 392 | text: '{}', 393 | selection: [1, 1] 394 | }; 395 | } else { 396 | CstyleBehaviour.recordMaybeInsert(editor, session, "{"); 397 | return { 398 | text: '{', 399 | selection: [1, 1] 400 | }; 401 | } 402 | } 403 | } else if (text == '}') { 404 | initContext(editor); 405 | var rightChar = line.substring(cursor.column, cursor.column + 1); 406 | if (rightChar == '}') { 407 | var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row}); 408 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 409 | CstyleBehaviour.popAutoInsertedClosing(); 410 | return { 411 | text: '', 412 | selection: [1, 1] 413 | }; 414 | } 415 | } 416 | } else if (text == "\n" || text == "\r\n") { 417 | initContext(editor); 418 | var closing = ""; 419 | if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) { 420 | closing = lang.stringRepeat("}", context.maybeInsertedBrackets); 421 | CstyleBehaviour.clearMaybeInsertedClosing(); 422 | } 423 | var rightChar = line.substring(cursor.column, cursor.column + 1); 424 | if (rightChar === '}') { 425 | var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}'); 426 | if (!openBracePos) 427 | return null; 428 | var next_indent = this.$getIndent(session.getLine(openBracePos.row)); 429 | } else if (closing) { 430 | var next_indent = this.$getIndent(line); 431 | } else { 432 | CstyleBehaviour.clearMaybeInsertedClosing(); 433 | return; 434 | } 435 | var indent = next_indent + session.getTabString(); 436 | 437 | return { 438 | text: '\n' + indent + '\n' + next_indent + closing, 439 | selection: [1, indent.length, 1, indent.length] 440 | }; 441 | } else { 442 | CstyleBehaviour.clearMaybeInsertedClosing(); 443 | } 444 | }); 445 | 446 | this.add("braces", "deletion", function(state, action, editor, session, range) { 447 | var selected = session.doc.getTextRange(range); 448 | if (!range.isMultiLine() && selected == '{') { 449 | initContext(editor); 450 | var line = session.doc.getLine(range.start.row); 451 | var rightChar = line.substring(range.end.column, range.end.column + 1); 452 | if (rightChar == '}') { 453 | range.end.column++; 454 | return range; 455 | } else { 456 | context.maybeInsertedBrackets--; 457 | } 458 | } 459 | }); 460 | 461 | this.add("parens", "insertion", function(state, action, editor, session, text) { 462 | if (text == '(') { 463 | initContext(editor); 464 | var selection = editor.getSelectionRange(); 465 | var selected = session.doc.getTextRange(selection); 466 | if (selected !== "" && editor.getWrapBehavioursEnabled()) { 467 | return getWrapped(selection, selected, '(', ')'); 468 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 469 | CstyleBehaviour.recordAutoInsert(editor, session, ")"); 470 | return { 471 | text: '()', 472 | selection: [1, 1] 473 | }; 474 | } 475 | } else if (text == ')') { 476 | initContext(editor); 477 | var cursor = editor.getCursorPosition(); 478 | var line = session.doc.getLine(cursor.row); 479 | var rightChar = line.substring(cursor.column, cursor.column + 1); 480 | if (rightChar == ')') { 481 | var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row}); 482 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 483 | CstyleBehaviour.popAutoInsertedClosing(); 484 | return { 485 | text: '', 486 | selection: [1, 1] 487 | }; 488 | } 489 | } 490 | } 491 | }); 492 | 493 | this.add("parens", "deletion", function(state, action, editor, session, range) { 494 | var selected = session.doc.getTextRange(range); 495 | if (!range.isMultiLine() && selected == '(') { 496 | initContext(editor); 497 | var line = session.doc.getLine(range.start.row); 498 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 499 | if (rightChar == ')') { 500 | range.end.column++; 501 | return range; 502 | } 503 | } 504 | }); 505 | 506 | this.add("brackets", "insertion", function(state, action, editor, session, text) { 507 | if (text == '[') { 508 | initContext(editor); 509 | var selection = editor.getSelectionRange(); 510 | var selected = session.doc.getTextRange(selection); 511 | if (selected !== "" && editor.getWrapBehavioursEnabled()) { 512 | return getWrapped(selection, selected, '[', ']'); 513 | } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { 514 | CstyleBehaviour.recordAutoInsert(editor, session, "]"); 515 | return { 516 | text: '[]', 517 | selection: [1, 1] 518 | }; 519 | } 520 | } else if (text == ']') { 521 | initContext(editor); 522 | var cursor = editor.getCursorPosition(); 523 | var line = session.doc.getLine(cursor.row); 524 | var rightChar = line.substring(cursor.column, cursor.column + 1); 525 | if (rightChar == ']') { 526 | var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row}); 527 | if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { 528 | CstyleBehaviour.popAutoInsertedClosing(); 529 | return { 530 | text: '', 531 | selection: [1, 1] 532 | }; 533 | } 534 | } 535 | } 536 | }); 537 | 538 | this.add("brackets", "deletion", function(state, action, editor, session, range) { 539 | var selected = session.doc.getTextRange(range); 540 | if (!range.isMultiLine() && selected == '[') { 541 | initContext(editor); 542 | var line = session.doc.getLine(range.start.row); 543 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 544 | if (rightChar == ']') { 545 | range.end.column++; 546 | return range; 547 | } 548 | } 549 | }); 550 | 551 | this.add("string_dquotes", "insertion", function(state, action, editor, session, text) { 552 | if (text == '"' || text == "'") { 553 | initContext(editor); 554 | var quote = text; 555 | var selection = editor.getSelectionRange(); 556 | var selected = session.doc.getTextRange(selection); 557 | if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) { 558 | return getWrapped(selection, selected, quote, quote); 559 | } else if (!selected) { 560 | var cursor = editor.getCursorPosition(); 561 | var line = session.doc.getLine(cursor.row); 562 | var leftChar = line.substring(cursor.column-1, cursor.column); 563 | var rightChar = line.substring(cursor.column, cursor.column + 1); 564 | 565 | var token = session.getTokenAt(cursor.row, cursor.column); 566 | var rightToken = session.getTokenAt(cursor.row, cursor.column + 1); 567 | if (leftChar == "\\" && token && /escape/.test(token.type)) 568 | return null; 569 | 570 | var stringBefore = token && /string|escape/.test(token.type); 571 | var stringAfter = !rightToken || /string|escape/.test(rightToken.type); 572 | 573 | var pair; 574 | if (rightChar == quote) { 575 | pair = stringBefore !== stringAfter; 576 | } else { 577 | if (stringBefore && !stringAfter) 578 | return null; // wrap string with different quote 579 | if (stringBefore && stringAfter) 580 | return null; // do not pair quotes inside strings 581 | var wordRe = session.$mode.tokenRe; 582 | wordRe.lastIndex = 0; 583 | var isWordBefore = wordRe.test(leftChar); 584 | wordRe.lastIndex = 0; 585 | var isWordAfter = wordRe.test(leftChar); 586 | if (isWordBefore || isWordAfter) 587 | return null; // before or after alphanumeric 588 | if (rightChar && !/[\s;,.})\]\\]/.test(rightChar)) 589 | return null; // there is rightChar and it isn't closing 590 | pair = true; 591 | } 592 | return { 593 | text: pair ? quote + quote : "", 594 | selection: [1,1] 595 | }; 596 | } 597 | } 598 | }); 599 | 600 | this.add("string_dquotes", "deletion", function(state, action, editor, session, range) { 601 | var selected = session.doc.getTextRange(range); 602 | if (!range.isMultiLine() && (selected == '"' || selected == "'")) { 603 | initContext(editor); 604 | var line = session.doc.getLine(range.start.row); 605 | var rightChar = line.substring(range.start.column + 1, range.start.column + 2); 606 | if (rightChar == selected) { 607 | range.end.column++; 608 | return range; 609 | } 610 | } 611 | }); 612 | 613 | }; 614 | 615 | 616 | CstyleBehaviour.isSaneInsertion = function(editor, session) { 617 | var cursor = editor.getCursorPosition(); 618 | var iterator = new TokenIterator(session, cursor.row, cursor.column); 619 | if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) { 620 | var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1); 621 | if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) 622 | return false; 623 | } 624 | iterator.stepForward(); 625 | return iterator.getCurrentTokenRow() !== cursor.row || 626 | this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS); 627 | }; 628 | 629 | CstyleBehaviour.$matchTokenType = function(token, types) { 630 | return types.indexOf(token.type || token) > -1; 631 | }; 632 | 633 | CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) { 634 | var cursor = editor.getCursorPosition(); 635 | var line = session.doc.getLine(cursor.row); 636 | if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0])) 637 | context.autoInsertedBrackets = 0; 638 | context.autoInsertedRow = cursor.row; 639 | context.autoInsertedLineEnd = bracket + line.substr(cursor.column); 640 | context.autoInsertedBrackets++; 641 | }; 642 | 643 | CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) { 644 | var cursor = editor.getCursorPosition(); 645 | var line = session.doc.getLine(cursor.row); 646 | if (!this.isMaybeInsertedClosing(cursor, line)) 647 | context.maybeInsertedBrackets = 0; 648 | context.maybeInsertedRow = cursor.row; 649 | context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket; 650 | context.maybeInsertedLineEnd = line.substr(cursor.column); 651 | context.maybeInsertedBrackets++; 652 | }; 653 | 654 | CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) { 655 | return context.autoInsertedBrackets > 0 && 656 | cursor.row === context.autoInsertedRow && 657 | bracket === context.autoInsertedLineEnd[0] && 658 | line.substr(cursor.column) === context.autoInsertedLineEnd; 659 | }; 660 | 661 | CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) { 662 | return context.maybeInsertedBrackets > 0 && 663 | cursor.row === context.maybeInsertedRow && 664 | line.substr(cursor.column) === context.maybeInsertedLineEnd && 665 | line.substr(0, cursor.column) == context.maybeInsertedLineStart; 666 | }; 667 | 668 | CstyleBehaviour.popAutoInsertedClosing = function() { 669 | context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1); 670 | context.autoInsertedBrackets--; 671 | }; 672 | 673 | CstyleBehaviour.clearMaybeInsertedClosing = function() { 674 | if (context) { 675 | context.maybeInsertedBrackets = 0; 676 | context.maybeInsertedRow = -1; 677 | } 678 | }; 679 | 680 | 681 | 682 | oop.inherits(CstyleBehaviour, Behaviour); 683 | 684 | exports.CstyleBehaviour = CstyleBehaviour; 685 | }); 686 | 687 | ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"], function(require, exports, module) { 688 | "use strict"; 689 | 690 | var oop = require("../../lib/oop"); 691 | var BaseFoldMode = require("./fold_mode").FoldMode; 692 | var Range = require("../../range").Range; 693 | 694 | var FoldMode = exports.FoldMode = function() {}; 695 | oop.inherits(FoldMode, BaseFoldMode); 696 | 697 | (function() { 698 | 699 | this.getFoldWidgetRange = function(session, foldStyle, row) { 700 | var range = this.indentationBlock(session, row); 701 | if (range) 702 | return range; 703 | 704 | var re = /\S/; 705 | var line = session.getLine(row); 706 | var startLevel = line.search(re); 707 | if (startLevel == -1 || line[startLevel] != "#") 708 | return; 709 | 710 | var startColumn = line.length; 711 | var maxRow = session.getLength(); 712 | var startRow = row; 713 | var endRow = row; 714 | 715 | while (++row < maxRow) { 716 | line = session.getLine(row); 717 | var level = line.search(re); 718 | 719 | if (level == -1) 720 | continue; 721 | 722 | if (line[level] != "#") 723 | break; 724 | 725 | endRow = row; 726 | } 727 | 728 | if (endRow > startRow) { 729 | var endColumn = session.getLine(endRow).length; 730 | return new Range(startRow, startColumn, endRow, endColumn); 731 | } 732 | }; 733 | this.getFoldWidget = function(session, foldStyle, row) { 734 | var line = session.getLine(row); 735 | var indent = line.search(/\S/); 736 | var next = session.getLine(row + 1); 737 | var prev = session.getLine(row - 1); 738 | var prevIndent = prev.search(/\S/); 739 | var nextIndent = next.search(/\S/); 740 | 741 | if (indent == -1) { 742 | session.foldWidgets[row - 1] = prevIndent!= -1 && prevIndent < nextIndent ? "start" : ""; 743 | return ""; 744 | } 745 | if (prevIndent == -1) { 746 | if (indent == nextIndent && line[indent] == "#" && next[indent] == "#") { 747 | session.foldWidgets[row - 1] = ""; 748 | session.foldWidgets[row + 1] = ""; 749 | return "start"; 750 | } 751 | } else if (prevIndent == indent && line[indent] == "#" && prev[indent] == "#") { 752 | if (session.getLine(row - 2).search(/\S/) == -1) { 753 | session.foldWidgets[row - 1] = "start"; 754 | session.foldWidgets[row + 1] = ""; 755 | return ""; 756 | } 757 | } 758 | 759 | if (prevIndent!= -1 && prevIndent < indent) 760 | session.foldWidgets[row - 1] = "start"; 761 | else 762 | session.foldWidgets[row - 1] = ""; 763 | 764 | if (indent < nextIndent) 765 | return "start"; 766 | else 767 | return ""; 768 | }; 769 | 770 | }).call(FoldMode.prototype); 771 | 772 | }); 773 | 774 | ace.define("ace/mode/ruby",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/ruby_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/mode/behaviour/cstyle","ace/mode/folding/coffee"], function(require, exports, module) { 775 | "use strict"; 776 | 777 | var oop = require("../lib/oop"); 778 | var TextMode = require("./text").Mode; 779 | var RubyHighlightRules = require("./ruby_highlight_rules").RubyHighlightRules; 780 | var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent; 781 | var Range = require("../range").Range; 782 | var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour; 783 | var FoldMode = require("./folding/coffee").FoldMode; 784 | 785 | var Mode = function() { 786 | this.HighlightRules = RubyHighlightRules; 787 | this.$outdent = new MatchingBraceOutdent(); 788 | this.$behaviour = new CstyleBehaviour(); 789 | this.foldingRules = new FoldMode(); 790 | }; 791 | oop.inherits(Mode, TextMode); 792 | 793 | (function() { 794 | 795 | 796 | this.lineCommentStart = "#"; 797 | 798 | this.getNextLineIndent = function(state, line, tab) { 799 | var indent = this.$getIndent(line); 800 | 801 | var tokenizedLine = this.getTokenizer().getLineTokens(line, state); 802 | var tokens = tokenizedLine.tokens; 803 | 804 | if (tokens.length && tokens[tokens.length-1].type == "comment") { 805 | return indent; 806 | } 807 | 808 | if (state == "start") { 809 | var match = line.match(/^.*[\{\(\[]\s*$/); 810 | var startingClassOrMethod = line.match(/^\s*(class|def|module)\s.*$/); 811 | var startingDoBlock = line.match(/.*do(\s*|\s+\|.*\|\s*)$/); 812 | var startingConditional = line.match(/^\s*(if|else)\s*/) 813 | if (match || startingClassOrMethod || startingDoBlock || startingConditional) { 814 | indent += tab; 815 | } 816 | } 817 | 818 | return indent; 819 | }; 820 | 821 | this.checkOutdent = function(state, line, input) { 822 | return /^\s+(end|else)$/.test(line + input) || this.$outdent.checkOutdent(line, input); 823 | }; 824 | 825 | this.autoOutdent = function(state, session, row) { 826 | var line = session.getLine(row); 827 | if (/}/.test(line)) 828 | return this.$outdent.autoOutdent(session, row); 829 | var indent = this.$getIndent(line); 830 | var prevLine = session.getLine(row - 1); 831 | var prevIndent = this.$getIndent(prevLine); 832 | var tab = session.getTabString(); 833 | if (prevIndent.length <= indent.length) { 834 | if (indent.slice(-tab.length) == tab) 835 | session.remove(new Range(row, indent.length-tab.length, row, indent.length)); 836 | } 837 | }; 838 | 839 | this.$id = "ace/mode/ruby"; 840 | }).call(Mode.prototype); 841 | 842 | exports.Mode = Mode; 843 | }); 844 | -------------------------------------------------------------------------------- /src/main/resources/theme-monokai.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"], function(require, exports, module) { 2 | 3 | exports.isDark = true; 4 | exports.cssClass = "ace-monokai"; 5 | exports.cssText = ".ace-monokai .ace_gutter {\ 6 | background: #2F3129;\ 7 | color: #8F908A\ 8 | }\ 9 | .ace-monokai .ace_print-margin {\ 10 | width: 1px;\ 11 | background: #555651\ 12 | }\ 13 | .ace-monokai {\ 14 | background-color: #272822;\ 15 | color: #F8F8F2\ 16 | }\ 17 | .ace-monokai .ace_cursor {\ 18 | color: #F8F8F0\ 19 | }\ 20 | .ace-monokai .ace_marker-layer .ace_selection {\ 21 | background: #49483E\ 22 | }\ 23 | .ace-monokai.ace_multiselect .ace_selection.ace_start {\ 24 | box-shadow: 0 0 3px 0px #272822;\ 25 | }\ 26 | .ace-monokai .ace_marker-layer .ace_step {\ 27 | background: rgb(102, 82, 0)\ 28 | }\ 29 | .ace-monokai .ace_marker-layer .ace_bracket {\ 30 | margin: -1px 0 0 -1px;\ 31 | border: 1px solid #49483E\ 32 | }\ 33 | .ace-monokai .ace_marker-layer .ace_active-line {\ 34 | background: #202020\ 35 | }\ 36 | .ace-monokai .ace_gutter-active-line {\ 37 | background-color: #272727\ 38 | }\ 39 | .ace-monokai .ace_marker-layer .ace_selected-word {\ 40 | border: 1px solid #49483E\ 41 | }\ 42 | .ace-monokai .ace_invisible {\ 43 | color: #52524d\ 44 | }\ 45 | .ace-monokai .ace_entity.ace_name.ace_tag,\ 46 | .ace-monokai .ace_keyword,\ 47 | .ace-monokai .ace_meta.ace_tag,\ 48 | .ace-monokai .ace_storage {\ 49 | color: #F92672\ 50 | }\ 51 | .ace-monokai .ace_punctuation,\ 52 | .ace-monokai .ace_punctuation.ace_tag {\ 53 | color: #fff\ 54 | }\ 55 | .ace-monokai .ace_constant.ace_character,\ 56 | .ace-monokai .ace_constant.ace_language,\ 57 | .ace-monokai .ace_constant.ace_numeric,\ 58 | .ace-monokai .ace_constant.ace_other {\ 59 | color: #AE81FF\ 60 | }\ 61 | .ace-monokai .ace_invalid {\ 62 | color: #F8F8F0;\ 63 | background-color: #F92672\ 64 | }\ 65 | .ace-monokai .ace_invalid.ace_deprecated {\ 66 | color: #F8F8F0;\ 67 | background-color: #AE81FF\ 68 | }\ 69 | .ace-monokai .ace_support.ace_constant,\ 70 | .ace-monokai .ace_support.ace_function {\ 71 | color: #66D9EF\ 72 | }\ 73 | .ace-monokai .ace_fold {\ 74 | background-color: #A6E22E;\ 75 | border-color: #F8F8F2\ 76 | }\ 77 | .ace-monokai .ace_storage.ace_type,\ 78 | .ace-monokai .ace_support.ace_class,\ 79 | .ace-monokai .ace_support.ace_type {\ 80 | font-style: italic;\ 81 | color: #66D9EF\ 82 | }\ 83 | .ace-monokai .ace_entity.ace_name.ace_function,\ 84 | .ace-monokai .ace_entity.ace_other,\ 85 | .ace-monokai .ace_entity.ace_other.ace_attribute-name,\ 86 | .ace-monokai .ace_variable {\ 87 | color: #A6E22E\ 88 | }\ 89 | .ace-monokai .ace_variable.ace_parameter {\ 90 | font-style: italic;\ 91 | color: #FD971F\ 92 | }\ 93 | .ace-monokai .ace_string {\ 94 | color: #E6DB74\ 95 | }\ 96 | .ace-monokai .ace_comment {\ 97 | color: #75715E\ 98 | }\ 99 | .ace-monokai .ace_indent-guide {\ 100 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y\ 101 | }"; 102 | 103 | var dom = require("../lib/dom"); 104 | dom.importCssString(exports.cssText, exports.cssClass); 105 | }); 106 | -------------------------------------------------------------------------------- /src/main/scala/Optimizer.scala: -------------------------------------------------------------------------------- 1 | import java_parser.JavaParserWrapper 2 | import java_transpiler.JavaClass 3 | 4 | import ast_renderers.RubyOutputter 5 | import useful_data_structures.UnorderedDataStructureLibrary 6 | 7 | import scala.io.Source 8 | 9 | object Optimizer { 10 | def optimize(jc: JavaClass): JavaClass = { 11 | val querified = jc.querify() 12 | 13 | val auxiliaryDataStructures = querified.queries().map({ (x) => 14 | x -> UnorderedDataStructureLibrary.getBestStructureForClass(x, querified) 15 | }).toMap 16 | 17 | querified.actualizeQueries(auxiliaryDataStructures) 18 | } 19 | 20 | def unoptimized(jc: JavaClass): JavaClass = { 21 | val querified = jc.querify() 22 | 23 | querified.actualizeQueries(Map()) 24 | } 25 | 26 | def main(args: Array[String]) { 27 | // val javaSource = Source.fromFile(args.headOption.getOrElse("example-apis/Example.java")).getLines().mkString("\n") 28 | val javaSource = Source.fromFile(args.headOption.getOrElse("example-apis/RichestPeopleRememberer.java")).getLines().mkString("\n") 29 | 30 | val javaClass = JavaParserWrapper.parseJavaClassToAst(javaSource) 31 | 32 | val optimizedClass = optimize(javaClass) 33 | 34 | val rubyCode = RubyOutputter.outputClass(optimizedClass) 35 | 36 | println(rubyCode) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/ast_renderers/RubyOutputter.scala: -------------------------------------------------------------------------------- 1 | package ast_renderers 2 | 3 | import java.io.{File, PrintWriter} 4 | import java_transpiler._ 5 | 6 | import cas._ 7 | import external_interfaces.ExternalInterfaces 8 | 9 | object RubyOutputter { 10 | def outputClassToFile(javaClass: JavaClass) = { 11 | val writer = new PrintWriter(new File(s"target/ruby/${javaClass.name}.rb" )) 12 | 13 | writer.write(outputClass(javaClass)) 14 | writer.close() 15 | } 16 | 17 | def outputClass(javaClass: JavaClass): String = { 18 | val initializationStmts = javaClass.fields.collect({ 19 | case x if x.initialValue.isDefined => ExpressionStatement( 20 | JavaAssignmentExpression(x.name, false, x.initialValue.get)) 21 | }) 22 | 23 | val initializationString = if (javaClass.constructor.isDefined || javaClass.fields.exists(_.initialValue.isDefined)) { 24 | val initializationMethod = JavaMethodDeclaration( 25 | "initialize", 26 | None, 27 | false, 28 | javaClass.constructor.map(_.args).getOrElse(Nil), 29 | javaClass.constructor.map(_.body).getOrElse(Nil) ++ initializationStmts) 30 | outputMethod(initializationMethod) 31 | } 32 | else 33 | "" 34 | 35 | val innerClasses = ""// javaClass.innerClasses.map(outputClass).mkString("\n") 36 | 37 | val fields = javaClass.fields.map({ (x) => 38 | s"# ${x.name}: ${x.javaType.toScalaTypeString()} = ${x.initialValue}" 39 | }).mkString("\n") 40 | 41 | val methodsString = javaClass.methods.map(outputMethod).mkString("\n\n") 42 | 43 | val badlyFormattedVersion = s"class ${javaClass.name}\n$innerClasses\n$initializationString\n$methodsString\nend" 44 | 45 | ExternalInterfaces.rubocopify(badlyFormattedVersion) 46 | } 47 | 48 | def outputMethod(decl: JavaMethodDeclaration): String = { 49 | val args = mbBracket(decl.args.map(_._1)) 50 | 51 | val body = outputBlock(decl.body, true).split("\n+").mkString("\n") 52 | 53 | s"def ${if (decl.isStatic) "self." else ""}${decl.name}$args\n$body\nend" 54 | } 55 | 56 | def outputStatement(stmt: JavaStatement, isAtEnd: Boolean): String = { 57 | val code = stmt match { 58 | case ExpressionStatement(exp) => outputExpression(exp) 59 | case ReturnStatement(exp) => isAtEnd match { 60 | case true => outputExpression(exp) 61 | case false => s"return ${outputExpression(exp)}" 62 | } 63 | case VariableDeclarationStatement(name, _, initialValue) => initialValue match { 64 | case Some(value) => s"$name = " + outputExpression(value) 65 | case None => "" 66 | } 67 | case IfStatement(cond, thenCase, elseCase) => 68 | s"if ${outputExpression(cond)} \n ${outputBlock(thenCase, isAtEnd)}\nelse\n${outputBlock(elseCase, isAtEnd)}\nend" 69 | case _ => 70 | throw new RuntimeException(s"ruby needs to have a output for ${stmt.getClass}") 71 | } 72 | 73 | (isAtEnd, stmt) match { 74 | case (_, _: ReturnStatement) => code 75 | case (false, _) => code 76 | case (true, _) => code// + "\nnil" // do I actually want this case? 77 | } 78 | } 79 | 80 | def outputBlock(stmts: List[JavaStatement], isAtEnd: Boolean): String = { 81 | val body = stmts.dropRight(1).map(outputStatement(_, false) + "\n").mkString("") 82 | val lastStatementInBody = stmts.lastOption.map(outputStatement(_, isAtEnd)).getOrElse("") 83 | body + "\n" + lastStatementInBody 84 | } 85 | 86 | def outputExpression(exp: JavaExpressionOrQuery): String = exp match { 87 | case JavaMath(ast) => outputMath(ast.mapOverVariables(outputExpression)) 88 | case JavaAssignmentExpression(name, isLocal, expr) => 89 | val variableString = if (isLocal) name else "@" + name 90 | val nameGetter = if (isLocal) JavaVariable(name) else JavaFieldAccess(JavaThis, name) 91 | 92 | expr match { 93 | case JavaMath(Sum(set)) if set.size == 2 && set.contains(JavaMathHelper.casify(nameGetter)) => { 94 | val otherThing = set.find(_ != JavaMathHelper.casify(nameGetter)).get 95 | 96 | s"$variableString += ${outputExpression(JavaMathHelper.decasify(otherThing))}" 97 | } 98 | case _ => s"$variableString = ${outputExpression(expr)}" 99 | } 100 | case JavaLambdaExpr(args, body) => args match { 101 | case Nil => body match { 102 | case JavaMath(Number(x)) => "{" + x.toString + "}" 103 | // case _ => s"lambda { ${outputExpression(body)} }" 104 | case _ => s"{ ${outputExpression(body)} }" 105 | } 106 | case _ => 107 | // s"lambda { |${args.map(_._1).mkString(", ")}| ${outputExpression(body)} }" 108 | s"{ |${args.map(_._1).mkString(", ")}| ${outputExpression(body)} }" 109 | } 110 | case JavaThis => "self" 111 | case JavaVariable(name) => name 112 | case JavaNewObject(className, _, args) => ///s"$className.new${mbBracket(args.map(outputExpression))}" 113 | outputExpression(JavaMethodCall(JavaVariable(className), "new", args)) 114 | case JavaArrayInitializerExpr(items) => "[" + items.map(outputExpression).mkString(", ") + "]" 115 | case JavaStringLiteral(x) => "\"" + x + "\"" 116 | case JavaBoolLit(x) => x.toString 117 | case JavaFieldAccess(JavaThis, field) => s"@$field" 118 | case JavaFieldAccess(scope, field) => outputExpression(scope) + "." + field 119 | case JavaMethodCall(scope, methodName, args) => methodName match { 120 | case "[]" => outputExpression(scope) + "[" + outputExpression(args.head) + "]" 121 | case "[]=" => outputExpression(scope) + "[" + outputExpression(args.head) + "] = " + outputExpression(args.last) 122 | case _ => 123 | args match { 124 | case Nil => outputExpression(scope) + "." + methodName 125 | case _ => args.last match { 126 | case x: JavaLambdaExpr => 127 | outputExpression(JavaMethodCall(scope, methodName, args.dropRight(1))) + " " + outputExpression(x) 128 | case _ => 129 | outputExpression(scope) + "." + methodName + mbBracket(args.map(outputExpression)) 130 | } 131 | } 132 | } 133 | case JavaNull => "nil" 134 | case UnorderedQueryApplication(query) => query.toString 135 | } 136 | 137 | def outputMath(math: MathExp[String]): String = math match { 138 | case Sum(terms) => "(" + terms.map(outputMath).mkString(" + ") + ")" 139 | case CasVariable(thing) => thing 140 | case Product(terms) => "(" + terms.map(outputMath).mkString(" * ") + ")" 141 | case Number(n) => n.toString 142 | case Power(base, exp) => "(" + outputMath(base) + "**" + outputMath(exp) + ")" 143 | case BinaryTreeApplication(op, lhs, rhs) => s"(${outputMath(lhs)} ${op.toString} ${outputMath(rhs)})" 144 | case CasFunctionApplication(function, args) => function.toString match { 145 | case "==" => outputMath(args.head) + " == " + outputMath(args.last) 146 | case ">" => args.head match { 147 | // this is to ensure that you get things like x < 10 instead of 10 > x 148 | case Number(n) => outputMath(args.last) + " < " + outputMath(args.head) 149 | case _ => outputMath(args.head) + " > " + outputMath(args.last) 150 | } 151 | } 152 | case x: BinaryOperatorApplication[String] => x.operator.name.name match { 153 | case "^" => s"(${x.lhs} ^ ${x.rhs})" 154 | } 155 | } 156 | 157 | def mbBracket(blah: List[String]) = { 158 | blah match { 159 | case Nil => "" 160 | case _ => "(" + blah.mkString(", ") + ")" 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/scala/big_o/BigO.scala: -------------------------------------------------------------------------------- 1 | package big_o 2 | 3 | import java_transpiler.{JavaExpressionOrQuery, JavaVariable, JavaMethodCall, JavaExpression} 4 | 5 | import com.github.javaparser.ast.expr.MethodCallExpr 6 | 7 | case class BigO(powerOfN: Int, sqrtOfN: Boolean, powerOfLogN: Int) extends Ordered[BigO] { 8 | assert(powerOfN >= 0) 9 | assert(powerOfLogN >= 0) 10 | 11 | def compare(other: BigO): Int = (this, other) match { 12 | case (BigO(n1, _, _),BigO(n2, _, _)) if n1 != n2 => n1 - n2 13 | case (BigO(_, b1, _), BigO(_, b2, _)) if b1 != b2 => if (b1) 1 else -1 14 | case (BigO(_, _, n1), BigO(_, _, n2)) => n1 - n2 15 | } 16 | 17 | // this is a hack to make java interop nicer. 18 | def time() = this 19 | 20 | override def toString = { 21 | if (this == Constant) 22 | "O(1)" 23 | else { 24 | val firstStringList = powerOfN match { 25 | case 0 => Nil 26 | case 1 => List("n") 27 | case n => List(s"n**$powerOfN") 28 | } 29 | 30 | val secondStringList = if (sqrtOfN) 31 | List(s"sqrt(n)") 32 | else 33 | Nil 34 | 35 | val thirdStringList = powerOfLogN match { 36 | case 0 => Nil 37 | case 1 => List("log n") 38 | case n => List(s"(log n)**$powerOfN") 39 | } 40 | 41 | "O(" + List(firstStringList, secondStringList, thirdStringList).flatten.mkString(" ") + ")" 42 | } 43 | } 44 | 45 | def *(other: BigO): BigO = { 46 | val extraThing = if (this.sqrtOfN && other.sqrtOfN) 1 else 0 47 | BigO( 48 | this.powerOfN + other.powerOfN + extraThing, 49 | this.sqrtOfN != other.sqrtOfN, 50 | this.powerOfLogN + other.powerOfLogN) 51 | } 52 | } 53 | 54 | object BigO { 55 | def fromJavaExpression(time: JavaExpressionOrQuery): BigO = time match { 56 | case JavaMethodCall(JavaVariable(name), "time", Nil) => name match { 57 | case "Constant" => Constant 58 | case "Linear" => Linear 59 | case "Logarithmic" => Logarithmic 60 | case "Linearithmic" => Linearithmic 61 | case "Quadratic" => Quadratic 62 | } 63 | case _ => throw new RuntimeException(s"$time is not a BigO.") 64 | } 65 | 66 | } 67 | 68 | object Linear extends BigO(1, false, 0) 69 | object Quadratic extends BigO(2, false, 0) 70 | object Logarithmic extends BigO(0, false, 1) 71 | object Linearithmic extends BigO(1, false, 1) 72 | object Constant extends BigO(0, false, 0) 73 | 74 | -------------------------------------------------------------------------------- /src/main/scala/cas/BinaryOperatorApplications.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | /* I am very proud of this file. 4 | 5 | The idea is that when you apply a binary operator, it decides on a data structure to use based on the properties of the 6 | operator. 7 | 8 | For example, applications of arbitrary binary operators are represented as binary trees. If they're associative, they're 9 | represented as lists. And so on. 10 | 11 | */ 12 | 13 | abstract class BinaryOperatorApplication[A](val operator: CasBinaryOperator[A]) extends MathExp[A] { 14 | def combineWithCollection(other: this.type): MathExp[A] 15 | def leftCombineWithItem(other: MathExp[A]): MathExp[A] 16 | def rightCombineWithItem(other: MathExp[A]): MathExp[A] 17 | def postSimplify(): MathExp[A] = this 18 | def lhs: MathExp[A] 19 | def rhs: MathExp[A] 20 | } 21 | 22 | case class SetApplication[A](op: CasBinaryOperator[A], set: Set[MathExp[A]]) extends BinaryOperatorApplication[A](op) { 23 | assert(op.is(Commutative, Associative, Idempotent)) 24 | 25 | lazy val variables = set.flatMap(_.variables) 26 | 27 | val lhs = set.head 28 | val rhs = SetApplication.build(op, set.tail) 29 | 30 | def mapOverVariables[B](f: A => B): MathExp[B] = set.map(_.mapOverVariables(f)).reduce(op.lossilyConvert[B]()(_, _)) 31 | 32 | def substitute(map: Map[A, MathExp[A]]): MathExp[A] = SetApplication.build(op, set.map(_.substitute(map))) 33 | 34 | def combineWithCollection(other: this.type): MathExp[A] = { 35 | assert(this.op == other.op) 36 | SetApplication.build(op, set ++ other.set) 37 | } 38 | 39 | def leftCombineWithItem(item: MathExp[A]) = { 40 | SetApplication.build(op, set + item) 41 | } 42 | 43 | def rightCombineWithItem(item: MathExp[A]) = leftCombineWithItem(item) 44 | } 45 | 46 | object SetApplication { 47 | def build[A](op: CasBinaryOperator[A], items: Set[MathExp[A]]): MathExp[A] = items.size match { 48 | case 0 => ??? 49 | case 1 => items.head 50 | case _ => 51 | SetApplication(op, items) 52 | } 53 | } 54 | 55 | case class ListApplication[A](op: CasBinaryOperator[A], list: List[MathExp[A]]) extends BinaryOperatorApplication[A](op) { 56 | lazy val variables = list.flatMap(_.variables).toSet 57 | 58 | val lhs = list.head 59 | val rhs = ListApplication.build(op, list.tail) 60 | 61 | def mapOverVariables[B](f: A => B): MathExp[B] = list.map(_.mapOverVariables(f)).reduce(op.lossilyConvert[B]()(_, _)) 62 | 63 | def substitute(map: Map[A, MathExp[A]]): MathExp[A] = list.map(_.substitute(map)).reduce(op.apply) 64 | 65 | def combineWithCollection(other: this.type): MathExp[A] = { 66 | assert(this.op == other.op) 67 | ListApplication(op, list ++ other.list).asInstanceOf[MathExp[A]] 68 | } 69 | 70 | def leftCombineWithItem(item: MathExp[A]) = { 71 | ListApplication(op, item +: list) 72 | } 73 | 74 | def rightCombineWithItem(item: MathExp[A]) = { ListApplication(op, list :+ item) } 75 | } 76 | 77 | object ListApplication { 78 | def build[A](op: CasBinaryOperator[A], items: List[MathExp[A]]): MathExp[A] = items.size match { 79 | case 0 => ??? 80 | case 1 => items.head 81 | case _ => ListApplication(op, items) 82 | } 83 | } 84 | 85 | 86 | case class MultisetApplication[A](op: CasBinaryOperator[A], multiset: Multiset[MathExp[A]]) 87 | extends BinaryOperatorApplication[A](op) { 88 | lazy val variables = multiset.keys.flatMap(_.variables).toSet 89 | 90 | lazy val (lhs, rhs) = multiset.splitToTwoChildren match { 91 | case (item, smallerMultiset) => (item, MultisetApplication.build(op, smallerMultiset)) 92 | } 93 | 94 | def mapOverVariables[B](f: A => B): MathExp[B] = 95 | multiset.splitToMultisets.map((x: Multiset[MathExp[A]]) => 96 | MultisetApplication( 97 | op.lossilyConvert[B](), 98 | Multiset[MathExp[B]](Map(x.items.head._1.mapOverVariables(f) -> x.items.head._2))) 99 | ).asInstanceOf[List[MathExp[B]]].reduce(op.lossilyConvert[B]()(_, _)) 100 | 101 | def substitute(map: Map[A, MathExp[A]]): MathExp[A] = { 102 | val itemsList: List[MathExp[A]] = multiset.splitToMultisets.map({x: Multiset[MathExp[A]] => 103 | val (exp, number) = x.items.head 104 | new MultisetApplication[A](op, Multiset[MathExp[A]](Map(exp.substitute(map) -> number))) 105 | }) 106 | 107 | itemsList.tail.foldLeft(itemsList.head){ (x: MathExp[A], y: MathExp[A]) => op(x, y) } 108 | } 109 | 110 | def combineWithCollection(other: this.type): MathExp[A] = { 111 | assert(this.op == other.op) 112 | MultisetApplication[A](op, multiset.combine(other.multiset)) 113 | } 114 | 115 | def leftCombineWithItem(item: MathExp[A]) = { 116 | MultisetApplication[A](op, multiset.add(item)) 117 | } 118 | 119 | def rightCombineWithItem(item: MathExp[A]) = leftCombineWithItem(item) 120 | } 121 | 122 | object MultisetApplication { 123 | def build[A](op: CasBinaryOperator[A], multiset: Multiset[MathExp[A]]): MathExp[A] = { 124 | if (multiset.items.size == 1 && multiset.items.head._2 == 1) 125 | multiset.items.head._1 126 | else 127 | MultisetApplication(op, multiset) 128 | } 129 | } 130 | 131 | case class NoDuplicatesListApplication[A](op: CasBinaryOperator[A], list: List[MathExp[A]]) 132 | extends BinaryOperatorApplication[A](op) { 133 | lazy val variables = list.flatMap(_.variables).toSet 134 | 135 | val lhs = list.head 136 | val rhs = NoDuplicatesListApplication.build(op, list.tail) 137 | 138 | def mapOverVariables[B](f: A => B): MathExp[B] = list.map(_.mapOverVariables(f)).reduce(op.lossilyConvert[B]()(_, _)) 139 | 140 | def substitute(map: Map[A, MathExp[A]]): MathExp[A] = { 141 | def removeConsecutiveDuplicates(list: List[MathExp[A]]): List[MathExp[A]] = list match { 142 | case x :: y :: xs if x == y => removeConsecutiveDuplicates(y :: xs) 143 | case x :: xs => x :: removeConsecutiveDuplicates(xs) 144 | case xs => xs 145 | } 146 | 147 | NoDuplicatesListApplication(op, removeConsecutiveDuplicates(list.map(_.substitute(map)))) 148 | } 149 | 150 | def combineWithCollection(other: this.type): MathExp[A] = { 151 | assert(this.op == other.op) 152 | NoDuplicatesListApplication[A](op, list ++ other.list.dropWhile(_ == list.last)) 153 | } 154 | 155 | def leftCombineWithItem(item: MathExp[A]) = { 156 | if (item == list.head) { 157 | this 158 | } 159 | else { 160 | NoDuplicatesListApplication(op, item :: list) 161 | } 162 | } 163 | 164 | def rightCombineWithItem(item: MathExp[A]) = { 165 | if (item == list.last) { 166 | this 167 | } 168 | else { 169 | NoDuplicatesListApplication(op, list ++ List(item)) 170 | } 171 | } 172 | } 173 | 174 | object NoDuplicatesListApplication { 175 | def build[A](op: CasBinaryOperator[A], items: List[MathExp[A]]): MathExp[A] = items.size match { 176 | case 0 => ??? 177 | case 1 => items.head 178 | case _ => NoDuplicatesListApplication(op, items) 179 | } 180 | } 181 | 182 | case class BinaryTreeApplication[A](op: CasBinaryOperator[A], lhs: MathExp[A], rhs: MathExp[A]) extends BinaryOperatorApplication[A](op) { 183 | lazy val variables = lhs.variables ++ rhs.variables 184 | 185 | def mapOverVariables[B](f: A => B): MathExp[B] = op.lossilyConvert[B]()(lhs.mapOverVariables(f), rhs.mapOverVariables(f)) 186 | 187 | def substitute(map: Map[A, MathExp[A]]) = op.apply(lhs.substitute(map), rhs.substitute(map)) 188 | 189 | def combineWithCollection(other: this.type) = { 190 | assert(this.op == other.op) 191 | BinaryTreeApplication(op, this, other) 192 | } 193 | 194 | def leftCombineWithItem(item: MathExp[A]) = BinaryTreeApplication(op, this, item) 195 | def rightCombineWithItem(item: MathExp[A]) = BinaryTreeApplication(op, item, this) 196 | } 197 | 198 | case class SymmetricTreeApplication[A](op: CasBinaryOperator[A], lhs: MathExp[A], rhs: MathExp[A]) extends BinaryOperatorApplication[A](op) { 199 | assert(lhs.hashCode() <= rhs.hashCode(), "ordering is violated for SymmetricIdempotentTreeApplication") 200 | 201 | lazy val variables = lhs.variables ++ rhs.variables 202 | 203 | def mapOverVariables[B](f: A => B): MathExp[B] = op.lossilyConvert[B]()(lhs.mapOverVariables(f), rhs.mapOverVariables(f)) 204 | 205 | def substitute(map: Map[A, MathExp[A]]) = op.apply(lhs.substitute(map), rhs.substitute(map)) 206 | 207 | def combineWithCollection(other: this.type) = { 208 | assert(this.op == other.op) 209 | if (this.hashCode() < other.hashCode()) 210 | BinaryTreeApplication(op, this, other) 211 | else 212 | BinaryTreeApplication(op, other, this) 213 | } 214 | 215 | def leftCombineWithItem(item: MathExp[A]) = { 216 | if (this.hashCode() < item.hashCode()) 217 | BinaryTreeApplication(op, this, item) 218 | else 219 | BinaryTreeApplication(op, item, this) 220 | } 221 | def rightCombineWithItem(item: MathExp[A]) = BinaryTreeApplication(op, item, this) 222 | } 223 | 224 | case class SymmetricIdempotentTreeApplication[A](op: CasBinaryOperator[A], lhs: MathExp[A], rhs: MathExp[A]) extends BinaryOperatorApplication[A](op) { 225 | assert(lhs.hashCode() < rhs.hashCode(), "ordering is violated for SymmetricIdempotentTreeApplication") 226 | 227 | lazy val variables = lhs.variables ++ rhs.variables 228 | 229 | def mapOverVariables[B](f: A => B): MathExp[B] = op.lossilyConvert[B]()(lhs.mapOverVariables(f), rhs.mapOverVariables(f)) 230 | 231 | def substitute(map: Map[A, MathExp[A]]) = op.apply(lhs.substitute(map), rhs.substitute(map)) 232 | 233 | def combineWithCollection(other: this.type) = { 234 | assert(this.op == other.op) 235 | if (this == other) 236 | this 237 | else if (this.hashCode() < other.hashCode()) 238 | BinaryTreeApplication(op, this, other) 239 | else 240 | BinaryTreeApplication(op, other, this) 241 | } 242 | 243 | def leftCombineWithItem(item: MathExp[A]) = { 244 | if (this == item) 245 | this 246 | else if (this.hashCode() < item.hashCode()) 247 | BinaryTreeApplication(op, this, item) 248 | else 249 | BinaryTreeApplication(op, item, this) 250 | } 251 | def rightCombineWithItem(item: MathExp[A]) = BinaryTreeApplication(op, item, this) 252 | } 253 | 254 | case class IdempotentTreeApplication[A](op: CasBinaryOperator[A], lhs: MathExp[A], rhs: MathExp[A]) extends BinaryOperatorApplication[A](op) { 255 | assert(lhs != rhs, "invariant violated in IdempotentTreeApplication") 256 | 257 | lazy val variables = lhs.variables ++ rhs.variables 258 | 259 | def mapOverVariables[B](f: A => B): MathExp[B] = op.lossilyConvert[B]()(lhs.mapOverVariables(f), rhs.mapOverVariables(f)) 260 | 261 | def substitute(map: Map[A, MathExp[A]]) = op.apply(lhs.substitute(map), rhs.substitute(map)) 262 | 263 | def combineWithCollection(other: this.type) = rightCombineWithItem(other) 264 | 265 | def leftCombineWithItem(item: MathExp[A]) = { 266 | if (this == item) 267 | this 268 | else 269 | IdempotentTreeApplication(op, item, this) 270 | } 271 | 272 | def rightCombineWithItem(item: MathExp[A]) = { 273 | if (this == item) 274 | this 275 | else 276 | IdempotentTreeApplication(op, this, item) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/main/scala/cas/CasBinaryOperator.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | case class CasBinaryOperator[A](name: Name, 4 | properties: Set[OperatorProperty], 5 | identities: List[MathExp[A]] = List(), 6 | annihilator: Option[MathExp[A]] = None) { 7 | def apply(lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = { 8 | lhs.applyBinaryOperator(this, rhs) 9 | } 10 | 11 | override def toString = name.name 12 | 13 | def lossilyConvert[B](): CasBinaryOperator[B] = new CasBinaryOperator[B](name, properties) 14 | 15 | // this deals with the case when you're taking the min of two expressions which aren't mins themselves. 16 | def seedWithOperation(lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = { 17 | (is(Commutative), is(Associative), is(Idempotent)) match { 18 | case (true, true, true) => SetApplication.build(this, Set(lhs, rhs)) 19 | case (true, true, false) => this.apply(MultisetApplication[A](this, new Multiset[MathExp[A]](Map(lhs -> 1))), rhs) 20 | case (true, false, true) => 21 | if (lhs == rhs) 22 | lhs 23 | else if (lhs.hashCode() < rhs.hashCode()) 24 | SymmetricIdempotentTreeApplication[A](this, lhs, rhs) 25 | else 26 | SymmetricIdempotentTreeApplication[A](this, rhs, lhs) 27 | case (true, false, false) => 28 | if (lhs.hashCode() < rhs.hashCode()) 29 | SymmetricTreeApplication[A](this, lhs, rhs) 30 | else 31 | SymmetricTreeApplication[A](this, rhs, lhs) 32 | case (false, true, true) => 33 | if (lhs == rhs) 34 | lhs 35 | else 36 | NoDuplicatesListApplication[A](this, List(lhs, rhs)) 37 | case (false, true, false) => ListApplication(this, List(lhs, rhs)) 38 | case (false, false, true) => 39 | if (lhs == rhs) 40 | lhs 41 | else 42 | IdempotentTreeApplication[A](this, lhs, rhs) 43 | case (false, false, false) => BinaryTreeApplication(this, lhs, rhs) 44 | }} 45 | 46 | def is(props: OperatorProperty*): Boolean = props.forall(properties.contains) 47 | 48 | def isIdentity(a: MathExp[A]) = identities contains a 49 | 50 | def isAnnihilator(a: MathExp[A]) = annihilator.toList contains a 51 | } 52 | 53 | sealed abstract class OperatorProperty 54 | case object Commutative extends OperatorProperty 55 | case object Associative extends OperatorProperty 56 | case object Idempotent extends OperatorProperty 57 | 58 | object min { 59 | def operator[A]() = CasBinaryOperator[A](Name("min"), Set(Commutative, Associative, Idempotent)) 60 | 61 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 62 | } 63 | 64 | object max { 65 | def operator[A]() = CasBinaryOperator[A](Name("max"), Set(Commutative, Associative, Idempotent)) 66 | 67 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 68 | } 69 | 70 | object bitwiseAnd { 71 | def operator[A]() = CasBinaryOperator[A](Name("&"), Set(Commutative, Associative, Idempotent)) 72 | 73 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 74 | } 75 | 76 | object bitwiseXor { 77 | def operator[A]() = CasBinaryOperator[A](Name("^"), Set(Commutative, Associative)) 78 | 79 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 80 | } 81 | 82 | object modulo { 83 | def operator[A]() = CasBinaryOperator[A](Name("%"), Set()) 84 | 85 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 86 | } 87 | 88 | 89 | object logicalAnd { 90 | def operator[A]() = CasBinaryOperator[A](Name("&"), Set(Commutative, Associative, Idempotent)) 91 | 92 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 93 | } 94 | 95 | object logicalOr { 96 | def operator[A]() = CasBinaryOperator[A](Name("&"), Set(Commutative, Associative, Idempotent)) 97 | 98 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = operator()(lhs, rhs) 99 | } 100 | 101 | 102 | //object max extends CasBinaryOperator(Name("min"), List(Commutative[Any], Associative[Any], Idempotent[Any])) 103 | -------------------------------------------------------------------------------- /src/main/scala/cas/DodgierCasFunction.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | case class DodgierCasFunction[A](params: List[A], body: MathExp[A]) { 4 | override def toString = s"f(${params.mkString(", ")}) = $body" 5 | lazy val isWellFormed = body.variables.forall(params.contains(_)) 6 | 7 | def apply(arguments: MathExp[A] *) = { 8 | assert(arguments.length == params.length) 9 | body.substitute(params.zip(arguments).toMap).simplify 10 | } 11 | 12 | def isCommutative() = { 13 | val (x, y) = (new DummyVariableMathExp[A], new DummyVariableMathExp[A]) 14 | apply(x, y) == apply(y, x) 15 | } 16 | 17 | def isAssociative() = { 18 | val (x, y, z) = (new DummyVariableMathExp[A], new DummyVariableMathExp[A], new DummyVariableMathExp[A]) 19 | apply(x, apply(y, z)) == apply(apply(x, y), z) 20 | } 21 | } 22 | 23 | object ExampleFunctions { 24 | implicit def intToNumber(value: Int): Number[_] = Number(value) 25 | implicit def stringToName(name: String): Name = Name(name) 26 | implicit def nameToVariableExpression(name: Name): CasVariable[_] = CasVariable(name) 27 | 28 | val plus = DodgierCasFunction(List("x", "y"), CasVariable("x") + CasVariable("y")) 29 | val plus2 = DodgierCasFunction(List("x", "y"), CasVariable("x") + CasVariable("y") * Number(2)) 30 | 31 | def main(args: Array[String]) { 32 | println(plus) 33 | println(plus.isCommutative()) 34 | println(plus2) 35 | println(plus2.isCommutative()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/cas/MathExp.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | import scala.util.Random 4 | 5 | 6 | abstract class MathExp[A] { 7 | type NameExpression = MathExp[Name] 8 | 9 | lazy val simplify: MathExp[A] = this 10 | 11 | def variables: Set[A] 12 | 13 | def mapOverVariables[B](f: A => B): MathExp[B] 14 | 15 | def substitute(names: Map[A, MathExp[A]]): MathExp[A] 16 | 17 | def +(other: MathExp[A]): MathExp[A] = (this, other) match { 18 | case (x: Sum[A], y: Sum[A]) => x.summands.foldLeft(y: MathExp[A])(_ + _) 19 | case (x: Sum[A], _) => Sum.addTerm(x.summands, other) 20 | case (_, y: Sum[A]) => Sum.addTerm(y.summands, this) 21 | case (Number(0), x) => x 22 | case (x, Number(0)) => x 23 | case (_, _) => Sum.addTerm(Set(this), other) 24 | } 25 | 26 | def *(other: MathExp[A]): MathExp[A] = (this, other) match { 27 | case (x: Product[A], y: Product[A]) => x.terms.foldLeft(y: MathExp[A])(_ * _) 28 | case (x: Product[A], _) => Product.addFactor(x.terms, other) 29 | case (_, y: Product[A]) => Product.addFactor(y.terms, this) 30 | case (Number(0), x) => Number(0) 31 | case (x, Number(0)) => Number(0) 32 | case (Number(1), x) => x 33 | case (x, Number(1)) => x 34 | case (_, _) => Product.addFactor(Set(this), other) 35 | } 36 | 37 | def -(other: MathExp[A]): MathExp[A] = this + other * Number(-1) 38 | 39 | def /(other: MathExp[A]): MathExp[A] = this * (other ** Number(-1)) 40 | 41 | def **(exponent: MathExp[A]): MathExp[A] = (this, exponent) match { 42 | case (_, Number(0)) => Number(1) 43 | case (Number(0), _) => Number(0) 44 | case (Number(1), _) => Number(1) 45 | case (_, Number(1)) => this 46 | case (Number(x), Number(y)) => (Number(x) ** Number(y)).asInstanceOf[MathExp[A]] 47 | case (Power(x, y), other) => x ** (y * other) 48 | case (x: Product[A], _) => x.terms.map(_ ** exponent).foldLeft(Number(1): MathExp[A])(_ * _) 49 | case (_, _) => Power(this, exponent) 50 | } 51 | 52 | def monteCarloEquals(other: MathExp[A]): Boolean = { 53 | if (this.variables != other.variables) { 54 | false 55 | } else { 56 | val r = new Random 57 | val values: Map[A, Number[A]] = this.variables.map(_ -> Number[A](r.nextInt(9999))).toMap 58 | 59 | this.substitute(values) == other.substitute(values) 60 | } 61 | } 62 | 63 | def applyBinaryOperator(op: CasBinaryOperator[A], other: MathExp[A]) = 64 | if (op.isIdentity(this)) 65 | other 66 | else if (op.isIdentity(other)) 67 | this 68 | else if (op.isAnnihilator(this) || op.isAnnihilator(other)) 69 | op.annihilator.get 70 | else 71 | this match { 72 | case exp : BinaryOperatorApplication[A] if exp.operator == op => 73 | other match { 74 | case exp2: BinaryOperatorApplication[A] if exp2.operator == op => 75 | exp.combineWithCollection(exp2.asInstanceOf[exp.type]) 76 | case _ => 77 | exp.rightCombineWithItem(other) 78 | } 79 | case _ => 80 | other match { 81 | case exp2: BinaryOperatorApplication[A] if exp2.operator == op => 82 | exp2.leftCombineWithItem(this) 83 | case _ => 84 | op.seedWithOperation(this, other) 85 | } 86 | } 87 | 88 | // def separateVariables(set: Set[A], otherSide: MathExp[A]): Option[(MathExp[A], MathExp[A])] 89 | def solve(name: A): Option[MathExp[A]] = None 90 | } 91 | 92 | case class Sum[A] private (summands: Set[MathExp[A]]) extends MathExp[A] { 93 | assert(summands.size > 1, s"summands are $summands") 94 | assert(summands.count(_.isInstanceOf[Number[A]]) <= 1, s"summands are $summands") 95 | assert(summands.find(_ == Number(0)) == None, s"summands are $summands") 96 | 97 | lazy val variables: Set[A] = summands.flatMap(_.variables) 98 | 99 | def substitute(map: Map[A, MathExp[A]]): MathExp[A] = { 100 | summands.toList.map(_.substitute(map)).reduce((x: MathExp[A], y: MathExp[A]) => x + y) 101 | } 102 | 103 | def mapOverVariables[B](f: A => B): MathExp[B] = summands.toList.map(_.mapOverVariables(f)).reduce(_ + _) 104 | 105 | override def solve(name: A): Option[MathExp[A]] = { 106 | val (yes, no) = summands.partition(_.variables.contains(name)) 107 | 108 | yes.toList match { 109 | case List(CasVariable(x)) if x == name => 110 | Some(Sum(no)) 111 | case _ => 112 | None 113 | } 114 | } 115 | } 116 | 117 | object Sum { 118 | import ExpressionHelper._ 119 | 120 | def addTerm[A](summands: Set[MathExp[A]], newSummand: MathExp[A]): MathExp[A] = { 121 | assert(!newSummand.isInstanceOf[Sum[_]]) 122 | 123 | val (newSummandConstantTerm, mbNewSummandExpression) = splitCoefficient(newSummand) 124 | 125 | if (newSummandConstantTerm.value == 0) 126 | buildFromValidSummandsSet(summands) 127 | else { 128 | val (otherItems, mbRelatedItem) = grabByPredicate(summands, { (x: MathExp[A]) => 129 | splitCoefficient(x)._2 == mbNewSummandExpression }) 130 | 131 | mbRelatedItem match { 132 | case None => buildFromValidSummandsSet(otherItems ++ Set(newSummand)) 133 | case Some(item) => { 134 | val newCoefficientValue = newSummandConstantTerm.value + splitCoefficient(item)._1.value 135 | if (newCoefficientValue == 0) 136 | buildFromValidSummandsSet(otherItems) 137 | else { 138 | val thingToAdd: MathExp[A] = mbNewSummandExpression.map(_ * Number(newCoefficientValue)) 139 | .getOrElse(Number(newCoefficientValue)) 140 | buildFromValidSummandsSet(otherItems ++ Set(thingToAdd)) 141 | }}}}} 142 | 143 | private def buildFromValidSummandsSet[A](summands: Set[MathExp[A]]): MathExp[A] = { 144 | summands.size match { 145 | case 0 => Number(0) 146 | case 1 => summands.head 147 | case _ => Sum(summands).asInstanceOf[MathExp[A]] // todo: why do I need this? 148 | } 149 | } 150 | } 151 | 152 | case class Product[A] private (terms: Set[MathExp[A]]) extends MathExp[A] { 153 | assert(terms.size > 1, s"terms are $terms") 154 | assert(terms.count(_.isInstanceOf[Number[A]]) <= 1, s"terms are $terms") 155 | 156 | lazy val variables = terms.flatMap(_.variables) 157 | 158 | def mapOverVariables[B](f: A => B): MathExp[B] = terms.toList.map(_.mapOverVariables(f)).reduce(_ * _) 159 | 160 | def substitute(map: Map[A, MathExp[A]]) = terms.toList.map(_.substitute(map)).reduce(_ * _) 161 | } 162 | 163 | object Product { 164 | import ExpressionHelper._ 165 | 166 | def addFactor[A](terms: Set[MathExp[A]], newTerm: MathExp[A]): MathExp[A] = { 167 | newTerm match { 168 | case n: Number[A] => addNumericFactor(terms, n) 169 | case _ => addNonNumericFactor(terms, newTerm) 170 | } 171 | } 172 | 173 | private def addNumericFactor[A](terms: Set[MathExp[A]], newTerm: Number[A]): MathExp[A] = { 174 | val (nonNumericItems, mbNumericItem) = grabByPredicate(terms, (x: MathExp[A]) => x.isInstanceOf[Number[A]]) 175 | val number = newTerm.value * mbNumericItem.getOrElse(Number(1)).asInstanceOf[Number[A]].value 176 | 177 | number match { 178 | case 0 => Number(0) 179 | case 1 => Product.buildFromValidTermsSet(nonNumericItems) 180 | case n => Product.buildFromValidTermsSet(nonNumericItems ++ Set(Number[A](n))) 181 | } 182 | } 183 | 184 | private def addNonNumericFactor[A](terms: Set[MathExp[A]], newTerm: MathExp[A]): MathExp[A] = { 185 | val (base, exponent) = splitBaseAndExponent(newTerm) 186 | 187 | if (base == Number(1) || exponent == Number(0)) 188 | buildFromValidTermsSet(terms) 189 | else { 190 | val (otherItems, mbRelatedItem) = grabByPredicate(terms, { (x: MathExp[A]) => 191 | splitBaseAndExponent(x)._1 == base }) 192 | 193 | mbRelatedItem match { 194 | case None => buildFromValidTermsSet(otherItems ++ Set(newTerm)) 195 | case Some(item) => { 196 | val newExponent = exponent + splitBaseAndExponent(item)._2 197 | if (newExponent == Number(0)) 198 | buildFromValidTermsSet(otherItems) 199 | else { 200 | val termToAdd = base ** newExponent 201 | buildFromValidTermsSet(otherItems ++ Set(termToAdd)) 202 | }}}} 203 | } 204 | 205 | def buildFromValidTermsSet[A](terms: Set[MathExp[A]]): MathExp[A] = { 206 | terms.size match { 207 | case 0 => Number(1) 208 | case 1 => terms.head 209 | case _ => Product(terms) 210 | } 211 | } 212 | } 213 | 214 | 215 | case class Power[A](base: MathExp[A], exponent: MathExp[A]) extends MathExp[A] { 216 | val variables = base.variables ++ exponent.variables 217 | 218 | assert(base != Number(1)) 219 | assert(exponent != Number(1)) 220 | 221 | def mapOverVariables[B](f: A => B): MathExp[B] = base.mapOverVariables(f) ** exponent.mapOverVariables(f) 222 | 223 | def substitute(map: Map[A, MathExp[A]]) = { 224 | base.substitute(map) ** exponent.substitute(map) 225 | } 226 | } 227 | 228 | case class CasVariable[A](name: A) extends MathExp[A] { 229 | override def toString = name.toString 230 | 231 | def mapOverVariables[B](f: A => B): MathExp[B] = CasVariable(f(name)) 232 | 233 | val variables = Set(name) 234 | 235 | def substitute(map: Map[A, MathExp[A]]) = if (map.contains(name)) map(name) else this 236 | } 237 | 238 | case class Number[A](value: Int) extends MathExp[A] { 239 | override def toString = value.toString 240 | val variables = Set[A]() 241 | 242 | def mapOverVariables[B](f: A => B): MathExp[B] = Number[B](value) 243 | 244 | def +(other: Number[A]) = Number(this.value + other.value) 245 | def *(other: Number[A]) = Number(this.value * other.value) 246 | def **(other: Number[A]) = Number(Math.pow(this.value, other.value).toInt) 247 | def substitute(map: Map[A, MathExp[A]]) = this 248 | } 249 | 250 | class DummyVariableMathExp[A] extends MathExp[A] { 251 | override def toString = "dummy" 252 | val variables = Set[A]() 253 | 254 | def mapOverVariables[B](f: A => B): MathExp[B] = new DummyVariableMathExp[B] 255 | 256 | def substitute(map: Map[A, MathExp[A]]) = this 257 | } 258 | 259 | object ExpressionHelper { 260 | def grabByPredicate[A](set: Set[A], predicate: A => Boolean): (Set[A], Option[A]) = { 261 | val (specialThings, remainingItems) = set.partition(predicate) 262 | assert(specialThings.size <= 1) 263 | (remainingItems, specialThings.headOption) 264 | } 265 | 266 | def splitCoefficient[A](exp: MathExp[A]): (Number[A], Option[MathExp[A]]) = { 267 | exp match { 268 | case n : Number[A] => (n, None) 269 | case p : Product[A] => 270 | val number = p.terms.find(_.isInstanceOf[Number[A]]).getOrElse(Number(1)).asInstanceOf[Number[A]] 271 | val exp = Product.buildFromValidTermsSet(p.terms.filterNot(_.isInstanceOf[Number[A]])) 272 | (number, Option(exp)) 273 | case _ => (Number(1), Some(exp)) 274 | } 275 | } 276 | 277 | def splitBaseAndExponent[A](exp: MathExp[A]): (MathExp[A], MathExp[A]) = { 278 | exp match { 279 | case Power(base, exponent) => (base, exponent) 280 | case _ => (exp, Number(1)) 281 | } 282 | } 283 | } 284 | 285 | case class CasFunction[A](function: MathExp[A], arity: Int) extends MathExp[A] { 286 | override def toString = function.toString 287 | 288 | def mapOverVariables[B](f: A => B): MathExp[B] = CasFunction(function.mapOverVariables(f), arity) 289 | 290 | val variables = function.variables 291 | 292 | def substitute(map: Map[A, MathExp[A]]) = this.copy(function = function.substitute(map)) 293 | } 294 | 295 | // things like "min" 296 | // this might be a terrible mistake :/ 297 | case class CasConstant[A](name: String) extends MathExp[A] { 298 | override def toString = name 299 | val variables = Set[A]() 300 | def mapOverVariables[B](f: A => B): MathExp[B] = CasConstant[B](name) 301 | def substitute(map: Map[A, MathExp[A]]) = this 302 | } 303 | 304 | case class CasFunctionApplication[A](function: CasFunction[A], args: List[MathExp[A]]) extends MathExp[A] { 305 | assert(function.arity == args.length) 306 | 307 | val variables = function.variables ++ args.flatMap(_.variables) 308 | 309 | def mapOverVariables[B](f: A => B): MathExp[B] = 310 | CasFunctionApplication( 311 | new CasFunction(function.function.mapOverVariables(f), function.arity), args.map(_.mapOverVariables(f))) 312 | 313 | def substitute(map: Map[A, MathExp[A]]) = CasFunctionApplication(function, args.map(_.substitute(map))) 314 | } 315 | 316 | object niceFunctions { 317 | object equals { 318 | def equals[A] = CasFunction[A](CasConstant("=="), 2) 319 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = CasFunctionApplication(equals, List(lhs, rhs)) 320 | } 321 | 322 | object greaterThan { 323 | def greaterThan[A] = CasFunction[A](CasConstant(">"), 2) 324 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = CasFunctionApplication(greaterThan, List(lhs, rhs)) 325 | } 326 | 327 | object greaterThanOrEquals { 328 | def greaterThanOrEquals[A] = CasFunction[A](CasConstant(">="), 2) 329 | def apply[A](lhs: MathExp[A], rhs: MathExp[A]): MathExp[A] = CasFunctionApplication(greaterThanOrEquals, List(lhs, rhs)) 330 | } 331 | } 332 | 333 | -------------------------------------------------------------------------------- /src/main/scala/cas/Multiset.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | case class Multiset[A](items: Map[A, Int]) { 4 | def get(item: A) = items.getOrElse(item, 0) 5 | 6 | def add(item: A): Multiset[A] = { 7 | Multiset(items + (item -> (get(item) + 1))) 8 | } 9 | 10 | def addMultiple(item: A, number: Int): Multiset[A] = { 11 | Multiset(items + (item -> (get(item) + number))) 12 | } 13 | 14 | def combine(other: Multiset[A]) = { 15 | other.items.foldLeft(this) { (multiset: Multiset[A], tuple: (A, Int)) => 16 | multiset.addMultiple(tuple._1, tuple._2) 17 | } 18 | } 19 | 20 | def splitToTwoChildren = { 21 | items.head match { 22 | case (item, 1) => (item, Multiset(items.tail)) 23 | case (item, n) => (item, Multiset(Map(item -> (n - 1)) ++ items.tail)) 24 | } 25 | } 26 | 27 | lazy val keys = items.keys 28 | 29 | lazy val splitToMultisets: List[Multiset[A]] = items.map({x: (A, Int) => Multiset(Map(x._1 -> x._2))}).toList 30 | 31 | lazy val size = items.values.sum 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/cas/Name.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | case class Name(name: String) { 4 | override def toString() = name 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/external_interfaces/ExternalInterfaces.scala: -------------------------------------------------------------------------------- 1 | package external_interfaces 2 | 3 | import java.io._ 4 | import scala.sys.process._ 5 | import scala.io.Source 6 | 7 | object ExternalInterfaces { 8 | def rubocopify(string: String) = { 9 | val writer = new PrintWriter(new File("/tmp/test.rb" )) 10 | writer.write(string) 11 | writer.close() 12 | 13 | "rubocop -a /tmp/test.rb" ! ProcessLogger(line => ()) 14 | 15 | Source.fromFile("/tmp/test.rb").mkString("") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/finatra_server/CompilationController.scala: -------------------------------------------------------------------------------- 1 | package finatra_server 2 | 3 | import java_parser.JavaParserWrapper 4 | 5 | import ast_renderers.RubyOutputter 6 | import com.twitter.finagle.http.Request 7 | import com.twitter.finatra.http.Controller 8 | import useful_data_structures.UnorderedDataStructureLibrary 9 | 10 | import scala.util.{Success, Failure, Try} 11 | 12 | class CompilationController extends Controller { 13 | post("/compile") { compilationRequest: CompilationRequest => 14 | val result = for { 15 | javaClass <- Try(JavaParserWrapper.parseJavaClassToAst(compilationRequest.contents)) 16 | querified <- Try(javaClass.querify()) 17 | auxiliaryDataStructures <- Try(querified.queries().map({ (x) => 18 | x -> UnorderedDataStructureLibrary.getBestStructureForClass(x, querified) 19 | }).toMap) 20 | optimizedClass <- if (compilationRequest.optimization) 21 | Try(querified.actualizeQueries(auxiliaryDataStructures)) 22 | else 23 | Try(querified.actualizeQueries(Map())) 24 | out <- Try(RubyOutputter.outputClass(optimizedClass)) 25 | } yield out 26 | 27 | result match { 28 | case Success(out) => out 29 | case Failure(exception) => exception.toString ++ "\n\n" ++ exception.getStackTrace.map(_.toString).mkString("\n") 30 | } 31 | } 32 | 33 | get("/:*") { request: Request => 34 | response.ok.file( 35 | request.params("*")) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/finatra_server/CompilationRequest.scala: -------------------------------------------------------------------------------- 1 | package finatra_server 2 | 3 | case class CompilationRequest(contents: String, optimization: Boolean) 4 | -------------------------------------------------------------------------------- /src/main/scala/finatra_server/CompilationServer.scala: -------------------------------------------------------------------------------- 1 | package finatra_server 2 | 3 | import com.twitter.finagle.http.{Response, Request} 4 | import com.twitter.finatra.http.HttpServer 5 | import com.twitter.finatra.http.filters.CommonFilters 6 | import com.twitter.finatra.http.routing.HttpRouter 7 | import com.twitter.finatra.logging.filter.{TraceIdMDCFilter, LoggingMDCFilter} 8 | import com.twitter.finatra.logging.modules.LogbackModule 9 | 10 | 11 | object CompilationServerMain extends CompilationServer 12 | 13 | class CompilationServer extends HttpServer { 14 | override def modules = Seq(LogbackModule) 15 | 16 | override def configureHttp(router: HttpRouter) { 17 | router 18 | .filter[LoggingMDCFilter[Request, Response]] 19 | .filter[TraceIdMDCFilter[Request, Response]] 20 | .filter[CommonFilters] 21 | .add[CompilationController] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/helpers/UnorderedPair.scala: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | case class UnorderedPair[A](lesser: A, greater: A) { 4 | assert(lesser.hashCode() < greater.hashCode()) 5 | 6 | lazy val toSet = Set(lesser, greater) 7 | 8 | lazy val toList = List(lesser, greater) 9 | } 10 | 11 | object UnorderedPair { 12 | def build[A](first: A, second: A): UnorderedPair[A] = { 13 | if (first.hashCode() < second.hashCode()) 14 | UnorderedPair(first, second) 15 | else 16 | UnorderedPair(second, first) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/helpers/VariableNameGenerator.scala: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | 4 | object VariableNameGenerator { 5 | var badVariableNames: List[String] = "foo bar cat dog hamster bird knife happy sad foolish game ruby x y z thing huh".split(" ").toList 6 | 7 | def getVariableName(): String = { 8 | val result = badVariableNames.head 9 | badVariableNames = badVariableNames.tail ++ List(result) 10 | result 11 | } 12 | 13 | def getRandomName(): String = { 14 | "x" + randomString(10) 15 | } 16 | 17 | def randomString(length: Int) = Stream.continually(util.Random.nextPrintableChar) take length mkString 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/java_parser/JavaParserWrapper.scala: -------------------------------------------------------------------------------- 1 | package java_parser 2 | 3 | import java.io.StringBufferInputStream 4 | import java_transpiler._ 5 | 6 | import ast_renderers.RubyOutputter 7 | import com.github.javaparser.JavaParser 8 | import com.github.javaparser.ast.CompilationUnit 9 | import useful_data_structures.UnorderedDataStructureLibrary 10 | 11 | import scala.collection.mutable 12 | 13 | object JavaParserWrapper { 14 | def main(args: Array[String]) { 15 | val node = parseJavaFile("") 16 | val classAsts = AstBuilder.build(node) 17 | println(classAsts) 18 | println(classAsts.map(RubyOutputter.outputClass).mkString("\n\n")) 19 | 20 | classAsts.foreach(RubyOutputter.outputClassToFile) 21 | } 22 | 23 | def parseJavaFile(filename: String): CompilationUnit = { 24 | parseJava(javaString) 25 | } 26 | 27 | def parseJava(java: String) = { 28 | // i know this is deprecated but IDGAF 29 | val stringBuffer = new StringBufferInputStream(java) 30 | JavaParser.parse(stringBuffer) 31 | } 32 | 33 | def parseJavaClassToAst(java: String): JavaClass = { 34 | AstBuilder.build(parseJava(java)).head 35 | } 36 | 37 | val javaString = 38 | """ 39 | |class Counter { 40 | | public Counter(int start) { 41 | | this.x = start; 42 | | new Counter(x -> x); 43 | | } 44 | | 45 | | void increase(int y) { 46 | | this.x += y; 47 | | } 48 | | int get() { 49 | | return this.x; 50 | | } 51 | |} 52 | """.stripMargin('|') 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/java_parser/JavaToAst.scala: -------------------------------------------------------------------------------- 1 | package java_parser 2 | 3 | import java.io.StringBufferInputStream 4 | import java_transpiler.AstBuilder 5 | 6 | import com.github.javaparser.JavaParser 7 | import com.github.javaparser.ast._ 8 | 9 | object JavaToAst { 10 | def main(args: Array[String]) { 11 | val node = parseJavaFile("") 12 | 13 | println(AstBuilder.build(node)) 14 | } 15 | 16 | def parseJavaFile(filename: String): CompilationUnit = { 17 | // i know this is deprecated but IDGAF 18 | println(javaString) 19 | println("... turns into") 20 | val stringBuffer = new StringBufferInputStream(javaString) 21 | JavaParser.parse(stringBuffer) 22 | } 23 | 24 | val javaString = 25 | """ 26 | class Counter { 27 | int x = 0; 28 | void increase(int y) { 29 | this.x += y; 30 | } 31 | int get() { 32 | return this.x; 33 | } 34 | } 35 | """ 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/AstBuilder.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import com.github.javaparser.ast.CompilationUnit 4 | import com.github.javaparser.ast.body._ 5 | 6 | import scala.collection.JavaConverters._ 7 | 8 | object AstBuilder { 9 | def build(cu: CompilationUnit): List[JavaClass] = { 10 | cu.getTypes.asScala.map(JavaClass.build).toList 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/AstModifier.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries.UnorderedQuery 4 | 5 | import com.sun.javafx.fxml.expression.VariableExpression 6 | import useful_data_structures.UsefulUnorderedDataStructure 7 | 8 | abstract class AstModifier() { 9 | def stmtMapper(statement: JavaStatement): List[JavaStatement] 10 | def exprMapper(expr: JavaExpressionOrQuery): JavaExpressionOrQuery 11 | 12 | def applyToStmt(stmt: JavaStatement): List[JavaStatement] = stmtMapper(mapOverStmt(stmt)) 13 | def applyToExpr(expr: JavaExpressionOrQuery): JavaExpressionOrQuery = exprMapper(mapOverExpr(expr)) 14 | 15 | private def apply(thing: Any): Any = thing match { 16 | case expr: JavaExpressionOrQuery => applyToExpr(expr) 17 | case stmt: JavaStatement => applyToStmt(stmt) 18 | } 19 | 20 | def mapOverStmt(stmt: JavaStatement): JavaStatement = stmt match { 21 | case VariableDeclarationStatement(name, javaType, initialValue) => 22 | VariableDeclarationStatement(name, javaType, initialValue.map(applyToExpr)) 23 | case ReturnStatement(value) => ReturnStatement(applyToExpr(value)) 24 | case ExpressionStatement(exp) => ExpressionStatement(applyToExpr(exp)) 25 | case IfStatement(cond, trueCase, falseCase) => 26 | IfStatement(applyToExpr(cond), trueCase.flatMap(applyToStmt), falseCase.flatMap(applyToStmt)) 27 | case WhileStatement(cond, action) => 28 | WhileStatement(applyToExpr(cond), action.flatMap(applyToStmt)) 29 | } 30 | 31 | def mapOverExpr(expr: JavaExpressionOrQuery): JavaExpressionOrQuery = expr match { 32 | case JavaNull => JavaNull 33 | case expr: JavaBoolLit => expr 34 | case JavaMethodCall(callee, name, args) => JavaMethodCall(applyToExpr(callee), name, args.map(applyToExpr)) 35 | case JavaFieldAccess(thing, field) => JavaFieldAccess(applyToExpr(thing), field) 36 | case JavaNewObject(className, typeArgs, args) => JavaNewObject(className, typeArgs, args.map(applyToExpr)) 37 | case JavaThis => JavaThis 38 | case expr: JavaVariable => expr 39 | case JavaIfExpression(cond, ifTrue, ifFalse) => JavaIfExpression(applyToExpr(cond), applyToExpr(ifTrue), applyToExpr(ifFalse)) 40 | case JavaLambdaExpr(args, body) => JavaLambdaExpr(args, applyToExpr(body)) 41 | case JavaUnit => JavaThis 42 | case JavaAssignmentExpression(name, local, value) => JavaAssignmentExpression(name, local, applyToExpr(value)) 43 | case JavaArrayInitializerExpr(items) => JavaArrayInitializerExpr(items.map(applyToExpr)) 44 | case expr: JavaStringLiteral => expr 45 | case JavaMath(math) => JavaMath(math.mapOverVariables(applyToExpr)) 46 | case UnorderedQueryApplication(UnorderedQuery(source, wheres, limiter, reduction)) => { 47 | val newQuery = UnorderedQuery(source, wheres.map(_.modify(this)), limiter.map(_.modify(this)), reduction.map(_.modify(this))) 48 | 49 | UnorderedQueryApplication(newQuery) 50 | } 51 | 52 | } 53 | 54 | def mapOverClass(javaClass: JavaClass): JavaClass = javaClass.modifyWithAstModifier(this) 55 | } 56 | 57 | case class VariableReplacer(map: Map[String, JavaExpressionOrQuery]) extends AstModifier { 58 | def stmtMapper(stmt: JavaStatement) = List(stmt) 59 | def exprMapper(expr: JavaExpressionOrQuery) = expr match { 60 | case JavaVariable(name) => map.getOrElse(name, JavaVariable(name)) 61 | case _ => expr 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/InternalTypeError.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | case class InternalTypeError(s: String) extends RuntimeException { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaBinaryOperator.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | sealed abstract class JavaBinaryOperator(name: String) 4 | 5 | case object JavaPlus extends JavaBinaryOperator("+") 6 | case object JavaMinus extends JavaBinaryOperator("-") 7 | case object JavaTimes extends JavaBinaryOperator("*") 8 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaClass.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_parser.JavaParserWrapper 4 | import java_transpiler.queries.{UnorderedQuery, JavaContext} 5 | import javax.management.Query 6 | 7 | import com.github.javaparser.ast.body._ 8 | import useful_data_structures.UsefulUnorderedDataStructure 9 | import scala.collection.JavaConverters._ 10 | 11 | case class JavaClass(name: String, 12 | constructor: Option[JavaConstructorDeclaration], 13 | fields: List[JavaFieldDeclaration], 14 | methods: List[JavaMethodDeclaration], 15 | innerClasses: List[JavaClass]) { 16 | def querify(): JavaClass = { 17 | val context = JavaContext(unorderedTables()) 18 | this.copy(methods = methods.map(_.querify(context))) 19 | } 20 | 21 | val toType: JavaType = JavaClassType(this.name, Nil) 22 | 23 | def queries(): Set[UnorderedQuery] = methods.flatMap(_.queries()).toSet 24 | 25 | def actualizeQueries(auxiliaryDataStructures: Map[UnorderedQuery, Option[UsefulUnorderedDataStructure]]): JavaClass = { 26 | val actualizer = QueryActualizer(auxiliaryDataStructures, this) 27 | val classWithModifiedMethods = modifyWithAstModifier(actualizer) 28 | 29 | val queryMethods = auxiliaryDataStructures.values.flatten.toList.map(_.methodCode).flatten 30 | 31 | val modificationMethods = magicMultisets.flatMap((x) => x._2.modificationMethods(x._1, auxiliaryDataStructures)) 32 | 33 | classWithModifiedMethods.copy(methods = classWithModifiedMethods.methods ++ queryMethods ++ modificationMethods, 34 | fields = fields ++ auxiliaryDataStructures.flatMap(_._2.map(_.fields).getOrElse(Nil))) 35 | } 36 | 37 | def unorderedTables(): Map[String, JavaClass] = { 38 | magicMultisets.map(x => x._1 -> x._2.itemClass) 39 | } 40 | 41 | def getMethod(name: String): Option[JavaMethodDeclaration] = { 42 | methods.find(_.name == name) 43 | } 44 | 45 | def getField(name: String): Option[JavaFieldDeclaration] = { 46 | fields.find(_.name == name) 47 | } 48 | 49 | def getInnerClass(name: String): Option[JavaClass] = innerClasses.find(_.name == name) 50 | 51 | lazy val magicMultisets: Map[String, MagicMultiset] = { 52 | val names: Set[String] = fields.flatMap((fieldDeclaration: JavaFieldDeclaration) => fieldDeclaration.javaType match { 53 | case JavaClassType(classTypeName, args) if classTypeName == "MagicMultiset" => 54 | Some(fieldDeclaration.name) 55 | case _ => None 56 | }).toSet 57 | 58 | names.map({(name) => 59 | val fieldDeclaration = getField(name).getOrElse(throw new RuntimeException(s"oh god $name")) 60 | val javaClass = getInnerClass(fieldDeclaration.javaType.asInstanceOf[JavaClassType].itemTypes.head.toScalaTypeString()) 61 | .getOrElse(throw new RuntimeException(s"I could not find an innerClass")) 62 | val supportsInsert = methodsCalledOnObject(name).contains("insert") 63 | val supportsRemove = methodsCalledOnObject(name).contains("remove") 64 | name -> MagicMultiset(javaClass, supportsInsert, supportsRemove)}).toMap 65 | } 66 | 67 | def methodsCalledOnObject(name: String): List[String] = { 68 | methods.flatMap((method: JavaMethodDeclaration) => 69 | method.body.flatMap(_.descendantExpressions).collect({ 70 | case JavaMethodCall(JavaVariable(objectName), methodName, _) if objectName == name => methodName 71 | }) 72 | ) 73 | } 74 | 75 | def modifyWithAstModifier(astModifier: AstModifier): JavaClass = { 76 | JavaClass( 77 | name, 78 | constructor.map(_.modifyWithAstModifier(astModifier)), 79 | fields.map(_.modifyWithAstModifier(astModifier)), 80 | methods.map(_.modifyWithAstModifier(astModifier)), 81 | innerClasses.map(_.modifyWithAstModifier(astModifier)) 82 | ) 83 | } 84 | } 85 | 86 | object JavaClass { 87 | def build(typeDeclaration: TypeDeclaration): JavaClass = { 88 | val fieldDeclarations = typeDeclaration 89 | .getMembers 90 | .asScala 91 | .filter(_.isInstanceOf[FieldDeclaration]) 92 | .map(_.asInstanceOf[FieldDeclaration]) 93 | 94 | val fieldDeclarationAsts = fieldDeclarations.map(JavaFieldDeclaration.build).toList 95 | 96 | val methodDeclarations = typeDeclaration 97 | .getMembers 98 | .asScala 99 | .filter(_.isInstanceOf[MethodDeclaration]) 100 | .map(_.asInstanceOf[MethodDeclaration]) 101 | 102 | val methodDeclarationAsts = methodDeclarations.map(JavaMethodDeclaration.build).toList 103 | 104 | val constructorAst = typeDeclaration 105 | .getMembers 106 | .asScala 107 | .filter(_.isInstanceOf[ConstructorDeclaration]) 108 | .map(_.asInstanceOf[ConstructorDeclaration]) 109 | .headOption 110 | .map(JavaMethodDeclaration.maybeBuildConstructor) 111 | 112 | val innerClasses = typeDeclaration 113 | .getMembers 114 | .asScala 115 | .filter(_.isInstanceOf[ClassOrInterfaceDeclaration]) 116 | .map(_.asInstanceOf[ClassOrInterfaceDeclaration]) 117 | .map(build) 118 | .toList 119 | 120 | JavaClass(typeDeclaration.getName, constructorAst, fieldDeclarationAsts, methodDeclarationAsts, innerClasses) 121 | } 122 | 123 | def parse(string: String): JavaClass = { 124 | JavaParserWrapper.parseJavaClassToAst(s"class Example { $string }") 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaExpression.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries._ 4 | 5 | 6 | import cas._ 7 | import com.github.javaparser.ast.expr._ 8 | import com.github.javaparser.ast.stmt.ExpressionStmt 9 | import scala.collection.JavaConverters._ 10 | import scala.util.Try 11 | 12 | sealed abstract class JavaExpression extends JavaExpressionOrQuery { 13 | def childrenExpressions(): List[JavaExpressionOrQuery] = this match { 14 | case JavaNull => Nil 15 | case expr: JavaBoolLit => Nil 16 | case JavaMethodCall(callee, name, args) => List(callee) ++ args 17 | case JavaFieldAccess(thing, field) => List(thing) 18 | case JavaNewObject(className, typeArgs, args) => args 19 | case JavaThis => Nil 20 | case expr: JavaVariable => Nil 21 | case JavaIfExpression(cond, ifTrue, ifFalse) => List(cond, ifTrue, ifFalse) 22 | case JavaLambdaExpr(args, body) => List(body) 23 | case JavaUnit => Nil 24 | case JavaAssignmentExpression(name, local, value) => List(value) 25 | case JavaArrayInitializerExpr(items) => items 26 | case expr: JavaStringLiteral => Nil 27 | case JavaMath(math) => math.variables.toList 28 | } 29 | 30 | def querify(c: JavaContext): JavaExpressionOrQuery = this match { 31 | case JavaNull => this 32 | case expr: JavaBoolLit => this 33 | case JavaMethodCall(callee, name, args) => querifyMethodCall(callee.querify(c), name, args.map(_.querify(c)), c) 34 | case JavaFieldAccess(thing, field) => JavaFieldAccess(thing.querify(c), field) 35 | case JavaNewObject(className, typeArgs, args) => JavaNewObject(className, typeArgs, args.map(_.querify(c))) 36 | case JavaThis => this 37 | case expr: JavaVariable => this 38 | case JavaIfExpression(cond, ifTrue, ifFalse) => JavaIfExpression(cond.querify(c), ifTrue.querify(c), ifFalse.querify(c)) 39 | case JavaLambdaExpr(args, body) => JavaLambdaExpr(args, body.querify(c)) 40 | case JavaUnit => this 41 | case JavaAssignmentExpression(name, local, value) => JavaAssignmentExpression(name, local, value.querify(c)) 42 | case JavaArrayInitializerExpr(items) => JavaArrayInitializerExpr(items.map(_.querify(c))) 43 | case expr: JavaStringLiteral => this 44 | case JavaMath(math) => JavaMathHelper.decasify(math.mapOverVariables(_.querify(c))) 45 | } 46 | 47 | private def querifyMethodCall(callee: JavaExpressionOrQuery, 48 | name: String, 49 | args: List[JavaExpressionOrQuery], 50 | context: JavaContext): JavaExpressionOrQuery = { 51 | name match { 52 | case "insert" => JavaMethodCall(callee, name, args) 53 | case "remove" => JavaMethodCall(callee, name, args) 54 | case _ => { 55 | val mbQuerifiedCallee: Option[UnorderedQuery] = callee match { 56 | case callee @ UnorderedQueryApplication(query) => Some(query) 57 | case JavaVariable(innerName) if context.unorderedMultisets.keys.toSet.contains(innerName) => { 58 | Some(UnorderedQuery.blank(callee)) 59 | } 60 | case _ => None 61 | } 62 | 63 | mbQuerifiedCallee match { 64 | case None => JavaMethodCall(callee, name, args) 65 | case Some(querifiedCallee) => querifiedCallee.applyMethod(name, args, context) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | case object JavaNull extends JavaExpression 73 | case class JavaBoolLit(boolean: Boolean) extends JavaExpression 74 | case class JavaMethodCall(callee: JavaExpressionOrQuery, methodName: String, args: List[JavaExpressionOrQuery]) extends JavaExpression 75 | case class JavaFieldAccess(thing: JavaExpressionOrQuery, field: String) extends JavaExpression 76 | case class JavaNewObject(className: String, typeArgs: List[JavaType], args: List[JavaExpressionOrQuery]) extends JavaExpression 77 | case object JavaThis extends JavaExpression 78 | case class JavaVariable(name: String) extends JavaExpression { 79 | override def freeVariables: Set[String] = Set(name) 80 | } 81 | case class JavaIfExpression(cond: JavaExpressionOrQuery, ifTrue: JavaExpressionOrQuery, ifFalse: JavaExpressionOrQuery) extends JavaExpression 82 | case class JavaLambdaExpr(args: List[(String, JavaType)], out: JavaExpressionOrQuery) extends JavaExpression { 83 | override def freeVariables: Set[String] = out.freeVariables -- args.map(_._1).toSet 84 | } 85 | case object JavaUnit extends JavaExpression 86 | case class JavaAssignmentExpression(name: String, local: Boolean, expression: JavaExpressionOrQuery) extends JavaExpression 87 | case class JavaArrayInitializerExpr(items: List[JavaExpressionOrQuery]) extends JavaExpression 88 | case class JavaStringLiteral(string: String) extends JavaExpression 89 | case class JavaMath(math: MathExp[JavaExpressionOrQuery]) extends JavaExpression 90 | 91 | object JavaExpression { 92 | def build(exp: Expression): JavaExpression = exp match { 93 | case null => ??? 94 | case exp: IntegerLiteralExpr => 95 | JavaMath(Number(exp.getValue.toInt)) 96 | case exp: AssignExpr => 97 | val (lhs, isLocal) = exp.getTarget match { 98 | case f: FieldAccessExpr => (f.getField, false) 99 | case n: NameExpr => (n.getName, true) 100 | } 101 | 102 | val mbOp = exp.getOperator match { 103 | case AssignExpr.Operator.assign => None 104 | case AssignExpr.Operator.plus => Some(BinaryExpr.Operator.plus) 105 | case AssignExpr.Operator.minus => Some(BinaryExpr.Operator.minus) 106 | case _ => ??? 107 | } 108 | 109 | val outExp = mbOp match { 110 | case None => build(exp.getValue) 111 | case Some(op) => JavaMathHelper.opToMath(op, JavaFieldAccess(JavaThis, lhs), build(exp.getValue)) 112 | } 113 | JavaAssignmentExpression(lhs, isLocal, outExp) 114 | case exp: BinaryExpr => 115 | JavaMathHelper.opToMath(exp.getOperator, build(exp.getLeft), build(exp.getRight)).asInstanceOf[JavaExpression] 116 | // case exp: MethodCallExpr => 117 | // ??? 118 | case exp: NameExpr => JavaVariable(exp.getName) 119 | case exp: FieldAccessExpr => JavaFieldAccess(build(exp.getScope), exp.getField) 120 | case exp: ThisExpr => JavaThis 121 | case exp: ObjectCreationExpr => 122 | val javaArgs = Option(exp.getArgs).map(_.asScala.toList) 123 | val args = javaArgs.getOrElse(List()) 124 | val name = exp.getType.getName 125 | val typeArgs = Option(exp.getTypeArgs).map(_.asScala.toList).getOrElse(Nil).map(JavaType.build) 126 | JavaNewObject(name, typeArgs, args.map(build)) 127 | case exp: LambdaExpr => exp.getBody match { 128 | case stmt: ExpressionStmt => 129 | val params = exp.getParameters.asScala.map(_.getId.getName -> JavaIntType).toList 130 | JavaLambdaExpr(params, build(stmt.getExpression)) 131 | case _ => 132 | throw new RuntimeException("I can't deal with non-expression contents of lambdas yet. Oh well, neither can Python.") 133 | } 134 | case exp: ArrayInitializerExpr => 135 | JavaArrayInitializerExpr(Option(exp.getValues).map(_.asScala.map(build)).getOrElse(Nil).toList) 136 | case exp: StringLiteralExpr => 137 | JavaStringLiteral(exp.getValue) 138 | case exp: BooleanLiteralExpr => 139 | JavaBoolLit(exp.getValue) 140 | case exp: MethodCallExpr => 141 | val args = Try(exp.getArgs.asScala.toList).getOrElse(Nil).map(build) 142 | val scope = Option(exp.getScope).map(build).getOrElse(JavaThis) 143 | JavaMethodCall(scope, exp.getName, args) 144 | case exp: VariableDeclarationExpr => 145 | throw new RuntimeException("this case should be handled in the JavaStatement#build method :/") 146 | case exp: NullLiteralExpr => 147 | JavaNull 148 | case exp: UnaryExpr if exp.getOperator == UnaryExpr.Operator.negative => 149 | JavaMathHelper.opToMath(BinaryExpr.Operator.minus, JavaMath(Number(0)), build(exp.getExpr)).asInstanceOf[JavaExpression] 150 | case _ => 151 | println(s"$exp : ${exp.getClass} not implemented, do it man") 152 | ??? 153 | } 154 | 155 | def parse(stuff: String): JavaExpressionOrQuery = { 156 | JavaStatement.parse(s"int x = $stuff;").asInstanceOf[VariableDeclarationStatement].initialValue.get 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaExpressionOrQuery.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries.{JavaContext, UnorderedQuery} 4 | 5 | // this class is implemented in JavaExpression and Query. 6 | abstract class JavaExpressionOrQuery { 7 | def descendantExpressions(): List[JavaExpressionOrQuery] = { 8 | this +: childrenExpressions().flatMap(_.descendantExpressions()) 9 | } 10 | 11 | def childrenExpressions(): List[JavaExpressionOrQuery] 12 | 13 | // this is overwritten by lambda, obviously 14 | def freeVariables: Set[String] = { 15 | childrenExpressions().flatMap(_.freeVariables).toSet 16 | } 17 | 18 | def querify(javaContext: JavaContext): JavaExpressionOrQuery 19 | 20 | def modify(astModifier: AstModifier): JavaExpressionOrQuery = astModifier.applyToExpr(this) 21 | 22 | def replaceVariables(map: Map[String, JavaExpressionOrQuery]): JavaExpressionOrQuery = { 23 | this.modify(VariableReplacer(map)) 24 | } 25 | 26 | def call(methodName: String, args: List[JavaExpressionOrQuery] = Nil) = JavaMethodCall(this, methodName, args) 27 | } 28 | 29 | case class UnorderedQueryApplication(unorderedQuery: UnorderedQuery) extends JavaExpressionOrQuery { 30 | override def childrenExpressions() = unorderedQuery.childrenExpressions() 31 | 32 | def querify(javaContext: JavaContext) = this 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaFieldDeclaration.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import com.github.javaparser.ast.body._ 4 | 5 | 6 | case class JavaFieldDeclaration(name: String, javaType: JavaType, initialValue: Option[JavaExpressionOrQuery] = None) { 7 | def modifyWithAstModifier(astModifier: AstModifier): JavaFieldDeclaration = { 8 | JavaFieldDeclaration(name, javaType, initialValue.map(astModifier.applyToExpr)) 9 | } 10 | } 11 | 12 | object JavaFieldDeclaration { 13 | def build(fieldDeclaration: FieldDeclaration): JavaFieldDeclaration = { 14 | val list = fieldDeclaration.getChildrenNodes 15 | val javaType = JavaType.build(fieldDeclaration.getType) 16 | 17 | list.size match { 18 | case 2 => (list.get(0), list.get(1)) match { 19 | case (_type, dec: VariableDeclarator) => 20 | JavaFieldDeclaration(dec.getId.getName, javaType, Option(dec.getInit).map(JavaExpression.build)) 21 | case _ => ??? 22 | } 23 | case _ => ??? 24 | } 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaMathHelper.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import cas._ 4 | import com.github.javaparser.ast.expr.BinaryExpr 5 | 6 | object JavaMathHelper { 7 | def opToMath(op: BinaryExpr.Operator, lhs: JavaExpressionOrQuery, rhs: JavaExpressionOrQuery): JavaExpressionOrQuery = { 8 | op match { 9 | case BinaryExpr.Operator.plus => JavaMath(casify(lhs) + casify(rhs)) 10 | case BinaryExpr.Operator.times => JavaMath(casify(lhs) * casify(rhs)) 11 | case BinaryExpr.Operator.minus => JavaMath(casify(lhs) - casify(rhs)) 12 | case BinaryExpr.Operator.divide => JavaMath(casify(lhs) / casify(rhs)) 13 | case BinaryExpr.Operator.equals => JavaMath(niceFunctions.equals(casify(lhs), casify(rhs))) 14 | case BinaryExpr.Operator.greater => JavaMath(niceFunctions.greaterThan(casify(lhs), casify(rhs))) 15 | case BinaryExpr.Operator.less => JavaMath(niceFunctions.greaterThan(casify(rhs), casify(lhs))) 16 | case BinaryExpr.Operator.greaterEquals => JavaMath(niceFunctions.greaterThanOrEquals(casify(lhs), casify(rhs))) 17 | case BinaryExpr.Operator.lessEquals => JavaMath(niceFunctions.greaterThanOrEquals(casify(rhs), casify(lhs))) 18 | case BinaryExpr.Operator.and => JavaMath(logicalAnd(casify(rhs), casify(lhs))) 19 | case BinaryExpr.Operator.binAnd => JavaMath(bitwiseAnd(casify(rhs), casify(lhs))) 20 | case BinaryExpr.Operator.or => JavaMath(logicalOr(casify(rhs), casify(lhs))) 21 | case BinaryExpr.Operator.binOr => JavaMath(bitwiseAnd(casify(rhs), casify(lhs))) 22 | case BinaryExpr.Operator.xor => JavaMath(bitwiseXor(casify(rhs), casify(lhs))) 23 | case BinaryExpr.Operator.remainder => JavaMath(modulo(casify(lhs), casify(rhs))) 24 | } 25 | } 26 | 27 | def casify(thing: JavaExpressionOrQuery): MathExp[JavaExpressionOrQuery] = thing match { 28 | case JavaMath(ast) => ast 29 | case _ => CasVariable(thing) 30 | } 31 | 32 | def decasify(thing: MathExp[JavaExpressionOrQuery]): JavaExpressionOrQuery = thing match { 33 | case CasVariable(exp) => exp 34 | case _ => JavaMath(thing) 35 | } 36 | 37 | val javaEquals = niceFunctions.equals.equals[JavaExpressionOrQuery] 38 | val javaGreaterThan = niceFunctions.greaterThan.greaterThan[JavaExpressionOrQuery] 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaMethodDeclaration.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_parser.JavaParserWrapper 4 | 5 | import cas._ 6 | import java_transpiler.queries._ 7 | import com.github.javaparser.ast.body._ 8 | import scala.collection.JavaConverters._ 9 | 10 | 11 | 12 | case class JavaMethodDeclaration(name: String, 13 | returnType: Option[JavaType], 14 | isStatic: Boolean, 15 | args: List[(String, JavaType)], 16 | body: List[JavaStatement]) { 17 | def modifyWithAstModifier(astModifier: AstModifier): JavaMethodDeclaration = { 18 | JavaMethodDeclaration(name, returnType, isStatic, args, body.flatMap(astModifier.applyToStmt)) 19 | } 20 | 21 | def querify(c: JavaContext): JavaMethodDeclaration = { 22 | this.copy(body = body.map(_.querify(c))) 23 | } 24 | 25 | def queries(): Set[UnorderedQuery] = body.flatMap(_.descendantExpressions).collect({ 26 | case UnorderedQueryApplication(q) => q 27 | }).toSet 28 | 29 | lazy val descendantExpressions: List[JavaExpressionOrQuery] = body.flatMap(_.descendantExpressions) 30 | lazy val descendantStatements: List[JavaStatement] = body.flatMap(_.descendantStatements) 31 | 32 | lazy val variables: List[VariableScopeDetails] = { 33 | val argVariables = args.map((tuple) => VariableScopeDetails(tuple._1, tuple._2, true)) 34 | val localVariables = body.flatMap(_.descendantStatements).toSet.flatMap((x: JavaStatement) => x match { 35 | case VariableDeclarationStatement(varName, javaType, _) => List(VariableScopeDetails(varName, javaType, false)) 36 | case _ => Nil 37 | }) 38 | 39 | argVariables ++ localVariables 40 | } 41 | } 42 | 43 | case class JavaConstructorDeclaration(args: List[(String, JavaType)], 44 | body: List[JavaStatement]) { 45 | def modifyWithAstModifier(astModifier: AstModifier): JavaConstructorDeclaration = { 46 | JavaConstructorDeclaration(args, body.flatMap(astModifier.applyToStmt)) 47 | } 48 | } 49 | 50 | object JavaMethodDeclaration { 51 | def build(methodDeclaration: MethodDeclaration) = { 52 | val name = methodDeclaration.getName 53 | 54 | if (name == "initialize") 55 | throw new RuntimeException("method name cannot be 'initialize' for Ruby compatibility reasons") 56 | 57 | val javaType = JavaType.buildOptionType(methodDeclaration.getType) 58 | 59 | // getModifiers returns some crazy bit flag bullshit, I should investigate further at some point. 60 | val isStatic = (methodDeclaration.getModifiers & 8) == 8 61 | 62 | val args = Option(methodDeclaration.getParameters).map(_.asScala.toList.map({ (x) => 63 | (x.getId.getName, JavaType.build(x.getType))})).getOrElse(List()) 64 | 65 | val body = JavaStatement.buildBlock(methodDeclaration.getBody) 66 | 67 | JavaMethodDeclaration(name, javaType, isStatic, args, body) 68 | } 69 | 70 | def maybeBuildConstructor(decl: ConstructorDeclaration): JavaConstructorDeclaration = { 71 | val args = Option(decl.getParameters).map(_.asScala.toList.map({ (x) => 72 | (x.getId.getName, JavaType.build(x.getType))})).getOrElse(List()) 73 | 74 | val body = JavaStatement.buildBlock(decl.getBlock) 75 | 76 | JavaConstructorDeclaration(args, body) 77 | } 78 | 79 | def parse(string: String): JavaMethodDeclaration = { 80 | JavaParserWrapper.parseJavaClassToAst(s"class Example { $string }").methods.head 81 | } 82 | 83 | def main(args: Array[String]) { 84 | val string = "int factorial() { Ant output[]; Bee output; if (x==0) output = 1; else output = factorial(x-1) * x; return output; }" 85 | println(parse(string).variables.mkString("\n")) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaStatement.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries.JavaContext 4 | 5 | import cas.Name 6 | import com.github.javaparser.ast.body.VariableDeclarator 7 | import com.github.javaparser.ast.expr._ 8 | import com.github.javaparser.ast.stmt._ 9 | import scala.collection.JavaConverters._ 10 | import scala.util.Try 11 | 12 | sealed abstract class JavaStatement { 13 | def childStatements: List[JavaStatement] = this match { 14 | case _: VariableDeclarationStatement => Nil 15 | case _: ReturnStatement => Nil 16 | case _: ExpressionStatement => Nil 17 | case IfStatement(cond, trueCase, falseCase) => trueCase ++ falseCase 18 | case WhileStatement(cond, action) => action 19 | } 20 | 21 | def childExpressions: List[JavaExpressionOrQuery] = this match { 22 | case s: VariableDeclarationStatement => s.initialValue.toList 23 | case s: ReturnStatement => List(s.value) 24 | case s: ExpressionStatement => List(s.value) 25 | case s: IfStatement => List(s.cond) 26 | case s: WhileStatement => List(s.cond) 27 | } 28 | 29 | def descendantExpressions: List[JavaExpressionOrQuery] = { 30 | this.childExpressions.flatMap(_.descendantExpressions()) ++ descendantStatements.flatMap(_.childExpressions) 31 | } 32 | 33 | def descendantStatements: List[JavaStatement] = { 34 | List(this) ++ childStatements.flatMap(_.descendantStatements) 35 | } 36 | 37 | def modify(astModifier: AstModifier): List[JavaStatement] = astModifier.applyToStmt(this) 38 | 39 | def querify(c: JavaContext): JavaStatement = this match { 40 | case stmt@VariableDeclarationStatement(_, _, initialValue) => 41 | stmt.copy(initialValue = initialValue.map(_.querify(c))) 42 | case ReturnStatement(value) => ReturnStatement(value.querify(c)) 43 | case ExpressionStatement(value) => ExpressionStatement(value.querify(c)) 44 | case IfStatement(cond, trueCase, falseCase) => 45 | IfStatement(cond.querify(c), trueCase.map(_.querify(c)), falseCase.map(_.querify(c))) 46 | case WhileStatement(cond, body) => 47 | WhileStatement(cond.querify(c), body.map(_.querify(c))) 48 | } 49 | } 50 | 51 | case class VariableDeclarationStatement(name: String, javaType: JavaType, initialValue: Option[JavaExpressionOrQuery]) extends JavaStatement 52 | case class ReturnStatement(value: JavaExpressionOrQuery) extends JavaStatement 53 | case class ExpressionStatement(value: JavaExpressionOrQuery) extends JavaStatement 54 | case class IfStatement(cond: JavaExpressionOrQuery, trueCase: List[JavaStatement], falseCase: List[JavaStatement]) extends JavaStatement 55 | case class WhileStatement(cond: JavaExpressionOrQuery, action: List[JavaStatement]) extends JavaStatement 56 | 57 | object JavaStatement { 58 | def buildBlock(blk: BlockStmt): List[JavaStatement] = { 59 | Option(blk.getStmts).map(_.asScala.toList).getOrElse(Nil).map(buildStatement) 60 | } 61 | 62 | def buildPotentiallyBlock(stmt: Statement): List[JavaStatement] = stmt match { 63 | case blk: BlockStmt => buildBlock(blk) 64 | case _ => List(buildStatement(stmt)) 65 | } 66 | 67 | def buildStatement(stmt: Statement): JavaStatement = stmt match { 68 | case s: ExpressionStmt => { 69 | s.getExpression match { 70 | case e: VariableDeclarationExpr => 71 | val name = e.getVars.get(0).getId.getName 72 | val javaType = JavaType.build(e.getType) 73 | 74 | val body = Try(e.getChildrenNodes.get(1).asInstanceOf[VariableDeclarator].getInit).toOption.flatMap(Option(_)) 75 | VariableDeclarationStatement(name, javaType, body.map(JavaExpression.build)) 76 | case e: Expression => ExpressionStatement(JavaExpression.build(e)) 77 | } 78 | } 79 | case s: IfStmt => 80 | val cond = JavaExpression.build(s.getCondition) 81 | val thenCase = JavaStatement.buildPotentiallyBlock(s.getThenStmt) 82 | val elseCase = JavaStatement.buildPotentiallyBlock(s.getElseStmt) 83 | IfStatement(cond, thenCase, elseCase) 84 | case s: ReturnStmt => ReturnStatement(JavaExpression.build(s.getExpr)) 85 | case _ => 86 | println(s"$stmt : ${stmt.getClass} not implemented, you should do it") 87 | ??? 88 | } 89 | 90 | def parse(stuff: String): JavaStatement = JavaMethodDeclaration.parse(s"void f() { $stuff }").body.head 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/JavaType.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_parser.JavaParserWrapper 4 | 5 | import com.github.javaparser.ast.`type`._ 6 | 7 | import scala.collection.JavaConverters._ 8 | import scala.util.Try 9 | 10 | sealed abstract class JavaType { 11 | def toScalaTypeString(): String 12 | } 13 | 14 | case object JavaIntType extends JavaType { 15 | lazy val toScalaTypeString = "Int" 16 | } 17 | 18 | case object JavaBoolType extends JavaType { 19 | lazy val toScalaTypeString = "Boolean" 20 | } 21 | 22 | case class JavaArrayType(itemType: JavaType) extends JavaType { 23 | lazy val toScalaTypeString = s"Array[${itemType.toScalaTypeString()}]" 24 | } 25 | 26 | case class JavaClassType(name: String, itemTypes: List[JavaType]) extends JavaType { 27 | lazy val toScalaTypeString = itemTypes match { 28 | case Nil => name 29 | case _ => s"$name[${itemTypes.map(_.toScalaTypeString()).mkString(", ")}]" 30 | } 31 | } 32 | 33 | case class JavaFunctionType(argTypes: List[JavaType], returnType: Option[JavaType]) extends JavaType { 34 | lazy val toScalaTypeString = { 35 | val typeString = returnType.map(_.toScalaTypeString()).getOrElse("Unit") 36 | s"(${argTypes.mkString(", ")}) => $typeString" 37 | } 38 | } 39 | 40 | object JavaType { 41 | def build(thing: Type): JavaType = { 42 | thing match { 43 | case x: PrimitiveType => 44 | x.getType match { 45 | case PrimitiveType.Primitive.Int => JavaIntType 46 | case PrimitiveType.Primitive.Boolean => JavaBoolType 47 | case _ => 48 | ??? 49 | } 50 | case x: ClassOrInterfaceType => 51 | val typeArgs = Try(x.getTypeArgs.asScala.map(JavaType.build).toList).recover({ 52 | case _: NullPointerException => Nil 53 | }).get 54 | JavaClassType(x.getName, typeArgs) 55 | case x: ReferenceType => 56 | // previously JavaArrayType(_) 57 | build(x.getType) 58 | case _ => 59 | ??? 60 | } 61 | } 62 | 63 | def buildOptionType(thing: Type): Option[JavaType] = { 64 | thing match { 65 | case _: VoidType => None 66 | case _ => Some(build(thing)) 67 | } 68 | 69 | } 70 | 71 | def parse(string: String): JavaType = { 72 | JavaParserWrapper.parseJavaClassToAst(s"class Example { $string x; }").fields.head.javaType 73 | } 74 | 75 | def main(args: Array[String]) { 76 | val parsedType = parse("Blah") 77 | println(parsedType) 78 | println(parsedType.toScalaTypeString()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/MagicMultiset.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries.UnorderedQuery 4 | 5 | import useful_data_structures.UsefulUnorderedDataStructure 6 | 7 | case class MagicMultiset(itemClass: JavaClass, supportsInsert: Boolean, supportsRemove: Boolean) { 8 | val itemType = itemClass.toType 9 | 10 | def modificationMethods(name: String, 11 | auxiliaryDataStructures: Map[UnorderedQuery, Option[UsefulUnorderedDataStructure]]): List[JavaMethodDeclaration] = { 12 | 13 | val deleteMethod = if (supportsRemove) { 14 | val removeBody = auxiliaryDataStructures.collect({ 15 | case (query, Some(dataStructure)) => query.source match { 16 | case JavaVariable(querySourceName) if name == querySourceName => 17 | dataStructure.onRemove.get 18 | } 19 | }).flatten.toList 20 | 21 | val actualRemoval = JavaStatement.parse(s"this.$name.remove(item);") 22 | 23 | Some(JavaMethodDeclaration( 24 | s"removeFrom_$name", 25 | Some(JavaIntType), 26 | false, 27 | List("item" -> itemType), 28 | removeBody :+ actualRemoval)) 29 | 30 | } else 31 | None 32 | 33 | val insertMethod = if (supportsInsert) { 34 | val insertBody = auxiliaryDataStructures.collect({ 35 | case (query, Some(dataStructure)) => query.source match { 36 | case JavaVariable(querySourceName) if name == querySourceName => 37 | dataStructure.onInsert.get 38 | } 39 | }).flatten.toList 40 | 41 | val actualInsertion = ExpressionStatement( 42 | JavaMethodCall( 43 | JavaFieldAccess(JavaThis, name), 44 | "insert", 45 | itemClass.fields.map((x) => JavaVariable(x.name)))) 46 | 47 | val insertArgs = itemClass.fields.map((x) => x.name -> x.javaType) 48 | 49 | Some(JavaMethodDeclaration(s"insertInto_$name", Some(itemType), false, insertArgs, insertBody :+ actualInsertion)) 50 | } else 51 | None 52 | 53 | 54 | List(deleteMethod, insertMethod).flatten 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/QueryActualizer.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | import java_transpiler.queries.UnorderedQuery 4 | import useful_data_structures.UsefulUnorderedDataStructure 5 | 6 | case class QueryActualizer(auxiliaryStructures: Map[UnorderedQuery, Option[UsefulUnorderedDataStructure]], javaClass: JavaClass) extends AstModifier { 7 | def stmtMapper(stmt: JavaStatement) = List(stmt) 8 | def exprMapper(expr: JavaExpressionOrQuery) = expr match { 9 | case UnorderedQueryApplication(query) => 10 | auxiliaryStructures.get(query).flatten match { 11 | case Some(structure) => structure.queryCode 12 | case None => query.toTrivialJavaExpression 13 | } 14 | case JavaMethodCall(JavaVariable(callee), "insert", args) => 15 | if (javaClass.magicMultisets.keys.toSet.contains(callee)) 16 | JavaMethodCall(JavaThis, s"insertInto_$callee", args) 17 | else 18 | expr 19 | case JavaMethodCall(JavaVariable(callee), "remove", args) => 20 | if (javaClass.magicMultisets.keys.toSet.contains(callee)) 21 | JavaMethodCall(JavaThis, s"removeFrom_$callee", args) 22 | else 23 | expr 24 | case _ => expr 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/VariableScopeDetails.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler 2 | 3 | case class VariableScopeDetails(name: String, javaType: JavaType, isArg: Boolean) 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/JavaContext.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler._ 4 | 5 | case class JavaContext(unorderedMultisets: Map[String, JavaClass]) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/LimitByClause.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler._ 4 | 5 | import ast_renderers.RubyOutputter 6 | import cas.MathExp 7 | 8 | case class LimitByClause(nodeVariableName: String, 9 | orderingFunction: JavaExpressionOrQuery, 10 | limitingFunction: JavaExpressionOrQuery) { 11 | def childrenExpressions() = List(orderingFunction, limitingFunction) 12 | 13 | override def toString: String = { 14 | s"LimitBy($nodeVariableName, ${RubyOutputter.outputExpression(orderingFunction)}, ${RubyOutputter.outputExpression(limitingFunction)})" 15 | } 16 | 17 | lazy val freeVariables: Set[String] = childrenExpressions().flatMap(_.freeVariables).toSet - nodeVariableName 18 | 19 | val orderingLambda = JavaLambdaExpr(List(nodeVariableName -> JavaIntType), orderingFunction) 20 | 21 | def modify(modifier: AstModifier): LimitByClause = 22 | LimitByClause(nodeVariableName, modifier.applyToExpr(orderingFunction), modifier.applyToExpr(limitingFunction)) 23 | 24 | val constantSizeLimitBy = ConstantSizeLimitBy.build(this) 25 | 26 | def applyOrderingFunction(thing: JavaExpressionOrQuery): JavaExpressionOrQuery = { 27 | orderingFunction.replaceVariables(Map(nodeVariableName -> thing)) 28 | } 29 | } 30 | 31 | object LimitByClause { 32 | def build(ordering: JavaExpressionOrQuery, limiting: JavaExpressionOrQuery, context: JavaContext) = { 33 | ordering match { 34 | case JavaLambdaExpr(List(name), body) => 35 | LimitByClause(name._1, body, limiting) 36 | case _ => throw new RuntimeException(s"this is not valid for a limit by: $ordering") 37 | } 38 | } 39 | } 40 | 41 | abstract class LimitByClauseNiceness 42 | 43 | case class ConstantSizeLimitBy(size: Int) extends LimitByClauseNiceness 44 | 45 | object ConstantSizeLimitBy { 46 | // This should work even if the number isn't actually constant, but it's constant over the time period we care about 47 | def build(limitByClause: LimitByClause): Option[ConstantSizeLimitBy] = limitByClause.limitingFunction match { 48 | case JavaMath(mathExp: MathExp[JavaExpressionOrQuery]) => mathExp match { 49 | case n: cas.Number[_] => Some(ConstantSizeLimitBy(n.value)) 50 | } 51 | case _ => None 52 | } 53 | } 54 | 55 | object PurelyNodeBasedOrderingFunction extends LimitByClauseNiceness { 56 | def build(limitByClause: LimitByClause): Option[LimitByClauseNiceness] = { 57 | if (limitByClause.freeVariables.subsetOf(Set(limitByClause.nodeVariableName))) 58 | Some(this) 59 | else 60 | None 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/OrderByClause.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler.JavaExpressionOrQuery 4 | 5 | import cas.MathExp 6 | 7 | class OrderByClause { 8 | 9 | } 10 | 11 | case class GoodOrderByClause(nodeExpr: MathExp[JavaExpressionOrQuery]) extends OrderByClause 12 | case class BadOrderByClause(expr: MathExp[JavaExpressionOrQuery]) extends OrderByClause 13 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/Reduction.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler._ 4 | 5 | import ast_renderers.RubyOutputter 6 | import cas.{CasVariable, MathExp} 7 | import helpers.VariableNameGenerator 8 | 9 | case class Reduction(start: JavaExpressionOrQuery, 10 | mapper: Mapper, 11 | reducer: Reducer) { 12 | lazy val freeVariables: Set[String] = start.freeVariables ++ mapper.freeVariables ++ reducer.freeVariables 13 | 14 | override def toString: String = { 15 | s"Reduction[${RubyOutputter.outputExpression(start)}, $mapper, $reducer]" 16 | } 17 | 18 | def modify(modifier: AstModifier): Reduction = { 19 | Reduction(modifier.applyToExpr(start), 20 | mapper.copy(body = modifier.applyToExpr(mapper.body)), 21 | reducer.copy(body = modifier.applyToExpr(reducer.body))) 22 | } 23 | 24 | lazy val isInvertible: Boolean = invertedReducer.isDefined 25 | 26 | lazy val invertedReducer: Option[Reducer] = reducer.invert 27 | } 28 | 29 | object Reduction { 30 | def build(start: JavaExpressionOrQuery, 31 | map: JavaExpressionOrQuery, 32 | reducer: JavaExpressionOrQuery, 33 | context: JavaContext) = { 34 | Reduction(start, buildMapper(map), buildReducer(reducer)) 35 | } 36 | 37 | def buildMapper(map: JavaExpressionOrQuery) = { 38 | map match { 39 | case JavaLambdaExpr(List(arg1), body) => Mapper(arg1._1, body) 40 | case _ => ??? 41 | } 42 | } 43 | 44 | def buildReducer(map: JavaExpressionOrQuery) = { 45 | map match { 46 | case JavaLambdaExpr(List(arg1, arg2), body) => Reducer(arg1._1, arg2._1, body) 47 | case _ => ??? 48 | } 49 | } 50 | } 51 | 52 | case class Mapper(arg: String, body: JavaExpressionOrQuery) { 53 | lazy val freeVariables = body.freeVariables - arg 54 | 55 | def useStr(name: String) = body.replaceVariables(Map(arg -> JavaVariable(name))) 56 | 57 | override def toString: String = s"($arg -> ${RubyOutputter.outputExpression(body)})" 58 | 59 | def asJavaLambda: JavaLambdaExpr = JavaLambdaExpr(List(arg -> JavaIntType), body) 60 | } 61 | 62 | case class Reducer(arg1: String, arg2: String, body: JavaExpressionOrQuery) { 63 | lazy val freeVariables = body.freeVariables - arg1 - arg2 64 | 65 | override def toString: String = s"($arg1, $arg2 -> ${RubyOutputter.outputExpression(body)})" 66 | 67 | def asJavaLambda: JavaLambdaExpr = JavaLambdaExpr(List(arg1 -> JavaIntType, arg2 -> JavaIntType), body) 68 | 69 | def useBody(map: Map[String, JavaExpressionOrQuery]): JavaExpressionOrQuery = body.replaceVariables(map) 70 | 71 | def invert: Option[Reducer] = { 72 | val otherSide = JavaVariable(VariableNameGenerator.getRandomName()) 73 | 74 | body match { 75 | case JavaMath(ast) => 76 | (ast - JavaMathHelper.casify(otherSide)).solve(JavaVariable(arg1)) match { 77 | case Some(solution) => Some(Reducer(arg2, otherSide.name, JavaMathHelper.decasify(solution))) 78 | case None => None 79 | } 80 | case _ => None 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/UnorderedQuery.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler._ 4 | 5 | import ast_renderers.RubyOutputter 6 | import cas.{Number, Name} 7 | 8 | case class UnorderedQuery( 9 | source: JavaExpressionOrQuery, 10 | whereClauses: Set[WhereClause], 11 | mbLimiter: Option[LimitByClause], 12 | mbReduction: Option[Reduction]) { 13 | 14 | lazy val targetIsMagic: Boolean = source.isInstanceOf[JavaVariable] 15 | 16 | val parameters = whereClauses.flatMap(_.freeVariables)++ mbLimiter.map(_.freeVariables) ++ mbReduction.map(_.freeVariables) 17 | 18 | def childrenExpressions(): List[JavaExpressionOrQuery] = { 19 | whereClauses.flatMap(_.childrenExpressions()).toList 20 | } 21 | 22 | val allowedMethods: Map[String, Int] = Map("head" -> 0) 23 | 24 | def applyMethod(methodName: String, args: List[JavaExpressionOrQuery], context: JavaContext): JavaExpressionOrQuery = { 25 | (methodName, args) match { 26 | case ("filter", List(arg)) => this.filter(arg, context) 27 | case ("limitBy", List(orderingFunction, limitingNumberFunction)) => 28 | this.limitBy(orderingFunction, limitingNumberFunction, context) 29 | case ("reduce", List(start, map, reducer)) => 30 | this.reduce(start, map, reducer, context) 31 | case ("sum", List(map)) => 32 | this.sum(map, context) 33 | case ("count", List()) => 34 | this.count(context) 35 | case (_, _) => 36 | if (allowedMethods.contains(methodName)) 37 | if (allowedMethods(methodName) == args.length) 38 | JavaMethodCall(UnorderedQueryApplication(this),methodName, args) 39 | else 40 | throw new RuntimeException(s"call $methodName to $source has wrong number of args: ${args.length} instead of ${allowedMethods(methodName)}") 41 | else 42 | throw new RuntimeException(s"there is no method called $methodName on $source") 43 | } 44 | } 45 | 46 | def filter(arg: JavaExpressionOrQuery, context: JavaContext): JavaExpressionOrQuery = { 47 | val thisClause = WhereClause.build(arg).toSet 48 | 49 | this match { 50 | case UnorderedQuery(_, _, None, None) => 51 | UnorderedQueryApplication(UnorderedQuery(source, whereClauses ++ thisClause, None, None)) 52 | case _ => UnorderedQueryApplication( 53 | UnorderedQuery(UnorderedQueryApplication(this), thisClause, None, None)) 54 | } 55 | } 56 | 57 | def limitBy(ordering: JavaExpressionOrQuery, limiting: JavaExpressionOrQuery, context: JavaContext): JavaExpressionOrQuery = { 58 | val thisClause = Some(LimitByClause.build(ordering, limiting, context)) 59 | 60 | this match { 61 | case UnorderedQuery(_, _, None, None) => 62 | UnorderedQueryApplication( 63 | UnorderedQuery(source, whereClauses, thisClause, None)) 64 | case _ => UnorderedQueryApplication( 65 | UnorderedQuery(UnorderedQueryApplication(this), Set(), thisClause, None)) 66 | } 67 | } 68 | 69 | def reduce(start: JavaExpressionOrQuery, map: JavaExpressionOrQuery, reducer: JavaExpressionOrQuery, context: JavaContext) = { 70 | this match { 71 | case UnorderedQuery(_, _, _, None) => 72 | UnorderedQueryApplication( 73 | UnorderedQuery(source, whereClauses, mbLimiter, Some(Reduction.build(start, map, reducer, context)))) 74 | case _ => ??? 75 | } 76 | } 77 | 78 | def sum(map: JavaExpressionOrQuery, context: JavaContext) = { 79 | val sumOnNumber = JavaLambdaExpr(List("x" -> JavaIntType, "y" -> JavaIntType), 80 | JavaExpression.parse("x + y")) 81 | 82 | this match { 83 | case UnorderedQuery(_, _, _, None) => 84 | UnorderedQueryApplication( 85 | UnorderedQuery(source, whereClauses, mbLimiter, Some( 86 | Reduction.build(JavaMath(Number(0)), map, sumOnNumber, context)))) 87 | } 88 | } 89 | 90 | def count(context: JavaContext) = { 91 | val constOneOnNumber = JavaLambdaExpr(List("x" -> JavaIntType), JavaMath(Number(1))) 92 | val sumOnNumber = JavaLambdaExpr(List("x" -> JavaIntType, "y" -> JavaIntType), 93 | JavaExpression.parse("x + y")) 94 | 95 | this match { 96 | case UnorderedQuery(_, _, _, None) => 97 | UnorderedQueryApplication( 98 | UnorderedQuery(source, whereClauses, mbLimiter, Some( 99 | Reduction.build(JavaMath(Number(0)), constOneOnNumber, sumOnNumber, context)))) 100 | } 101 | } 102 | 103 | // def head(context: JavaContext) = this match { 104 | // case UnorderedQuery(_, _, None, None) => 105 | // UnorderedQueryApplication( 106 | // UnorderedQuery(source, whereClauses, Some(LimitByClause.build(JavaLambdaExpr(List"x", JavaUnit), JavaMath(Number(1)), context)), None)) 107 | // } 108 | 109 | def toTrivialJavaExpression: JavaExpressionOrQuery = { 110 | val afterWhere = whereClauses.foldLeft(source) { (x: JavaExpressionOrQuery, y: WhereClause) => 111 | JavaMethodCall(x, "select", List(y.toJavaLambdaExpression)) 112 | } 113 | 114 | val afterLimit = mbLimiter match { 115 | case Some(limiter) => { 116 | JavaMethodCall( 117 | JavaMethodCall(afterWhere, "sort_by", List(limiter.orderingLambda)), 118 | "take", 119 | List(limiter.limitingFunction)) 120 | } 121 | case None => afterWhere 122 | } 123 | 124 | mbReduction match { 125 | case Some(reduction) => { 126 | JavaMethodCall( 127 | JavaMethodCall(afterLimit, "map", List(reduction.mapper.asJavaLambda)), 128 | "inject", 129 | List(reduction.start, reduction.reducer.asJavaLambda) 130 | ) 131 | } 132 | case None => afterLimit 133 | } 134 | } 135 | 136 | lazy val trickyWhereClauses = { 137 | whereClauses.filter((x) => ! x.isConstant && ! x.isEqualitySeparable ) 138 | } 139 | } 140 | 141 | object UnorderedQuery { 142 | def blank(source: JavaExpressionOrQuery) = UnorderedQuery(source, Set(), None, None) 143 | } 144 | -------------------------------------------------------------------------------- /src/main/scala/java_transpiler/queries/WhereClause.scala: -------------------------------------------------------------------------------- 1 | package java_transpiler.queries 2 | 3 | import java_transpiler._ 4 | import cas._ 5 | 6 | import scala.collection.generic.SeqFactory 7 | 8 | case class WhereClause( 9 | nodeVariableName: String, 10 | body: JavaExpressionOrQuery) { 11 | 12 | val constantWhereClause = ConstantWhereClause.build(this) 13 | val isConstant = constantWhereClause.isDefined 14 | 15 | val separableInequalityWhereClause = SeparableInequalityWhereClause.build(this) 16 | val separableEqualityWhereClause = SeparableEqualityWhereClause.build(this) 17 | 18 | val isSeparable = separableInequalityWhereClause.isDefined || separableEqualityWhereClause.isDefined 19 | val isEqualitySeparable = separableEqualityWhereClause.isDefined 20 | 21 | // just checking I haven't screwed up... 22 | assert(isSeparable || ! isConstant, s"a where clause ($this) is allegedly not separable but constant") 23 | 24 | def childrenExpressions(): List[JavaExpressionOrQuery] = { 25 | List(body) 26 | } 27 | 28 | lazy val freeVariables = body.freeVariables - nodeVariableName 29 | 30 | def modify(astModifier: AstModifier): WhereClause = { 31 | WhereClause(nodeVariableName, body.modify(astModifier)) 32 | } 33 | 34 | def replaceTarget(newTarget: String): WhereClause = { 35 | val map = Map(nodeVariableName -> JavaVariable(newTarget)) 36 | WhereClause(newTarget, body.replaceVariables(map)) 37 | } 38 | 39 | def toJavaLambdaExpression = JavaLambdaExpr(List(nodeVariableName -> JavaIntType), body) 40 | } 41 | 42 | object WhereClause { 43 | def build(predicate: JavaExpressionOrQuery): List[WhereClause] = { 44 | predicate match { 45 | case JavaLambdaExpr(args, body) => 46 | assert(args.length == 1, s"where clause $predicate") 47 | buildBody(body).map(WhereClause(args.head._1, _)) 48 | case _ => throw new InternalTypeError(s"where clause got the condition $predicate, which isn't a lambda") 49 | } 50 | } 51 | 52 | def buildBody(body: JavaExpressionOrQuery): List[JavaExpressionOrQuery] = body match { 53 | case JavaMath(x : BinaryOperatorApplication[JavaExpressionOrQuery]) if x.operator == logicalAnd.operator() => 54 | buildBody(JavaMathHelper.decasify(x.lhs)) ++ buildBody(JavaMathHelper.decasify(x.rhs)) 55 | case x => List(x) 56 | } 57 | } 58 | 59 | abstract class WhereClauseNiceness 60 | 61 | 62 | class ConstantWhereClause extends WhereClauseNiceness 63 | 64 | object ConstantWhereClause { 65 | def build(whereClause: WhereClause): Option[ConstantWhereClause] = { 66 | if (whereClause.freeVariables.subsetOf(Set(whereClause.nodeVariableName))) 67 | Some(new ConstantWhereClause) 68 | else 69 | None 70 | } 71 | } 72 | 73 | case object SeparableInequalityWhereClause { 74 | def build(whereClause: WhereClause): Option[SeparableInequalityWhereClause] = { 75 | whereClause.body match { 76 | case JavaMath(CasFunctionApplication(op, List(lhs, rhs))) if op.toString == ">" => 77 | if (JavaMath(lhs).freeVariables - whereClause.nodeVariableName == Set()) 78 | Some(SeparableInequalityWhereClause(JavaMathHelper.decasify(lhs), JavaMathHelper.decasify(rhs))) 79 | else if (JavaMath(rhs).freeVariables - whereClause.nodeVariableName == Set()) 80 | Some(SeparableInequalityWhereClause(JavaMathHelper.decasify(rhs), JavaMathHelper.decasify(lhs))) 81 | else 82 | None 83 | case JavaMath(CasFunctionApplication(op, List(lhs, rhs))) if op.toString == "<" => 84 | build( 85 | WhereClause( 86 | whereClause.nodeVariableName, 87 | JavaMath(CasFunctionApplication(JavaMathHelper.javaGreaterThan, List(rhs, lhs))))) 88 | case _ => None 89 | } 90 | } 91 | } 92 | 93 | case class SeparableInequalityWhereClause( 94 | nodeFunction: JavaExpressionOrQuery, 95 | paramFunction: JavaExpressionOrQuery) extends WhereClauseNiceness 96 | 97 | case object SeparableEqualityWhereClause { 98 | def build(whereClause: WhereClause): Option[SeparableEqualityWhereClause] = whereClause.body match { 99 | case JavaMath(CasFunctionApplication(op, List(lhs, rhs))) if op.toString == "==" => 100 | if (JavaMath(lhs).freeVariables - whereClause.nodeVariableName == Set()) 101 | Some(SeparableEqualityWhereClause(JavaMathHelper.decasify(lhs), JavaMathHelper.decasify(rhs))) 102 | else if (JavaMath(rhs).freeVariables - whereClause.nodeVariableName == Set()) 103 | Some(SeparableEqualityWhereClause(JavaMathHelper.decasify(rhs), JavaMathHelper.decasify(lhs))) 104 | else 105 | None 106 | case _ => None 107 | } 108 | } 109 | 110 | case class SeparableEqualityWhereClause( 111 | nodeFunction: JavaExpressionOrQuery, 112 | paramFunction: JavaExpressionOrQuery) extends WhereClauseNiceness 113 | 114 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/JavaAbbreviations.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures 2 | 3 | import java_transpiler._ 4 | 5 | import cas.{niceFunctions, Number} 6 | 7 | object JavaAbbreviations { 8 | def jif(condition: JavaExpressionOrQuery, 9 | trueCase: List[JavaStatement], 10 | falseCase: List[JavaStatement] = Nil): JavaStatement = { 11 | IfStatement(condition, trueCase, falseCase) 12 | } 13 | 14 | def num(int: Int): JavaExpressionOrQuery = JavaMath(Number(int)) 15 | 16 | def javaEquals(lhs: JavaExpressionOrQuery, rhs: JavaExpressionOrQuery): JavaExpressionOrQuery = 17 | JavaMath(niceFunctions.equals(JavaMathHelper.casify(lhs), JavaMathHelper.casify(rhs))) 18 | 19 | def gt(lhs: JavaExpressionOrQuery, rhs: JavaExpressionOrQuery): JavaExpressionOrQuery = 20 | JavaMath(niceFunctions.greaterThan(JavaMathHelper.casify(lhs), JavaMathHelper.casify(rhs))) 21 | 22 | def lt(lhs: JavaExpressionOrQuery, rhs: JavaExpressionOrQuery): JavaExpressionOrQuery = 23 | JavaMath(niceFunctions.greaterThan(JavaMathHelper.casify(rhs), JavaMathHelper.casify(lhs))) 24 | 25 | def jv(string: String) = JavaVariable(string) 26 | // def call(thing: JavaExpressionOrQuery, methodName: String, args: List[JavaExpressionOrQuery]) = 27 | // JavaMethodCall(thing, methodName, args) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/UnorderedDataStructureLibrary.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures 2 | 3 | import java_transpiler._ 4 | import java_transpiler.queries._ 5 | 6 | import big_o.BigO 7 | import useful_data_structures.data_structure_library._ 8 | 9 | object UnorderedDataStructureLibrary { 10 | val helpfulStructures: List[UsefulUnorderedDataStructureFactory] = List( 11 | ConstantSizeHeapFactory, 12 | MonoidMemoizerFactory, 13 | GroupMemoizerFactory 14 | ) 15 | 16 | def getBestStructureForClass(query: UnorderedQuery, javaClass: JavaClass): Option[UsefulUnorderedDataStructure] = { 17 | // obviously this is hella dangerous 18 | val multiset = javaClass.magicMultisets(query.source.asInstanceOf[JavaVariable].name) 19 | getBestStructure(query, multiset.supportsInsert, multiset.supportsRemove) 20 | } 21 | 22 | def getBestStructure(query: UnorderedQuery, 23 | requiresInsert: Boolean, 24 | requiresRemove: Boolean): Option[UsefulUnorderedDataStructure] = { 25 | helpfulStructures 26 | .flatMap { _.tryToCreate(query) } 27 | .filter(_.insertionFragment.isDefined || ! requiresInsert) 28 | .filter(_.removalFragment.isDefined || ! requiresRemove) 29 | .sortBy(_.asymptoticQueryTime) 30 | .headOption 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/UsefulDataStructureHelper.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures 2 | 3 | import java_transpiler.queries.WhereClause 4 | import java_transpiler.{IfStatement, JavaStatement, JavaExpressionOrQuery} 5 | 6 | object UsefulDataStructureHelper { 7 | def wrapInIfs(clauses: List[JavaExpressionOrQuery], action: List[JavaStatement]) = { 8 | clauses.foldRight(action) { (condition: JavaExpressionOrQuery, statement: List[JavaStatement]) => 9 | List(IfStatement(condition, statement, Nil)).asInstanceOf[List[JavaStatement]] 10 | } 11 | } 12 | 13 | def wrapInWheres(whereClauses: Set[WhereClause], statements: List[JavaStatement]): List[JavaStatement] = { 14 | whereClauses.size match { 15 | case 0 => statements 16 | case _ => { 17 | val clauses: List[JavaExpressionOrQuery] = whereClauses.toList.map({ (x) => 18 | x.replaceTarget("item").body 19 | }) 20 | 21 | UsefulDataStructureHelper.wrapInIfs(clauses, statements) 22 | } 23 | } 24 | } 25 | 26 | def filterAndWrapInWheres(whereClauses: Set[WhereClause], statements: List[JavaStatement]): List[JavaStatement] = { 27 | wrapInWheres(whereClauses.filter((x) => x.isConstant), statements) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/UsefulUnorderedDataStructure.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures 2 | 3 | import java_transpiler._ 4 | import java_transpiler.queries._ 5 | 6 | import big_o._ 7 | 8 | abstract class UsefulUnorderedDataStructureFactory { 9 | def tryToCreate(query: UnorderedQuery): Option[UsefulUnorderedDataStructure] 10 | } 11 | 12 | abstract class UsefulUnorderedDataStructure(query: UnorderedQuery) { 13 | def asymptoticQueryTime: BigO 14 | 15 | def insertionFragment: Option[List[JavaStatement]] 16 | def removalFragment: Option[List[JavaStatement]] 17 | 18 | def queryCode: JavaExpressionOrQuery 19 | def methodCode: Option[JavaMethodDeclaration] 20 | 21 | protected def item = JavaVariable("item") 22 | 23 | def onInsert: Option[List[JavaStatement]] = insertionFragment.map { (fragment) => 24 | UsefulDataStructureHelper.filterAndWrapInWheres(query.whereClauses, fragment) 25 | } 26 | 27 | def onRemove: Option[List[JavaStatement]] = removalFragment.map { (fragment) => 28 | UsefulDataStructureHelper.filterAndWrapInWheres(query.whereClauses, fragment) 29 | } 30 | 31 | def fields: List[JavaFieldDeclaration] = { 32 | fieldFragments.map { (decl) => 33 | JavaFieldDeclaration( 34 | decl.name, 35 | wrapType(separableEqualsWhereClauses.length, decl.javaType), 36 | decl.initialValue.map(wrapInDeclarations(separableEqualsWhereClauses, _))) 37 | } 38 | } 39 | 40 | private def wrapInIndexingCalls(clauses: List[SeparableEqualityWhereClause], 41 | finalTarget: JavaExpressionOrQuery): JavaExpressionOrQuery = { 42 | clauses match { 43 | case Nil => finalTarget 44 | case clause :: otherClauses => 45 | JavaMethodCall(wrapInIndexingCalls(otherClauses, finalTarget), "[]", List(clause.paramFunction)) 46 | } 47 | } 48 | 49 | private def wrapInDeclarations(clauses: List[SeparableEqualityWhereClause], 50 | finalTarget: JavaExpressionOrQuery): JavaExpressionOrQuery = { 51 | clauses match { 52 | case Nil => finalTarget 53 | case clause :: otherClauses => 54 | // the types here are wrong 55 | JavaNewObject("Hash", List(JavaIntType), List(JavaLambdaExpr(Nil, wrapInDeclarations(otherClauses, finalTarget)))) 56 | } 57 | } 58 | 59 | private def wrapType(n: Int, javaType: JavaType): JavaType = n match { 60 | case 0 => javaType 61 | case _ => JavaClassType("Hash", List(JavaIntType, wrapType(n -1, javaType))) 62 | } 63 | 64 | def fieldFragments: List[JavaFieldDeclaration] 65 | 66 | lazy val separableEqualsWhereClauses: List[SeparableEqualityWhereClause] = { 67 | val clauses = query.whereClauses.filter((x) => x.isEqualitySeparable && ! x.isConstant) 68 | clauses.map(_.separableEqualityWhereClause.get).toList 69 | } 70 | 71 | def getField(fieldName: String): JavaExpressionOrQuery = { 72 | wrapInIndexingCalls(separableEqualsWhereClauses, JavaFieldAccess(JavaThis, fieldName)) 73 | } 74 | 75 | def setField(fieldName: String, value: JavaExpressionOrQuery): JavaStatement = separableEqualsWhereClauses.length match { 76 | case 0 => 77 | ExpressionStatement(JavaAssignmentExpression(fieldName, false, value)) 78 | case _ => 79 | val hashMap = wrapInIndexingCalls(separableEqualsWhereClauses.tail, JavaFieldAccess(JavaThis, fieldName)) 80 | ExpressionStatement(JavaMethodCall(hashMap, "[]=", List(separableEqualsWhereClauses.head.paramFunction, value))) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/data_structure_library/ConstantSizeHeapFactory.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures.data_structure_library 2 | 3 | import java_transpiler._ 4 | import java_transpiler.queries.{WhereClause, UnorderedQuery} 5 | 6 | import big_o.{Constant, BigO} 7 | import helpers.VariableNameGenerator 8 | import useful_data_structures._ 9 | 10 | import scala.collection.mutable 11 | 12 | object ConstantSizeHeapFactory extends UsefulUnorderedDataStructureFactory { 13 | case class ConstantSizeHeap(query: UnorderedQuery) extends UsefulUnorderedDataStructure(query) { 14 | import JavaAbbreviations._ 15 | 16 | val asymptoticQueryTime = Constant 17 | 18 | val limiter = query.mbLimiter.get 19 | val size = limiter.constantSizeLimitBy.get.size 20 | 21 | val fieldName = VariableNameGenerator.getVariableName() 22 | val field = getField(fieldName) 23 | 24 | lazy val insertionFragment: Option[List[JavaStatement]] = { 25 | val tempVarName = VariableNameGenerator.getVariableName() 26 | 27 | val precalculate = ExpressionStatement( 28 | JavaAssignmentExpression(tempVarName, true, limiter.applyOrderingFunction(item)) 29 | ) 30 | 31 | val sizeCondition = javaEquals(field.call("length"), num(size)) 32 | 33 | val condition = gt(field.call("min"), jv(tempVarName)) 34 | 35 | val pop = ExpressionStatement(field.call("popMin")) 36 | 37 | val push = ExpressionStatement(field.call("push", List(JavaArrayInitializerExpr(List(jv(tempVarName), item))))) 38 | 39 | val code = jif( 40 | condition, 41 | List( 42 | jif(sizeCondition, // if heap.length < maxHeapSize 43 | List(pop)), 44 | push 45 | ) 46 | ) 47 | 48 | Some(List(precalculate, code)) 49 | } 50 | 51 | def removalFragment: Option[List[JavaStatement]] = None 52 | 53 | def fieldFragments = List( 54 | JavaFieldDeclaration(fieldName, 55 | JavaClassType("MinHeap", List()), 56 | Some(JavaNewObject("MinHeap", Nil, Nil))) 57 | ) 58 | 59 | def queryCode = field.call("keys") 60 | 61 | def methodCode = None 62 | } 63 | 64 | def tryToCreate(query: UnorderedQuery): Option[UsefulUnorderedDataStructure] = { 65 | query match { 66 | case UnorderedQuery(source, whereClauses, Some(limiter), None) 67 | if query.trickyWhereClauses.size == 0 && limiter.constantSizeLimitBy.isDefined => 68 | Some(ConstantSizeHeap(query)) 69 | case _ => None 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/data_structure_library/GroupMemoizerFactory.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures.data_structure_library 2 | 3 | import java_transpiler._ 4 | import java_transpiler.queries.{UnorderedQuery, WhereClause} 5 | 6 | import big_o.{BigO, Constant} 7 | import helpers.VariableNameGenerator 8 | import useful_data_structures._ 9 | 10 | object GroupMemoizerFactory extends UsefulUnorderedDataStructureFactory { 11 | 12 | case class GroupMemoizer(query: UnorderedQuery) extends UsefulUnorderedDataStructure(query) { 13 | val asymptoticQueryTime = Constant 14 | 15 | val reduction = query.mbReduction.get 16 | 17 | val fieldName = VariableNameGenerator.getVariableName() 18 | 19 | lazy val insertionFragment: Option[List[JavaStatement]] = { 20 | val mapper = reduction.mapper 21 | 22 | val variableMap = Map( 23 | reduction.reducer.arg1 -> getField(fieldName), 24 | reduction.reducer.arg2 -> mapper.useStr("item") 25 | ) 26 | 27 | val body = reduction.reducer.useBody(variableMap) 28 | 29 | Some(List(setField(fieldName, body))) 30 | } 31 | 32 | def removalFragment: Option[List[JavaStatement]] = { 33 | val mapper = reduction.mapper 34 | val reducer = reduction.invertedReducer.get 35 | 36 | val variableMap = Map( 37 | reducer.arg1 -> getField(fieldName), 38 | reducer.arg2 -> mapper.useStr("item") 39 | ) 40 | 41 | val body = reducer.useBody(variableMap) 42 | 43 | Some(List(setField(fieldName, body))) 44 | } 45 | 46 | def fieldFragments = List(JavaFieldDeclaration(fieldName, JavaIntType, Some(reduction.start))) 47 | 48 | def queryCode = getField(fieldName) 49 | 50 | def methodCode = None 51 | } 52 | 53 | def tryToCreate(query: UnorderedQuery): Option[UsefulUnorderedDataStructure] = { 54 | query match { 55 | case UnorderedQuery(source, whereClauses, None, Some(reduction)) 56 | if query.trickyWhereClauses.size == 0 && reduction.isInvertible => 57 | Some(GroupMemoizer(query)) 58 | case _ => None 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/data_structure_library/MonoidKDTreeFactory.scala: -------------------------------------------------------------------------------- 1 | //package useful_data_structures.data_structure_library 2 | // 3 | //import java_transpiler._ 4 | //import java_transpiler.queries.{UnorderedQuery, WhereClause} 5 | // 6 | //import big_o.{Logarithmic, BigO, Constant} 7 | //import helpers.VariableNameGenerator 8 | //import useful_data_structures.UsefulUnorderedDataStructure 9 | //import useful_data_structures.UsefulUnorderedDataStructureFactory 10 | //import useful_data_structures._ 11 | //import useful_data_structures.data_structure_library.GroupMemoizerFactory.GroupMemoizer 12 | // 13 | //object MonoidKDTreeFactory extends UsefulUnorderedDataStructureFactory { 14 | // 15 | // case class MonoidKDTree(query: UnorderedQuery) extends UsefulUnorderedDataStructure(query) { 16 | // val asymptoticQueryTime = Logarithmic 17 | // val reduction = query.mbReduction.get 18 | // val fieldName = VariableNameGenerator.getVariableName() 19 | // 20 | // lazy val insertionFragment: Option[List[JavaStatement]] = { 21 | // val mapper = reduction.mapper 22 | // 23 | // val variableMap = Map( 24 | // reduction.reducer.arg1 -> getField(fieldName), 25 | // reduction.reducer.arg2 -> mapper.useStr("item") 26 | // ) 27 | // 28 | // val body = reduction.reducer.useBody(variableMap) 29 | // 30 | // Some(List(setField(fieldName, body))) 31 | // } 32 | // 33 | // def removalFragment: Option[List[JavaStatement]] = { 34 | // val mapper = reduction.mapper 35 | // val reducer = reduction.invertedReducer.get 36 | // 37 | // val variableMap = Map( 38 | // reducer.arg1 -> getField(fieldName), 39 | // reducer.arg2 -> mapper.useStr("item") 40 | // ) 41 | // 42 | // val body = reducer.useBody(variableMap) 43 | // 44 | // Some(List(setField(fieldName, body))) 45 | // } 46 | // 47 | // def fieldFragments = List(JavaFieldDeclaration(fieldName, JavaIntType, Some(reduction.start))) 48 | // 49 | // def queryCode = getField(fieldName) 50 | // 51 | // def methodCode = None 52 | // } 53 | // 54 | // def tryToCreate(query: UnorderedQuery): Option[UsefulUnorderedDataStructure] = { 55 | // query match { 56 | // case UnorderedQuery(source, whereClauses, None, Some(reduction)) 57 | // if query.trickyWhereClauses.size == 0 && reduction.isInvertible => 58 | // Some(GroupMemoizer(query)) 59 | // case _ => None 60 | // } 61 | // } 62 | //} 63 | -------------------------------------------------------------------------------- /src/main/scala/useful_data_structures/data_structure_library/MonoidMemoizerFactory.scala: -------------------------------------------------------------------------------- 1 | package useful_data_structures.data_structure_library 2 | 3 | import java_transpiler._ 4 | import java_transpiler.queries.{UnorderedQuery, WhereClause} 5 | 6 | import big_o.{BigO, Constant} 7 | import helpers.VariableNameGenerator 8 | import useful_data_structures.{UsefulDataStructureHelper, UsefulUnorderedDataStructure, UsefulUnorderedDataStructureFactory} 9 | 10 | object MonoidMemoizerFactory extends UsefulUnorderedDataStructureFactory { 11 | case class MonoidMemoizer(query: UnorderedQuery) extends UsefulUnorderedDataStructure(query) { 12 | val asymptoticQueryTime = Constant 13 | 14 | val reduction = query.mbReduction.get 15 | 16 | val fieldName = VariableNameGenerator.getVariableName() 17 | 18 | lazy val insertionFragment: Option[List[JavaStatement]] = { 19 | val mapper = reduction.mapper 20 | 21 | val variableMap = Map( 22 | reduction.reducer.arg1 -> getField(fieldName), 23 | reduction.reducer.arg2 -> mapper.useStr("item") 24 | ) 25 | 26 | val body = reduction.reducer.useBody(variableMap) 27 | 28 | Some(List(ExpressionStatement(JavaAssignmentExpression(fieldName, false, body)))) 29 | } 30 | 31 | def removalFragment: Option[List[JavaStatement]] = None 32 | 33 | def fieldFragments = List(JavaFieldDeclaration(fieldName, JavaIntType, Some(reduction.start))) 34 | 35 | def queryCode = JavaFieldAccess(JavaThis, fieldName) 36 | 37 | def methodCode = None 38 | } 39 | 40 | def tryToCreate(query: UnorderedQuery): Option[UsefulUnorderedDataStructure] = { 41 | query match { 42 | case UnorderedQuery(source, whereClauses, None, Some(reduction)) 43 | if query.trickyWhereClauses.size == 0 => 44 | Some(MonoidMemoizer(query)) 45 | case _ => None 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/cas/ExpressionTests.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | import org.scalatest.PropSpec 4 | import org.scalatest.prop.PropertyChecks 5 | import org.scalatest.prop.Checkers 6 | import org.scalatest.matchers.MustMatchers 7 | import org.scalacheck.{Arbitrary, Gen, Properties} 8 | import org.scalacheck.Prop.forAll 9 | import cas._ 10 | 11 | object ExpressionGenerators { 12 | lazy val genExpression: Gen[MathExp[Name]] = for { 13 | result <- Gen.oneOf(genVariable, genConstant, genSum, genProduct) 14 | } yield result 15 | 16 | lazy val genVariable: Gen[CasVariable[Name]] = { 17 | Gen.oneOf("x", "y", "z").map((name: String) => CasVariable(Name(name))) 18 | } 19 | 20 | lazy val genSum: Gen[MathExp[Name]] = for { 21 | x <- genVariable 22 | y <- genExpression 23 | } yield x + y 24 | 25 | lazy val genProduct: Gen[MathExp[Name]] = for { 26 | x <- genSum 27 | y <- Gen.oneOf(genProduct, genVariable) 28 | z <- Gen.listOf(genVariable) 29 | } yield (List(x, y) ++ z).reduce(_ * _) 30 | 31 | lazy val genConstant: Gen[MathExp[Name]] = Gen.oneOf(Number(0), Number(1), Number(2)).map(_.asInstanceOf[MathExp[Name]]) 32 | 33 | implicit lazy val arbExpression: Arbitrary[MathExp[Name]] = Arbitrary(genExpression) 34 | } 35 | 36 | class ExpressionTests extends PropSpec with PropertyChecks with MustMatchers { 37 | import ExpressionGenerators._ 38 | 39 | implicit lazy val arbInteger: Arbitrary[Int] = Arbitrary(Gen.chooseNum(-10, 10)) 40 | 41 | type Exp = MathExp[Name] 42 | 43 | property("Expression generator isn't buggy") { 44 | forAll { (exp: Exp) => 45 | exp must be(exp) 46 | } 47 | } 48 | 49 | property("Addition works on numbers") { 50 | forAll { (lhs: Int, rhs: Int) => 51 | Number(lhs) + Number(rhs) must be (Number(lhs + rhs)) 52 | } 53 | } 54 | 55 | property("Addition isn't buggy") { 56 | forAll { (lhs:Exp, rhs: Exp) => 57 | (lhs + rhs) must be(lhs + rhs) 58 | } 59 | } 60 | 61 | property("Addition is commutative") { 62 | forAll { (lhs:Exp, rhs: Exp) => 63 | (lhs + rhs) must be(rhs + lhs) 64 | } 65 | } 66 | 67 | property("Addition is commutative according to monte carlo") { 68 | forAll { (lhs:Exp, rhs: Exp) => 69 | (lhs + rhs).monteCarloEquals(rhs + lhs) must be(true) 70 | } 71 | } 72 | 73 | property("Addition is associative") { 74 | forAll { (a: Exp, b: Exp, c: Exp) => 75 | ((a + b) + c).simplify must be(a + (b + c)) 76 | } 77 | } 78 | 79 | property("Addition is associative according to monte carlo") { 80 | forAll { (a:Exp, b: Exp, c: Exp) => 81 | ((a + b) + c).monteCarloEquals(a + (b + c)) must be(true) 82 | } 83 | } 84 | 85 | property("Simplification only needs to be done once") { 86 | forAll { (exp:Exp) => 87 | exp.simplify must be(exp.simplify.simplify) 88 | } 89 | } 90 | 91 | property("Multiplication works on numbers") { 92 | forAll { (lhs: Int, rhs: Int) => 93 | Number(lhs) * Number(rhs) must be (Number(lhs * rhs)) 94 | } 95 | } 96 | 97 | property("Multiplication works on expressions") { 98 | forAll { (lhs: Exp, rhs: Exp) => 99 | lhs * rhs must be(lhs * rhs) 100 | } 101 | } 102 | 103 | property("Multiplication is commutative") { 104 | forAll { (lhs:Exp, rhs: Exp) => 105 | (lhs * rhs) must be(rhs * lhs) 106 | } 107 | } 108 | 109 | property("Multiplication is associative") { 110 | forAll { (a: Exp, b: Exp, c: Exp) => 111 | ((a * b) * c).simplify must be(a * (b * c)) 112 | } 113 | } 114 | 115 | property("Min is commutative") { 116 | forAll { (lhs:Exp, rhs: Exp) => 117 | min(lhs, rhs) must be(min(rhs, lhs)) 118 | } 119 | } 120 | 121 | property("Min is associative") { 122 | forAll { (a: Exp, b: Exp, c: Exp) => 123 | min(min(a, b), c) must be(min(a, min(b, c))) 124 | } 125 | } 126 | 127 | property("Monte Carlo equals isn't obviously broken") { 128 | forAll { (a: Exp, b: Exp) => 129 | a.monteCarloEquals(a + b) must be(b.monteCarloEquals(Number(0))) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/test/scala/cas/GenericExpressionTests.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | import org.scalatest.PropSpec 4 | import org.scalatest.prop.PropertyChecks 5 | import org.scalatest.prop.Checkers 6 | import org.scalatest.matchers.MustMatchers 7 | import org.scalacheck.{Arbitrary, Gen, Properties} 8 | import org.scalacheck.Prop.forAll 9 | 10 | class GenericExpressionTests extends PropSpec with PropertyChecks with MustMatchers { 11 | 12 | import ExpressionGenerators._ 13 | 14 | implicit lazy val arbInteger: Arbitrary[Int] = Arbitrary(Gen.chooseNum(-10, 10)) 15 | 16 | type Exp = MathExp[Name] 17 | 18 | implicit lazy val genOperator: Arbitrary[CasBinaryOperator[Name]] = Arbitrary(for { 19 | x <- Gen.option(Gen.const(Idempotent)) 20 | y <- Gen.option(Gen.const(Associative)) 21 | z <- Gen.option(Gen.const(Commutative)) 22 | } yield new CasBinaryOperator(Name("f"), List(x, y, z).flatten.toSet)) 23 | 24 | property("Min is commutative") { 25 | forAll { (a: Exp, b: Exp) => 26 | min(a, b) must be(min(b, a)) 27 | } 28 | } 29 | 30 | property("Min is associative") { 31 | forAll { (a: Exp, b: Exp, c: Exp) => 32 | min(a, min(b, c)) must be(min(min(a, b), c)) 33 | } 34 | } 35 | 36 | property("Functions are associative if they should be") { 37 | forAll { (f: CasBinaryOperator[Name], a: Exp, b: Exp, c: Exp) => 38 | if (f.is(Associative)) { 39 | f(a, f(b, c)) must be(f(f(a, b), c)) 40 | } 41 | } 42 | } 43 | 44 | property("Functions are not associative if they should not be") { 45 | forAll { (f: CasBinaryOperator[Name], a: Exp, b: Exp, c: Exp) => 46 | if (!f.is(Associative) && a != b && b != c && a != c) { 47 | f(a, f(b, c)) must not be f(f(a, b), c) 48 | } 49 | } 50 | } 51 | 52 | property("Functions are commutative if they should be") { 53 | forAll { (f: CasBinaryOperator[Name], a: Exp, b: Exp) => 54 | if (f.is(Commutative)) { 55 | f(a, b) must be(f(b, a)) 56 | } 57 | } 58 | } 59 | 60 | property("Functions are not commutative if they should not be") { 61 | forAll { (f: CasBinaryOperator[Name], a: Exp, b: Exp) => 62 | if (!f.is(Commutative) && a != b) { 63 | f(a, b) must not be f(b, a) 64 | } 65 | } 66 | } 67 | 68 | property("Functions are idempotent if they should be") { 69 | forAll { (f: CasBinaryOperator[Name], a: Exp) => 70 | if (f.is(Idempotent)) { 71 | f(a, a) must be(a) 72 | } 73 | } 74 | } 75 | 76 | property("Functions are not idempotent if they should not be") { 77 | forAll { (f: CasBinaryOperator[Name], a: Exp) => 78 | if (!f.is(Idempotent)) { 79 | f(a, a) must not be a 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/scala/java_transpiler/JavaMethodParsingSpecs.scala: -------------------------------------------------------------------------------- 1 | package cas 2 | 3 | import java_transpiler._ 4 | 5 | import org.scalatest.{Matchers, FlatSpec, PropSpec} 6 | import org.scalatest.prop.PropertyChecks 7 | import org.scalatest.matchers.{ShouldMatchers, MustMatchers} 8 | 9 | class JavaMethodParsingSpecs extends FlatSpec with Matchers { 10 | "A method" should "be parsed to a correct AST" in { 11 | val java = 12 | """ 13 | int factorial(int x) { 14 | if (x <= 0) return 1; 15 | else return factorial(x - 1) * x; 16 | } 17 | """ 18 | 19 | val method = JavaMethodDeclaration.parse(java) 20 | 21 | method.args should be(List("x" -> JavaIntType)) 22 | method.name should be("factorial") 23 | method.returnType should be(Some(JavaIntType)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /thoughts/examples.rb: -------------------------------------------------------------------------------- 1 | class PriorityQueue 2 | define_tables do |tables| 3 | tables.add_table :items, UnorderedTable(name, price) 4 | end 5 | 6 | def insert(name, price) 7 | items.insert(name, price) 8 | end 9 | 10 | def delete(name) 11 | items.where(:name => name).delete_all 12 | end 13 | 14 | def min_price 15 | items.minimum(:price) 16 | end 17 | end 18 | 19 | 20 | class PriorityQueue 21 | define_tables do |tables| 22 | tables.add_table :items, UnorderedTable(name, us_price) 23 | end 24 | 25 | def insert(name, price) 26 | items.where(:name => name).delete_all 27 | items.insert(name, us_price) 28 | end 29 | 30 | def delete(name) 31 | items.where(:name => name).delete_all 32 | end 33 | end 34 | 35 | class InsanePriorityQueue 36 | define_tables do |tables| 37 | tables.add_table :items, UnorderedTable(name, us_price, aud_price) 38 | end 39 | 40 | def insert(name, us_price, aud_price) 41 | items.where(:name => name).delete_all 42 | items.insert(name, us_price, aud_price) 43 | end 44 | 45 | def delete(name) 46 | items.where(:name => name).delete_all 47 | end 48 | 49 | def get_cheapest(exchange_rate) 50 | items.minimum_by({ |item| item.us_price + item.aud_price * exchange_rate}) 51 | end 52 | end 53 | 54 | --------------------------------------------------------------------------------