├── README.md ├── build.sbt ├── project └── build.properties └── src ├── main ├── resources │ └── scalac-plugin.xml └── scala │ └── basic │ └── BasicPlugin.scala └── test └── scala └── Basic.scala /README.md: -------------------------------------------------------------------------------- 1 | ## BASIC-emitting Scala compiler plugin 2 | 3 | An example of how to write a plugin for the Scala compiler. 4 | 5 | This plugin adds a phase that traverses selected parts of the AST, generates corresponding BASIC code and writes it to a file. 6 | 7 | Only supports a tiny subset of Scala/BASIC syntax. 8 | 9 | ## Usage 10 | 11 | First package the plugin into a jar: 12 | 13 | ``` 14 | $ sbt package 15 | ``` 16 | 17 | Then compile the sample code in `src/main/test`: 18 | 19 | ``` 20 | $ sbt test:compile 21 | ``` 22 | 23 | This will result in a BASIC file being created: 24 | 25 | ``` 26 | $ cat printHelloForever.bas 27 | 10 REM 28 | 20 PRINT "Hello Scala World" 29 | 30 GOTO 10 30 | ``` 31 | 32 | Run the file in a BASIC interpreter of your choice (I used Chipmunk BASIC on OSX). 33 | 34 | ``` 35 | $ basic printHelloForever.bas 36 | Hello Scala World 37 | Hello Scala World 38 | Hello Scala World 39 | Hello Scala World 40 | ... 41 | ``` 42 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.11.7" 2 | libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.7" 3 | scalacOptions in Test += "-Xplugin:target/scala-2.11/scalac-plugin-basic_2.11-0.1-SNAPSHOT.jar" 4 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.9 2 | -------------------------------------------------------------------------------- /src/main/resources/scalac-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | basic 3 | basic.BasicPlugin 4 | 5 | -------------------------------------------------------------------------------- /src/main/scala/basic/BasicPlugin.scala: -------------------------------------------------------------------------------- 1 | package basic 2 | 3 | import scala.tools.nsc 4 | import nsc.Global 5 | import nsc.Phase 6 | import nsc.plugins.Plugin 7 | import nsc.plugins.PluginComponent 8 | import java.nio.file._ 9 | import java.nio.charset.StandardCharsets 10 | 11 | class BasicPlugin(val global: Global) extends Plugin { 12 | import global._ 13 | 14 | val name = "basic" 15 | val description = "generates BASIC code" 16 | val components = List[PluginComponent](Component) 17 | 18 | private object Component extends PluginComponent { 19 | val global: BasicPlugin.this.global.type = BasicPlugin.this.global 20 | val runsAfter = List("typer") 21 | val phaseName = BasicPlugin.this.name 22 | override val description = BasicPlugin.this.description 23 | def newPhase(prev: Phase) = new BasicPhase(prev) 24 | 25 | class BasicPhase(prev: Phase) extends StdPhase(prev) { 26 | override def name = BasicPlugin.this.name 27 | def apply(unit: CompilationUnit): Unit = { 28 | for { 29 | DefDef( 30 | mod, 31 | methodName, 32 | typeArgs, 33 | paramLists, 34 | tpe, 35 | body 36 | ) <- unit.body if methodName.toString.startsWith("BASIC_") 37 | } { 38 | val programName = methodName.toString.substring(6) 39 | println(s"Found a BASIC program: $programName") 40 | val lines = parseProgram(body) 41 | val path = writeToFile(lines, programName) 42 | println(s"Wrote BASIC program to file: ${path.toAbsolutePath}") 43 | } 44 | } 45 | 46 | private def parseProgram(tree: Tree): List[String] = { 47 | var lines: List[(Int, String)] = Nil 48 | var lineNum = 0 49 | def nextLineNum() = { lineNum = lineNum + 10; lineNum } 50 | def appendLine(cmd: String): Int = { 51 | val lineNum = nextLineNum() 52 | lines = (lineNum, cmd) :: lines 53 | lineNum 54 | } 55 | 56 | val traverser = new Traverser { 57 | override def traverse(tree: Tree): Unit = tree match { 58 | case LabelDef(name, _, 59 | If( 60 | Literal(Constant(true)), 61 | ifBody, 62 | _ 63 | ) 64 | ) => 65 | // this is a while(true) loop 66 | val gotoLine = appendLine("REM") 67 | super.traverse(ifBody) 68 | appendLine(s"GOTO $gotoLine") 69 | case Apply( 70 | Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("println")), 71 | List(Literal(Constant(message))) 72 | ) => appendLine(s"""PRINT "$message"""") 73 | case _ => super.traverse(tree) 74 | } 75 | } 76 | traverser.traverse(tree) 77 | 78 | lines.reverse.map { case (lineNum, cmd) => s"$lineNum $cmd" } 79 | } 80 | 81 | private def writeToFile(lines: List[String], programName: String): Path = { 82 | val bytes = lines.mkString("\n").getBytes("UTF-8") 83 | val path = Paths.get(s"${programName}.bas") 84 | Files.write(path, bytes) 85 | path 86 | } 87 | 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/scala/Basic.scala: -------------------------------------------------------------------------------- 1 | object Basic { 2 | 3 | def BASIC_printHelloForever = { 4 | while(true) { 5 | println("Hello Scala World") 6 | } 7 | } 8 | 9 | } 10 | --------------------------------------------------------------------------------