├── .claude └── settings.local.json ├── .github ├── CODEOWNERS └── workflows │ └── scala.yml ├── .gitignore ├── CLAUDE.md ├── LICENSE ├── README.md ├── bin ├── onion ├── onion.bat ├── onionc └── onionc.bat ├── build.sbt ├── docs ├── grammar.txt └── parser-refactoring.md ├── grammar ├── JJOnionParser.jj └── JJOnionParserRefactored.jj ├── licenses ├── BCEL-LICENSE ├── JAVACC-LICENSE ├── JUNIT-LICENSE └── SCALA-LICENSE ├── project ├── .build.properties.un~ ├── build.properties └── plugins.sbt ├── run ├── Array.on ├── Bean.on ├── Calculator.on ├── Delegation.on ├── Factorial.on ├── Foreach.on ├── Hello.on ├── LineCounter.on ├── LineFilter.on ├── List.on ├── ReadLine.on ├── Select.on └── StringCat.on └── src ├── main ├── java │ └── onion │ │ ├── Function0.java │ │ ├── Function1.java │ │ ├── Function10.java │ │ ├── Function2.java │ │ ├── Function3.java │ │ ├── Function4.java │ │ ├── Function5.java │ │ ├── Function6.java │ │ ├── Function7.java │ │ ├── Function8.java │ │ ├── Function9.java │ │ ├── IO.java │ │ ├── Iterables.java │ │ ├── View.java │ │ └── compiler │ │ └── parser │ │ └── ASTBuilderAdapter.java ├── resources │ ├── META-INF │ │ └── Manifest.mf │ ├── errorMessage.properties │ └── errorMessage_ja.properties └── scala │ └── onion │ ├── compiler │ ├── AST.scala │ ├── ASTBuilder.scala │ ├── AbstractTable.scala │ ├── AsmCodeGeneration.scala │ ├── AsmCodeGenerationVisitor.scala │ ├── BytecodeGenerator.scala │ ├── ClassTable.scala │ ├── ClosureLocalBinding.scala │ ├── CompileError.scala │ ├── CompiledClass.scala │ ├── CompilerConfig.scala │ ├── FileInputSource.scala │ ├── ImportItem.scala │ ├── InputSource.scala │ ├── LocalBinding.scala │ ├── LocalContext.scala │ ├── LocalFrame.scala │ ├── LocalScope.scala │ ├── Location.scala │ ├── Modifier.scala │ ├── MultiTable.scala │ ├── Named.scala │ ├── OnionClassLoader.scala │ ├── OnionCompiler.scala │ ├── OnionTypeConversion.scala │ ├── OrderedTable.scala │ ├── Parser.scala │ ├── Parsing.scala │ ├── Processor.scala │ ├── Rewriting.scala │ ├── SemanticError.scala │ ├── SemanticErrorReporter.scala │ ├── StaticImportItem.scala │ ├── StaticImportList.scala │ ├── StreamInputSource.scala │ ├── StringInputSource.scala │ ├── Symbol.scala │ ├── TypedAST.scala │ ├── TypedASTVisitor.scala │ ├── TypedGenerating.scala │ ├── Typing.scala │ ├── environment │ │ ├── AsmRefs.scala │ │ ├── ClassFileTable.scala │ │ └── ReflectionRefs.scala │ ├── exceptions │ │ ├── CompilationException.scala │ │ └── ScriptException.scala │ └── toolbox │ │ ├── Boxing.scala │ │ ├── Classes.scala │ │ ├── Inputs.scala │ │ ├── InvocationException.scala │ │ ├── Message.scala │ │ ├── MethodInvoker.scala │ │ ├── Paths.scala │ │ ├── SymbolGenerator.scala │ │ └── Systems.scala │ └── tools │ ├── CompilerFrontend.scala │ ├── ScriptRunner.scala │ ├── Shell.scala │ ├── option │ ├── CommandLineParam.scala │ ├── CommandLineParser.scala │ ├── OptionConfig.scala │ └── ParseResult.scala │ └── package.scala └── test ├── resources └── dummy.txt ├── run ├── Hello.on └── Hello.out └── scala └── onion └── compiler ├── TestASTBuilder.scala └── tools ├── AbstractShellSpec.scala ├── BeanSpec.scala ├── BreakContinueSpec.scala ├── CountersSpec.scala ├── DecrementSpec.scala ├── FactorialSpec.scala ├── ForeachSpec.scala ├── FunctionWithExpressionBodySpec.scala ├── HelloWorldSpec.scala ├── ImportSpec.scala ├── IncrementSpec.scala ├── StringInterpolationSpec.scala └── TerminatorSpec.scala /.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(find:*)", 5 | "Bash(java:*)", 6 | "Bash(sbt clean:*)", 7 | "Bash(rm:*)", 8 | "Bash(sbt:*)", 9 | "Bash(grep:*)", 10 | "Bash(mv:*)", 11 | "Bash(ls:*)", 12 | "Bash(cat:*)", 13 | "Bash(timeout 30 sbt 'testOnly *CountersSpec')", 14 | "Bash(rg:*)", 15 | "Bash(scala -cp \"target/scala-3.6.2/classes:target/scala-3.6.2/test-classes\" manual_test.scala)" 16 | ], 17 | "deny": [] 18 | } 19 | } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @kmizu 2 | -------------------------------------------------------------------------------- /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: onion 2 | 3 | on: 4 | pull_request: 5 | push: 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install sbt 11 | uses: sbt/setup-sbt@v1 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Setup JDK 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: 'temurin' 18 | java-version: '21' 19 | cache: 'sbt' 20 | - name: Build and Test 21 | run: sbt -v +test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .bsp/ 3 | build.lib/ 4 | .scala-build/ 5 | 6 | project/boot 7 | project/metals.sbt 8 | project/project 9 | 10 | src/main/parser/onion 11 | target/ 12 | .idea/ 13 | .idea_modules/ 14 | .cache 15 | .build.sbt.un~ 16 | 17 | .settings 18 | 19 | .bloop/ 20 | .metals/ 21 | .vscode/ 22 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Build Commands 6 | 7 | - **Build the project**: `sbt compile` 8 | - **Run tests**: `sbt test` 9 | - **Run a single test**: `sbt 'testOnly *HelloWorldSpec'` 10 | - **Package JAR**: `sbt assembly` or `sbt packageBin` 11 | - **Create distribution**: `sbt dist` (creates a distribution zip in target/dist) 12 | - **Run a script**: `sbt 'runScript [args]'` 13 | - **Clean**: `sbt clean` 14 | 15 | ## High-Level Architecture 16 | 17 | The Onion compiler follows a pipeline architecture with distinct phases: 18 | 19 | 1. **Parsing** (`Parsing.scala`) - Uses JavaCC-generated parser (`JJOnionParser.jj`) to create untyped AST 20 | 2. **Rewriting** (`Rewriting.scala`) - Transforms and normalizes the AST 21 | 3. **Type Checking** (`Typing.scala`) - Produces typed AST from untyped AST 22 | 4. **Code Generation** (`TypedGenerating.scala`) - Generates JVM bytecode from typed AST 23 | - Currently transitioning from BCEL to ASM for bytecode generation 24 | - `AsmCodeGeneration.scala` contains the new ASM-based backend 25 | 26 | ### Key Components 27 | 28 | - **Processor Pipeline**: Compiler phases extend `Processor[A,B]` trait and can be composed with `andThen` 29 | - **AST Types**: 30 | - Untyped AST in `AST.scala` 31 | - Typed AST in `TypedAST.scala` 32 | - **Entry Points**: 33 | - `onion.tools.CompilerFrontend` - Main compiler executable (`onionc`) 34 | - `onion.tools.ScriptRunner` - Script execution (`onion`) 35 | - `onion.tools.Shell` - Interactive shell/REPL 36 | - **Error Handling**: `SemanticError`, `CompileError`, and `SemanticErrorReporter` for compilation diagnostics 37 | 38 | ### Testing 39 | 40 | Tests use ScalaTest and extend `AbstractShellSpec` for integration testing. Test files are in `src/test/scala/onion/compiler/tools/`. 41 | 42 | ### Language Features 43 | 44 | Onion is a statically-typed, object-oriented language that compiles to JVM bytecode. Key features include: 45 | - Classes with public/private visibility modifiers 46 | - Static and instance methods 47 | - Type inference 48 | - First-class functions (Function0 through Function10 interfaces) 49 | - Module system with imports -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2014, Kota Mizushima 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list 8 | of conditions and the following disclaimer. 9 | Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or other 11 | materials provided with the distribution. 12 | Neither the name of the Kota Mizushima nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without specific 14 | prior written permission. 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 19 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 23 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 24 | OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Onion - A Statically Typed Programming Language on JVM [![Build Status](https://github.com/onion-lang/onion/actions/workflows/scala.yml/badge.svg?branch=main)](https://github.com/onion-lang/onion/actions) 2 | 3 | Onion is an object-oriented and statically typed programming language. Source codes of Onion 4 | compiles into JVM class files as in-memory or real files. 5 | 6 | Originally, Onion was written in Java. It has been rewritten in Scala completely except Parser, 7 | using JavaCC. 8 | 9 | ## Architecture 10 | 11 | The compiler parses source code into an untyped AST and then performs type 12 | checking to produce a **typed AST**. The old intermediate representation (IRT) 13 | has been folded into this typed tree. Code generation now runs on the typed 14 | AST via a thin compatibility layer using ASM. 15 | 16 | ## Tools 17 | 18 | ### onionc 19 | 20 | #### Usage 21 | 22 | ```txt 23 | onionc [options] source files... 24 | ``` 25 | 26 | #### Available options: 27 | 28 | * -classpath Set classpath of source files in compilation. 29 | * -encoding Set encoding of source files. 30 | * -d Set output directory of results. 31 | * -maxErrorReports Set the maximum number of comiplation errors reported. 32 | 33 | `onionc` compiles source files into class files in the directorys corresponding to module names 34 | of source files rooted by "-d" option. If "-d" is not specified, the value of "-d" is specified as the current directory. 35 | 36 | For example, if source files which module name is "org.onion_lang" is compiled, class files are generated under: 37 | 38 | * Unix-like OS : org/onion_lang 39 | * Windows: org\onion_lang 40 | 41 | ### onion 42 | 43 | #### Usage 44 | 45 | ``` 46 | onion [options] source files... [command line arguments] 47 | ``` 48 | 49 | #### Available options 50 | * -classpath classpath of source files in compilation. 51 | * -encoding encoding of source files. 52 | * -maxErrorReports the maximum number of comiplation errors reported. 53 | 54 | `onion` compiles source files into in-memory class files and execute them. The entry point is: 55 | 56 | 1. A main method if there is an explicit class definition and it have the main method. 57 | 2. The main method of the class on the top. 58 | 3. Otherwise, the first statement on the top. 59 | 60 | ## Limitations 61 | 62 | * Some compilation-time checks is not implemented yet. For example, 63 | It's not checked that abstract methods are implemented. Compiled 64 | codes maybe rejected by class file verifiers sometimes. If you 65 | find the problem, reporting it to help me. 66 | 67 | * Currently, `onionc` has edge cases. The compiler crashes sometimes. 68 | The source codes in example directory compiles and is executed correctly. 69 | 70 | * There are some partially supported features. For example, `finally` clause 71 | in try-catch is not supported yet. 72 | 73 | ## BuildHive (Jenkins) 74 | 75 | This software includes softwares developed by [Apache Software Foundation](http://www.apache.org/). 76 | -------------------------------------------------------------------------------- /bin/onion: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use java from PATH if JAVA_HOME is not set 4 | if [ -z "$JAVA_HOME" ]; then 5 | JAVA_CMD="java" 6 | else 7 | JAVA_CMD="$JAVA_HOME/bin/java" 8 | fi 9 | 10 | # Check if java is available 11 | if ! command -v "$JAVA_CMD" > /dev/null 2>&1; then 12 | echo "Error: Java not found. Please install Java or set JAVA_HOME environment variable." 13 | exit 1 14 | fi 15 | 16 | # Build classpath with all jars in lib directory 17 | CLASSPATH="$ONION_HOME/onion.jar" 18 | if [ -d "$ONION_HOME/lib" ]; then 19 | for jar in "$ONION_HOME/lib"/*.jar; do 20 | if [ -f "$jar" ]; then 21 | CLASSPATH="$CLASSPATH:$jar" 22 | fi 23 | done 24 | fi 25 | 26 | # Add user classpath if set 27 | if [ -n "$CLASSPATH" ]; then 28 | CLASSPATH="$CLASSPATH:$CLASSPATH" 29 | fi 30 | echo $CLASSPATH 31 | 32 | # Run the Onion script runner 33 | exec "$JAVA_CMD" -classpath "$CLASSPATH" onion.tools.ScriptRunner "$@" -------------------------------------------------------------------------------- /bin/onion.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | rem Remove trailing backslash from ONION_HOME if present 5 | if "%ONION_HOME:~-1%"=="\" set "ONION_HOME=%ONION_HOME:~0,-1%" 6 | 7 | rem Use java from PATH if JAVA_HOME is not set 8 | if defined JAVA_HOME ( 9 | set "JAVA_CMD=%JAVA_HOME%\bin\java" 10 | ) else ( 11 | set "JAVA_CMD=java" 12 | ) 13 | 14 | rem Check if java is available 15 | "%JAVA_CMD%" -version >nul 2>&1 16 | if errorlevel 1 ( 17 | echo Error: Java not found. Please install Java or set JAVA_HOME environment variable. 18 | exit /b 1 19 | ) 20 | 21 | rem Build classpath with all jars in lib directory 22 | set "CP=%ONION_HOME%\onion.jar" 23 | if exist "%ONION_HOME%\lib" ( 24 | for %%F in ("%ONION_HOME%\lib\*.jar") do ( 25 | set "CP=!CP!;%%F" 26 | ) 27 | ) 28 | 29 | rem Add user classpath if set 30 | if defined CLASSPATH ( 31 | set "CP=%CP%;%CLASSPATH%" 32 | ) 33 | 34 | rem Run the Onion script runner 35 | "%JAVA_CMD%" -classpath "%CP%" onion.tools.ScriptRunner %* -------------------------------------------------------------------------------- /bin/onionc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use java from PATH if JAVA_HOME is not set 4 | if [ -z "$JAVA_HOME" ]; then 5 | JAVA_CMD="java" 6 | else 7 | JAVA_CMD="$JAVA_HOME/bin/java" 8 | fi 9 | 10 | # Check if java is available 11 | if ! command -v "$JAVA_CMD" > /dev/null 2>&1; then 12 | echo "Error: Java not found. Please install Java or set JAVA_HOME environment variable." 13 | exit 1 14 | fi 15 | 16 | # Run the Onion compiler 17 | exec "$JAVA_CMD" -jar "$ONION_HOME/onion.jar" "$@" -------------------------------------------------------------------------------- /bin/onionc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Remove trailing backslash from ONION_HOME if present 4 | if "%ONION_HOME:~-1%"=="\" set "ONION_HOME=%ONION_HOME:~0,-1%" 5 | 6 | rem Use java from PATH if JAVA_HOME is not set 7 | if defined JAVA_HOME ( 8 | set "JAVA_CMD=%JAVA_HOME%\bin\java" 9 | ) else ( 10 | set "JAVA_CMD=java" 11 | ) 12 | 13 | rem Check if java is available 14 | "%JAVA_CMD%" -version >nul 2>&1 15 | if errorlevel 1 ( 16 | echo Error: Java not found. Please install Java or set JAVA_HOME environment variable. 17 | exit /b 1 18 | ) 19 | 20 | rem Run the Onion compiler 21 | "%JAVA_CMD%" -jar "%ONION_HOME%\onion.jar" %* -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.* 2 | import sbt.internal.inc.classpath.* 3 | import Keys.* 4 | import sbt.internal.librarymanagement.StringUtilities 5 | 6 | import java.util.jar.{Attributes, Manifest} 7 | 8 | lazy val onion = (project in file(".")).settings(onionSettings:_*) 9 | 10 | lazy val dist = TaskKey[Unit]("onion-dist") 11 | 12 | lazy val distPath = SettingKey[File]("onion-dist-path") 13 | 14 | lazy val runScript = inputKey[Unit]("Runs the ScriptRunner with arguments") 15 | 16 | fullRunInputTask( 17 | runScript, 18 | Compile, 19 | "onion.tools.ScriptRunner" 20 | ) 21 | 22 | def isArchive(file: File): Boolean = { 23 | val fileName = file.getName 24 | val lc = fileName.toLowerCase 25 | lc.endsWith(".jar") || lc.endsWith(".zip") || 26 | lc.endsWith(".war") || lc.endsWith(".ear") 27 | } 28 | 29 | def manifestExtra(artifact: File, classpath: Classpath) = { 30 | val libs = classpath.map(_.data).filter(isArchive) :+ artifact 31 | val mf = new Manifest 32 | mf.getMainAttributes.put(Attributes.Name.CLASS_PATH, libs.map("lib/" + _.getName).mkString(" ")) 33 | Package.JarManifest(mf) 34 | } 35 | 36 | def distTask(target: File, out: File, artifact: File, classpath: Classpath) = { 37 | val libs = classpath.map(_.data).filter(isArchive) 38 | val libdir = out / "lib" 39 | IO.createDirectory(libdir) 40 | val map = libs.map(source => (source.asFile, libdir / source.getName)) 41 | IO.copy(map) 42 | IO.copyDirectory(file("bin"), out / "bin") 43 | IO.copyDirectory(file("run"), out / "run") 44 | IO.copyFile(artifact, out / "onion.jar") 45 | IO.copyFile(file("README.md"), out / "README.md") 46 | val files = (out ** AllPassFilter).get.flatMap(f=> f.relativeTo(out).map(r=>(f, r.getPath))) 47 | IO.zip(files, target / "onion-dist.zip", Some(System.currentTimeMillis())) 48 | } 49 | 50 | def javacc(classpath: Classpath, output: File, log: Logger): Seq[File] = { 51 | Fork.java( 52 | ForkOptions().withOutputStrategy( 53 | OutputStrategy.LoggedOutput(log) 54 | ), 55 | "-cp" :: 56 | Path.makeString(classpath.map(_.data)) :: 57 | List( 58 | "javacc", 59 | "-UNICODE_INPUT=true", 60 | "-JAVA_UNICODE_ESCAPE=true", 61 | "-BUILD_TOKEN_MANAGER=true", 62 | "-OUTPUT_DIRECTORY=%s/onion/compiler/parser".format(output.toString), 63 | "grammar/JJOnionParser.jj" 64 | ) 65 | ) match { 66 | case exitCode if exitCode != 0 => sys.error("Nonzero exit code returned from javacc: " + exitCode) 67 | case 0 => 68 | } 69 | (output ** "*.java").get 70 | } 71 | 72 | lazy val onionSettings = Seq( 73 | version := "0.2.0-SNAPSHOT", 74 | scalaVersion := "3.6.2", 75 | name := "onion", 76 | organization := "org.onion_lang", 77 | Compile / unmanagedSourceDirectories := { 78 | (Seq((Compile / javaSource).value) ++ Seq((Compile / scalaSource).value) ++ Seq((Compile / sourceManaged).value)) 79 | }, 80 | scalacOptions ++= Seq("-encoding", "utf8", "-unchecked", "-deprecation", "-feature", "-language:implicitConversions", "-language:existentials"), 81 | javacOptions ++= Seq("-sourcepath", "src.lib", "-Xlint:unchecked", "-source", "17"), 82 | libraryDependencies ++= Seq( 83 | "org.ow2.asm" % "asm" % "9.8", 84 | "org.ow2.asm" % "asm-commons" % "9.8", 85 | "org.ow2.asm" % "asm-tree" % "9.8", 86 | "org.ow2.asm" % "asm-util" % "9.8", 87 | "net.java.dev.javacc" % "javacc" % "5.0", 88 | "junit" % "junit" % "4.7" % "test", 89 | "org.scalatest" %% "scalatest" % "3.2.19" % "test" 90 | ), 91 | Compile / sourceGenerators += Def.task { 92 | val cp = (Compile / externalDependencyClasspath).value 93 | val dir = (Compile / sourceManaged).value 94 | val s = streams.value 95 | val parser = dir / "java" / "onion" / "compiler" / "parser" / "JJOnionParser.java" 96 | val grammar = new java.io.File("grammar") / "JJOnionParser.jj" 97 | if(grammar.lastModified() > parser.lastModified()) { 98 | javacc(cp, dir / "java", s.log) 99 | } else { 100 | Seq() 101 | } 102 | }.taskValue, 103 | Compile / packageBin / packageOptions := { 104 | val main = mainClass.value 105 | val opts = (Compile / packageBin / packageOptions).value 106 | opts ++ main.map{m => Package.MainClass(m)} 107 | }, 108 | Compile / packageBin / packageOptions := { 109 | val opts = (Compile / packageBin / packageOptions).value 110 | val a = (Compile / packageBin / artifactPath).value 111 | val cp = (Runtime / dependencyClasspath).value 112 | opts :+ manifestExtra(a, cp) 113 | }, 114 | dist := { 115 | val t = target.value 116 | val out = (dist / distPath).value 117 | val p = (Compile / packageBin).value 118 | val cp = (Runtime / fullClasspath).value 119 | distTask(t, out, p, cp) 120 | }, 121 | dist / distPath := { 122 | target.value / "dist" 123 | }, 124 | mainClass := Some("onion.tools.CompilerFrontend"), 125 | assembly / assemblyJarName := "onion.jar", 126 | assembly / assemblyMergeStrategy := { 127 | case "module-info.class" => MergeStrategy.discard 128 | case x => 129 | val oldStrategy = (assembly / assemblyMergeStrategy).value 130 | oldStrategy(x) 131 | } 132 | ) 133 | 134 | fork in run := true 135 | -------------------------------------------------------------------------------- /docs/grammar.txt: -------------------------------------------------------------------------------- 1 | The followings are the grammar of Onion programming language like EBNF: 2 | 3 | block_comment ::= '/*' ([^*])* '*' ('*' | ([^*/] ([^*])* '*'))* '/' 4 | line_comment ::= '//' ([^\r\n])* 5 | shell_comment ::= '#!' ([^\r\n])* 6 | 7 | compile_unit ::= module? import? ( 8 | 'class' | 'interface' | 'statement' | 'function' | 'global_var' 9 | )+ 10 | module ::= 'module' id (. id)* ; 11 | import ::= 'import' { (id (. id)+ ; | id = id (. id)+ ;)* } 12 | 13 | access ::= 'public' | 'protected' | 'private' 14 | modifier ::= 'final' | 'internal' | 'volatile' | 'synchronized' | 'static' | 'inherited' 15 | 16 | type ::= primitive | id | '#<' id (. id)* '>' | type ('[]')+ 17 | primitive ::= 'Byte' | 'Short' | 'Char' | 'Int' | 'Long' | 'Float' | 'Double' | 'Boolean' | 'Unit' 18 | 19 | class ::= modifier* 'class' id (':' type)? ('<:' type (, type)*)? { 20 | (field | method | constructor)* 21 | (access : (field | method | constructor)*)* 22 | } 23 | interface ::= modifier* 'interface' id ('<:' type (, type)*)? { 24 | (id ('(' (var_decl (, var_decl)*)? ')')? (: type)? ;)* 25 | } 26 | 27 | field ::= modifier* ('forward')? field_id : type [= expression] ; 28 | method ::= ['def'] modifier* id ( '(' (arg_list)? ')' )? (: type)? (; | block) 29 | constructor ::= modifier* new ( '(' (arg_list)? ')' )? (: '(' [expression_list] ')' )? block 30 | function ::= modifier* def id ( '(' (arg_list)? ')' )? (: type)? block 31 | global_var ::= modifier* var id : type (= expression)? ; 32 | 33 | statement ::= 'if' expression block [else block] 34 | | 'select' expression { (case expr_list block)* [else block] } 35 | | 'while' expression block 36 | | 'for' (var_decl | expression_stmt) expression ; [expr] block 37 | | 'foreach' var_decl 'in' expression block 38 | | 'synchronized' expression block 39 | | 'try' block ('rec' id : type block)* 'fin' block 40 | | 'break' ; 41 | | 'continue' ; 42 | | 'return' [expression] ; 43 | | 'throw' expression ; 44 | | { statement* } 45 | | ';' 46 | | expression ; 47 | | var_decl 48 | | id : type (= expression)? ; 49 | 50 | arg_list ::= id : type (, id : type)* 51 | 52 | expression ::= expression binary_op expression 53 | | expression assign_op expression 54 | | unary_op expression 55 | | expression ( . id ( '(' (expr_list)? ')' )? )* 56 | | 'new' type '[' expression_list ']' 57 | | 'new' type ( '(' (expression_list)? ')' )? 58 | | type '::' id ( '(' (expression_list)? ')' )? 59 | | expression ('[' expression ']')* 60 | | '(' expression ')' 61 | | id ( '(' (expression_list)? ')' )? 62 | | expression 'is' type 63 | | expression $ type 64 | | # type . id ( '(' [arg_list] ')' )? block 65 | | '[' (expression_list)? ']' 66 | | 'this' 67 | | 'true' 68 | | 'false' 69 | | 'null' 70 | | 71 | | 75 | 76 | expression_list ::= expression (, expression)* 77 | unary_op ::= ! | - | + | ~ 78 | binary_op ::= + | - | * | / | % | << || >> | >>> | ^ | & | '|' | < | > | <= | >= | == | != | === | !== | '||' | && 79 | assign_op ::= = | += | -= | *= | /= | %= 80 | id ::= ([a-z] | [A-Z] | _) ([a-z] | [A-Z] | [0-9] | _)* 81 | field_id ::= '@' id 82 | -------------------------------------------------------------------------------- /docs/parser-refactoring.md: -------------------------------------------------------------------------------- 1 | # Parser Refactoring: Separating Grammar from AST Building 2 | 3 | ## Overview 4 | 5 | This refactoring introduces the Builder pattern to separate parsing concerns from AST construction in the Onion compiler. This separation provides several benefits: 6 | 7 | 1. **Testability**: AST construction can be tested independently of parsing 8 | 2. **Flexibility**: Different AST builders can be used for different purposes 9 | 3. **Maintainability**: Grammar changes don't require AST construction changes and vice versa 10 | 4. **Extensibility**: New behaviors can be added without modifying the parser 11 | 12 | ## Architecture 13 | 14 | ### Before Refactoring 15 | 16 | The original parser (`JJOnionParser.jj`) directly constructs AST nodes within the grammar rules: 17 | 18 | ```java 19 | AST.ClassDeclaration class_decl(int mset) : { 20 | // ... variable declarations ... 21 | }{ 22 | t1="class" t2= /* ... parsing ... */ { 23 | return new AST.ClassDeclaration( // Direct AST construction 24 | p(t1), mset, t2.image, ty1, toList(ty2s), sec3, toList(sec2s) 25 | ); 26 | } 27 | } 28 | ``` 29 | 30 | ### After Refactoring 31 | 32 | The refactored parser uses an `ASTBuilder` interface: 33 | 34 | ```java 35 | AST.ClassDeclaration class_decl(int mset) : { 36 | // ... variable declarations ... 37 | }{ 38 | t1="class" t2= /* ... parsing ... */ { 39 | return builder.createClassDeclaration( // Delegated to builder 40 | p(t1), mset, t2.image, ty1, toList(ty2s), sec3, toList(sec2s) 41 | ); 42 | } 43 | } 44 | ``` 45 | 46 | ## Components 47 | 48 | ### 1. ASTBuilder Trait (`ASTBuilder.scala`) 49 | 50 | Defines the interface for AST construction: 51 | 52 | ```scala 53 | trait ASTBuilder { 54 | def createCompilationUnit(...): AST.CompilationUnit 55 | def createClassDeclaration(...): AST.ClassDeclaration 56 | def createMethodDeclaration(...): AST.MethodDeclaration 57 | // ... other AST node creation methods 58 | } 59 | ``` 60 | 61 | ### 2. DefaultASTBuilder (`ASTBuilder.scala`) 62 | 63 | Provides the default implementation that simply constructs AST nodes: 64 | 65 | ```scala 66 | class DefaultASTBuilder extends ASTBuilder { 67 | def createClassDeclaration(...) = { 68 | AST.ClassDeclaration(location, modifiers, name, ...) 69 | } 70 | } 71 | ``` 72 | 73 | ### 3. ASTBuilderAdapter (`ASTBuilderAdapter.java`) 74 | 75 | Java adapter for seamless integration with JavaCC: 76 | 77 | ```java 78 | public class ASTBuilderAdapter { 79 | private final ASTBuilder builder; 80 | 81 | // Handles Java-Scala interop complexities 82 | public AST.ClassDeclaration createClassDeclaration(...) { 83 | return builder.createClassDeclaration(...); 84 | } 85 | } 86 | ``` 87 | 88 | ### 4. JJOnionParserRefactored (`JJOnionParserRefactored.jj`) 89 | 90 | Modified JavaCC grammar that uses the builder pattern instead of direct AST construction. 91 | 92 | ## Use Cases 93 | 94 | ### 1. Custom Analysis 95 | 96 | ```scala 97 | class AnalyzingASTBuilder extends DefaultASTBuilder { 98 | var methodCount = 0 99 | 100 | override def createMethodDeclaration(...) = { 101 | methodCount += 1 102 | super.createMethodDeclaration(...) 103 | } 104 | } 105 | ``` 106 | 107 | ### 2. Validation 108 | 109 | ```scala 110 | class ValidatingASTBuilder extends DefaultASTBuilder { 111 | override def createMethodDeclaration(...) = { 112 | if (args.length > 10) { 113 | throw new IllegalArgumentException("Too many parameters") 114 | } 115 | super.createMethodDeclaration(...) 116 | } 117 | } 118 | ``` 119 | 120 | ### 3. Transformation 121 | 122 | ```scala 123 | class TransformingASTBuilder extends DefaultASTBuilder { 124 | override def createMethodDeclaration(...) = { 125 | val modifiedBody = addLogging(body) 126 | super.createMethodDeclaration(..., modifiedBody) 127 | } 128 | } 129 | ``` 130 | 131 | ### 4. Debugging 132 | 133 | ```scala 134 | class LoggingASTBuilder extends DefaultASTBuilder { 135 | override def createClassDeclaration(...) = { 136 | println(s"Creating class: $name at $location") 137 | super.createClassDeclaration(...) 138 | } 139 | } 140 | ``` 141 | 142 | ## Migration Strategy 143 | 144 | 1. **Phase 1**: Create builder infrastructure (completed) 145 | - ASTBuilder trait 146 | - DefaultASTBuilder implementation 147 | - ASTBuilderAdapter for Java interop 148 | 149 | 2. **Phase 2**: Refactor parser gradually 150 | - Start with simple constructs (literals, identifiers) 151 | - Move to complex constructs (classes, methods) 152 | - Maintain backward compatibility 153 | 154 | 3. **Phase 3**: Update existing code 155 | - Modify Parsing.scala to use new parser 156 | - Update tests to use refactored components 157 | 158 | 4. **Phase 4**: Leverage new capabilities 159 | - Add validation builders 160 | - Implement transformation builders 161 | - Create specialized builders for different compilation modes 162 | 163 | ## Benefits Realized 164 | 165 | 1. **Separation of Concerns**: Grammar rules focus on syntax; builders focus on semantics 166 | 2. **Testability**: AST construction logic can be unit tested without parsing 167 | 3. **Extensibility**: New compilation features can be added via custom builders 168 | 4. **Maintainability**: Changes to AST structure don't require grammar modifications 169 | 5. **Debugging**: Logging/tracing can be added without touching the parser 170 | 171 | ## Future Enhancements 172 | 173 | 1. **Builder Composition**: Chain multiple builders for complex transformations 174 | 2. **Context-Aware Building**: Builders that maintain compilation context 175 | 3. **Error Recovery**: Builders that can construct partial ASTs for better error messages 176 | 4. **Optimization**: Builders that perform early optimizations during parsing -------------------------------------------------------------------------------- /licenses/BCEL-LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Apache License 3 | * Version 2.0, January 2004 4 | * http://www.apache.org/licenses/ 5 | * 6 | * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | * 8 | * 1. Definitions. 9 | * 10 | * "License" shall mean the terms and conditions for use, reproduction, 11 | * and distribution as defined by Sections 1 through 9 of this document. 12 | * 13 | * "Licensor" shall mean the copyright owner or entity authorized by 14 | * the copyright owner that is granting the License. 15 | * 16 | * "Legal Entity" shall mean the union of the acting entity and all 17 | * other entities that control, are controlled by, or are under common 18 | * control with that entity. For the purposes of this definition, 19 | * "control" means (i) the power, direct or indirect, to cause the 20 | * direction or management of such entity, whether by contract or 21 | * otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | * outstanding shares, or (iii) beneficial ownership of such entity. 23 | * 24 | * "You" (or "Your") shall mean an individual or Legal Entity 25 | * exercising permissions granted by this License. 26 | * 27 | * "Source" form shall mean the preferred form for making modifications, 28 | * including but not limited to software source code, documentation 29 | * source, and configuration files. 30 | * 31 | * "Object" form shall mean any form resulting from mechanical 32 | * transformation or translation of a Source form, including but 33 | * not limited to compiled object code, generated documentation, 34 | * and conversions to other media types. 35 | * 36 | * "Work" shall mean the work of authorship, whether in Source or 37 | * Object form, made available under the License, as indicated by a 38 | * copyright notice that is included in or attached to the work 39 | * (an example is provided in the Appendix below). 40 | * 41 | * "Derivative Works" shall mean any work, whether in Source or Object 42 | * form, that is based on (or derived from) the Work and for which the 43 | * editorial revisions, annotations, elaborations, or other modifications 44 | * represent, as a whole, an original work of authorship. For the purposes 45 | * of this License, Derivative Works shall not include works that remain 46 | * separable from, or merely link (or bind by name) to the interfaces of, 47 | * the Work and Derivative Works thereof. 48 | * 49 | * "Contribution" shall mean any work of authorship, including 50 | * the original version of the Work and any modifications or additions 51 | * to that Work or Derivative Works thereof, that is intentionally 52 | * submitted to Licensor for inclusion in the Work by the copyright owner 53 | * or by an individual or Legal Entity authorized to submit on behalf of 54 | * the copyright owner. For the purposes of this definition, "submitted" 55 | * means any form of electronic, verbal, or written communication sent 56 | * to the Licensor or its representatives, including but not limited to 57 | * communication on electronic mailing lists, source code control systems, 58 | * and issue tracking systems that are managed by, or on behalf of, the 59 | * Licensor for the purpose of discussing and improving the Work, but 60 | * excluding communication that is conspicuously marked or otherwise 61 | * designated in writing by the copyright owner as "Not a Contribution." 62 | * 63 | * "Contributor" shall mean Licensor and any individual or Legal Entity 64 | * on behalf of whom a Contribution has been received by Licensor and 65 | * subsequently incorporated within the Work. 66 | * 67 | * 2. Grant of Copyright License. Subject to the terms and conditions of 68 | * this License, each Contributor hereby grants to You a perpetual, 69 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | * copyright license to reproduce, prepare Derivative Works of, 71 | * publicly display, publicly perform, sublicense, and distribute the 72 | * Work and such Derivative Works in Source or Object form. 73 | * 74 | * 3. Grant of Patent License. Subject to the terms and conditions of 75 | * this License, each Contributor hereby grants to You a perpetual, 76 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | * (except as stated in this section) patent license to make, have made, 78 | * use, offer to sell, sell, import, and otherwise transfer the Work, 79 | * where such license applies only to those patent claims licensable 80 | * by such Contributor that are necessarily infringed by their 81 | * Contribution(s) alone or by combination of their Contribution(s) 82 | * with the Work to which such Contribution(s) was submitted. If You 83 | * institute patent litigation against any entity (including a 84 | * cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | * or a Contribution incorporated within the Work constitutes direct 86 | * or contributory patent infringement, then any patent licenses 87 | * granted to You under this License for that Work shall terminate 88 | * as of the date such litigation is filed. 89 | * 90 | * 4. Redistribution. You may reproduce and distribute copies of the 91 | * Work or Derivative Works thereof in any medium, with or without 92 | * modifications, and in Source or Object form, provided that You 93 | * meet the following conditions: 94 | * 95 | * (a) You must give any other recipients of the Work or 96 | * Derivative Works a copy of this License; and 97 | * 98 | * (b) You must cause any modified files to carry prominent notices 99 | * stating that You changed the files; and 100 | * 101 | * (c) You must retain, in the Source form of any Derivative Works 102 | * that You distribute, all copyright, patent, trademark, and 103 | * attribution notices from the Source form of the Work, 104 | * excluding those notices that do not pertain to any part of 105 | * the Derivative Works; and 106 | * 107 | * (d) If the Work includes a "NOTICE" text file as part of its 108 | * distribution, then any Derivative Works that You distribute must 109 | * include a readable copy of the attribution notices contained 110 | * within such NOTICE file, excluding those notices that do not 111 | * pertain to any part of the Derivative Works, in at least one 112 | * of the following places: within a NOTICE text file distributed 113 | * as part of the Derivative Works; within the Source form or 114 | * documentation, if provided along with the Derivative Works; or, 115 | * within a display generated by the Derivative Works, if and 116 | * wherever such third-party notices normally appear. The contents 117 | * of the NOTICE file are for informational purposes only and 118 | * do not modify the License. You may add Your own attribution 119 | * notices within Derivative Works that You distribute, alongside 120 | * or as an addendum to the NOTICE text from the Work, provided 121 | * that such additional attribution notices cannot be construed 122 | * as modifying the License. 123 | * 124 | * You may add Your own copyright statement to Your modifications and 125 | * may provide additional or different license terms and conditions 126 | * for use, reproduction, or distribution of Your modifications, or 127 | * for any such Derivative Works as a whole, provided Your use, 128 | * reproduction, and distribution of the Work otherwise complies with 129 | * the conditions stated in this License. 130 | * 131 | * 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | * any Contribution intentionally submitted for inclusion in the Work 133 | * by You to the Licensor shall be under the terms and conditions of 134 | * this License, without any additional terms or conditions. 135 | * Notwithstanding the above, nothing herein shall supersede or modify 136 | * the terms of any separate license agreement you may have executed 137 | * with Licensor regarding such Contributions. 138 | * 139 | * 6. Trademarks. This License does not grant permission to use the trade 140 | * names, trademarks, service marks, or product names of the Licensor, 141 | * except as required for reasonable and customary use in describing the 142 | * origin of the Work and reproducing the content of the NOTICE file. 143 | * 144 | * 7. Disclaimer of Warranty. Unless required by applicable law or 145 | * agreed to in writing, Licensor provides the Work (and each 146 | * Contributor provides its Contributions) on an "AS IS" BASIS, 147 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | * implied, including, without limitation, any warranties or conditions 149 | * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | * PARTICULAR PURPOSE. You are solely responsible for determining the 151 | * appropriateness of using or redistributing the Work and assume any 152 | * risks associated with Your exercise of permissions under this License. 153 | * 154 | * 8. Limitation of Liability. In no event and under no legal theory, 155 | * whether in tort (including negligence), contract, or otherwise, 156 | * unless required by applicable law (such as deliberate and grossly 157 | * negligent acts) or agreed to in writing, shall any Contributor be 158 | * liable to You for damages, including any direct, indirect, special, 159 | * incidental, or consequential damages of any character arising as a 160 | * result of this License or out of the use or inability to use the 161 | * Work (including but not limited to damages for loss of goodwill, 162 | * work stoppage, computer failure or malfunction, or any and all 163 | * other commercial damages or losses), even if such Contributor 164 | * has been advised of the possibility of such damages. 165 | * 166 | * 9. Accepting Warranty or Additional Liability. While redistributing 167 | * the Work or Derivative Works thereof, You may choose to offer, 168 | * and charge a fee for, acceptance of support, warranty, indemnity, 169 | * or other liability obligations and/or rights consistent with this 170 | * License. However, in accepting such obligations, You may act only 171 | * on Your own behalf and on Your sole responsibility, not on behalf 172 | * of any other Contributor, and only if You agree to indemnify, 173 | * defend, and hold each Contributor harmless for any liability 174 | * incurred by, or claims asserted against, such Contributor by reason 175 | * of your accepting any such warranty or additional liability. 176 | * 177 | * END OF TERMS AND CONDITIONS 178 | * 179 | * APPENDIX: How to apply the Apache License to your work. 180 | * 181 | * To apply the Apache License to your work, attach the following 182 | * boilerplate notice, with the fields enclosed by brackets "[]" 183 | * replaced with your own identifying information. (Don't include 184 | * the brackets!) The text should be enclosed in the appropriate 185 | * comment syntax for the file format. We also recommend that a 186 | * file or class name and description of purpose be included on the 187 | * same "printed page" as the copyright notice for easier 188 | * identification within third-party archives. 189 | * 190 | * Copyright [yyyy] [name of copyright owner] 191 | * 192 | * Licensed under the Apache License, Version 2.0 (the "License"); 193 | * you may not use this file except in compliance with the License. 194 | * You may obtain a copy of the License at 195 | * 196 | * http://www.apache.org/licenses/LICENSE-2.0 197 | * 198 | * Unless required by applicable law or agreed to in writing, software 199 | * distributed under the License is distributed on an "AS IS" BASIS, 200 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | * See the License for the specific language governing permissions and 202 | * limitations under the License. 203 | */ -------------------------------------------------------------------------------- /licenses/JAVACC-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | -Redistribution of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | -Redistribution in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation and/or 10 | other materials provided with the distribution. 11 | 12 | Neither the name of Sun Microsystems, Inc. or the names of contributors may 13 | be used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | This software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17 | 18 | You acknowledge that this software is not designed, licensed or intended for use in the design, construction, operation or maintenance of any nuclear facility. 19 | -------------------------------------------------------------------------------- /licenses/JUNIT-LICENSE: -------------------------------------------------------------------------------- 1 | Common Public License Version 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | 11 | b) in the case of each subsequent Contributor: 12 | 13 | i) changes to the Program, and 14 | 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 18 | 19 | "Contributor" means any person or entity that distributes the Program. 20 | 21 | "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 22 | 23 | "Program" means the Contributions distributed in accordance with this Agreement. 24 | 25 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 26 | 27 | 2. GRANT OF RIGHTS 28 | 29 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 30 | 31 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 32 | 33 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 34 | 35 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 36 | 37 | 3. REQUIREMENTS 38 | 39 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 40 | 41 | a) it complies with the terms and conditions of this Agreement; and 42 | 43 | b) its license agreement: 44 | 45 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 46 | 47 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 48 | 49 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 50 | 51 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 52 | 53 | When the Program is made available in source code form: 54 | 55 | a) it must be made available under this Agreement; and 56 | 57 | b) a copy of this Agreement must be included with each copy of the Program. 58 | 59 | Contributors may not remove or alter any copyright notices contained within the Program. 60 | 61 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 62 | 63 | 4. COMMERCIAL DISTRIBUTION 64 | 65 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 66 | 67 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 68 | 69 | 5. NO WARRANTY 70 | 71 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 72 | 73 | 6. DISCLAIMER OF LIABILITY 74 | 75 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 76 | 77 | 7. GENERAL 78 | 79 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 80 | 81 | If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 82 | 83 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 84 | 85 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 86 | 87 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -------------------------------------------------------------------------------- /licenses/SCALA-LICENSE: -------------------------------------------------------------------------------- 1 | SCALA LICENSE 2 | 3 | Copyright (c) 2002-2010 EPFL, Lausanne, unless otherwise specified. 4 | All rights reserved. 5 | 6 | This software was developed by the Programming Methods Laboratory of the 7 | Swiss Federal Institute of Technology (EPFL), Lausanne, Switzerland. 8 | 9 | Permission to use, copy, modify, and distribute this software in source 10 | or binary form for any purpose with or without fee is hereby granted, 11 | provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright 14 | notice, this list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the EPFL nor the names of its contributors 21 | may be used to endorse or promote products derived from this 22 | software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 | SUCH DAMAGE. -------------------------------------------------------------------------------- /project/.build.properties.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onion-lang/onion/c0a4780b9c053426e454935ca61f26c128c923b8/project/.build.properties.un~ -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.13.0") -------------------------------------------------------------------------------- /run/Array.on: -------------------------------------------------------------------------------- 1 | arr = new String[3]; 2 | arr[0] = "foo"; 3 | arr[1] = "bar"; 4 | arr[2] = "baz"; 5 | for i = 0; i < arr.length; i = i + 1 { 6 | System::out.println(arr[i]); 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /run/Bean.on: -------------------------------------------------------------------------------- 1 | import { 2 | java.io.*; 3 | java.beans.*; 4 | } 5 | 6 | class ExampleBean <: Serializable { 7 | @name :String; 8 | @value :Int; 9 | 10 | public: 11 | def this { 12 | } 13 | 14 | def this(name :String, value :Int){ 15 | @name = name; 16 | @value = value; 17 | } 18 | 19 | def getName :String { 20 | return @name; 21 | } 22 | 23 | def setName(name :String) { 24 | @name = name; 25 | } 26 | 27 | def setValue(value :Int) { 28 | @value = value; 29 | } 30 | 31 | def getValue :Int { 32 | return @value; 33 | } 34 | 35 | def toString :String { 36 | return "ExampleBean(name = " + @name + ", value = " + @value + ")"; 37 | } 38 | } 39 | 40 | out = new ByteArrayOutputStream; 41 | 42 | encoder = new XMLEncoder(out); 43 | encoder.writeObject(new ExampleBean("HogeBean", 100)); 44 | encoder.close; 45 | 46 | decoder = new XMLDecoder(new ByteArrayInputStream(out.toByteArray())); 47 | bean = decoder.readObject$ExampleBean; 48 | System::out.println(bean); 49 | -------------------------------------------------------------------------------- /run/Calculator.on: -------------------------------------------------------------------------------- 1 | import { 2 | javax.swing.* 3 | java.awt.* 4 | java.awt.event.* 5 | } 6 | 7 | class Calculator : JFrame <: ActionListener { 8 | @text :JTextField 9 | @left :Long 10 | @right :Long 11 | @operator :String 12 | @isError :Boolean 13 | public: 14 | def setValue(value : Long) { @text.setText(JLong::toString(value)); } 15 | def setValue(value : String) { @text.setText(value); } 16 | def actionPerformed(event : ActionEvent) { 17 | label = event.source$JButton.label 18 | try{ 19 | if @isError && !(label == "C") { 20 | return 21 | } 22 | input = JLong::parseLong(label) 23 | if @operator != null { 24 | @right = @right * 10 + input 25 | setValue(@right) 26 | }else{ 27 | @left = @left * 10 + input 28 | setValue(@left) 29 | } 30 | }catch e :NumberFormatException{ 31 | if label == "C" { 32 | @left = 0 33 | @right = 0 34 | @operator = null 35 | @text.setText(JLong::toString(0)) 36 | @isError = false 37 | }else{ 38 | if @operator != null { 39 | if @operator == "/" { 40 | if @right == 0L{ 41 | setValue("0で割ることはできません。") 42 | @left = 0L 43 | @right = 0L 44 | @isError = true 45 | }else{ 46 | @left = @left / @right 47 | } 48 | setValue(@left) 49 | }else{ 50 | select @operator { 51 | case "+": @left = @left + @right 52 | case "-": @left = @left - @right 53 | case "*": @left = @left * @right 54 | case "/": @left = @left / @right 55 | } 56 | setValue(@left) 57 | } 58 | } 59 | @operator = label 60 | @right = 0 61 | } 62 | } 63 | } 64 | 65 | def this:("簡易電卓") { 66 | setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE) 67 | setSize(800, 600) 68 | @text = new JTextField 69 | @text.setHorizontalAlignment(JTextField::RIGHT) 70 | pane = getContentPane() 71 | north = new JPanel 72 | north.setLayout(new BorderLayout) 73 | north.add(@text, BorderLayout::NORTH) 74 | center= new JPanel 75 | center.setLayout(new GridLayout(4, 5, 4, 3)) 76 | center.setFont(new Font(null, Font::PLAIN, 8)) 77 | labels = [ 78 | "7", "8", "9", "C", 79 | "4", "5", "6", "*", 80 | "1", "2", "3", "-", 81 | "0", "=", "/", "+" 82 | ] 83 | foreach label:String in labels { 84 | button = new JButton(label$String) 85 | button.addActionListener(self) 86 | button.setPreferredSize(new Dimension(42, 28)) 87 | center.add(button) 88 | } 89 | pane.add(north, BorderLayout::NORTH) 90 | pane.add(center, BorderLayout::CENTER) 91 | } 92 | 93 | static def main(args :String[]){ 94 | frame = new Calculator 95 | frame.pack 96 | frame.setVisible(true) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /run/Delegation.on: -------------------------------------------------------------------------------- 1 | import { 2 | java.util.List; 3 | java.util.ArrayList; 4 | } 5 | 6 | class Delegation <: List { 7 | forward @n :List; 8 | public: 9 | def new { 10 | @n = new ArrayList; 11 | } 12 | static def main(args: String[]){ 13 | list = new Delegation; 14 | list << "a"; 15 | list << "b"; 16 | list << "c"; 17 | list << "d"; 18 | for i = 0; i < list.size; i = i + 1 { 19 | System::out.println(list[i]); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /run/Factorial.on: -------------------------------------------------------------------------------- 1 | def input(prompt :String, reader :BufferedReader) :String { 2 | System::out.print(prompt); 3 | return reader.readLine; 4 | } 5 | 6 | def fact(n :Int): Int { 7 | if n < 2 { return 1; } 8 | else { return n * fact(n - 1); } 9 | } 10 | 11 | reader = new BufferedReader(new InputStreamReader(System::in)); 12 | 13 | while (line = input("階乗を求めたい数を入力してください。> ", reader)) != null { 14 | try { 15 | number = JInteger::parseInt(line); 16 | if(number < 0){ 17 | System::out.println("正の整数を入力してください。"); 18 | }else{ 19 | System::out.println("fact(" + number + ") = " + fact(number)); 20 | } 21 | }catch e :NumberFormatException { 22 | System::out.println("整数を入力してください。"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /run/Foreach.on: -------------------------------------------------------------------------------- 1 | 2 | strings = new ArrayList; 3 | 4 | strings << "Foo"; 5 | strings << "Bar"; 6 | strings << "Hoge"; 7 | 8 | foreach object :String in strings { 9 | System::out.println(object.toLowerCase); 10 | } -------------------------------------------------------------------------------- /run/Hello.on: -------------------------------------------------------------------------------- 1 | IO::println("Hello") 2 | -------------------------------------------------------------------------------- /run/LineCounter.on: -------------------------------------------------------------------------------- 1 | def countLines(file: File): Int { 2 | reader = new BufferedReader(new FileReader(file)); 3 | count = 0; 4 | for line: String = null; (line = reader.readLine) != null; { 5 | count++; 6 | } 7 | return count; 8 | } 9 | def process(file: File, callback: Function1) { 10 | if(file == null) { return; } 11 | foreach myFile: File in file.listFiles { 12 | if myFile.isDirectory { 13 | process(myFile, callback); 14 | } else { 15 | callback.call(myFile); 16 | } 17 | } 18 | } 19 | lines = 0; 20 | foreach file: File in [new File("src"), new File("run")] { 21 | process(file, #(file: Object){ 22 | f = file$File; 23 | if f.name.endsWith(".scala") || f.name.endsWith(".java") || f.name.endsWith(".jj") { 24 | lines = lines + countLines(f); 25 | } 26 | }); 27 | } 28 | System::out.println("lines: " + lines); 29 | -------------------------------------------------------------------------------- /run/LineFilter.on: -------------------------------------------------------------------------------- 1 | def filterLines(filter :Function1) :String { 2 | reader = new BufferedReader(new InputStreamReader(System::in)); 3 | buffer = new StringBuffer; 4 | while (line = reader.readLine) != null { 5 | buffer.append(filter.call(line)$String); 6 | } 7 | return new String(buffer); 8 | } 9 | 10 | i = 1; 11 | 12 | lines = filterLines(#(line :Object){ 13 | newLine = i + ":" + line; 14 | i++; 15 | return newLine + System::getProperty("line.separator"); 16 | }); 17 | 18 | System::out.println(lines); -------------------------------------------------------------------------------- /run/List.on: -------------------------------------------------------------------------------- 1 | list = []; 2 | 3 | list << "a"; 4 | list << "b"; 5 | list << "c"; 6 | list << "d"; 7 | 8 | for i = 0; i < list.size; i = i + 1 { 9 | System::out.println(list[i]); 10 | } -------------------------------------------------------------------------------- /run/ReadLine.on: -------------------------------------------------------------------------------- 1 | line = IO::input("Please input a line: ") 2 | IO::println("You input: " + line) 3 | -------------------------------------------------------------------------------- /run/Select.on: -------------------------------------------------------------------------------- 1 | random = (Math::random() * 10)$Int; 2 | 3 | select random { 4 | case 0, 1, 2, 3: 5 | System::out.println("[0-3]"); 6 | case 4, 5, 6: 7 | System::out.println("[4-6]"); 8 | case 7, 8, 9: 9 | System::out.println("[7-9]"); 10 | else: 11 | System::out.println("10"); 12 | } -------------------------------------------------------------------------------- /run/StringCat.on: -------------------------------------------------------------------------------- 1 | list = ["a", "b", "c", "d", "e", "f", "g"]; 2 | for i = 0; i < list.size; i = i + 1 { 3 | System::out.println("list[" + i + "] = " + list[i]); 4 | } -------------------------------------------------------------------------------- /src/main/java/onion/Function0.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function0 { 4 | R call(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function1.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function1 { 4 | R call(A arg); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function10.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function10 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function2.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function2 { 4 | R call(A arg1, B arg2); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function3.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function3 { 4 | R call(A arg1, B arg2, C arg3); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function4.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function4 { 4 | R call(A arg1, B arg2, C arg3, D arg4); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function5.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function5 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function6.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function6 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5, F arg6); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function7.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function7 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function8.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function8 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/Function9.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | public interface Function9 { 4 | R call(A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/onion/IO.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | import java.io.IOException; 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.util.Scanner; 6 | 7 | public class IO { 8 | public static void print(Object o) { 9 | System.out.print(o); 10 | } 11 | 12 | public static void println(Object o) { 13 | System.out.println(o); 14 | } 15 | 16 | public static String readLine() { 17 | try(var scanner = new Scanner(System.in)) { 18 | return scanner.nextLine(); 19 | } 20 | } 21 | 22 | public static String input(String prompt) { 23 | try(var scanner = new Scanner(System.in)) { 24 | System.out.print(prompt); 25 | return scanner.nextLine(); 26 | } 27 | } 28 | 29 | public static String readAll() throws IOException{ 30 | return new String( 31 | System.in.readAllBytes(), 32 | System.getProperty("file.encoding") 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/onion/Iterables.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class Iterables { 12 | private static void _map(Iterable it, Function1 f, Collection result) { 13 | List newList = new ArrayList(); 14 | for(A arg: it) result.add(f.call(arg)); 15 | } 16 | public static List map(List list, Function1 f) { 17 | List result = new ArrayList(); 18 | _map(list, f, result); 19 | return result; 20 | } 21 | public static Iterable map(Iterable list, Function1 f) { 22 | return map(list, f); 23 | } 24 | public static Set map(Set set, Function1 f) { 25 | Set result = new HashSet(); 26 | _map(set, f, result); 27 | return result; 28 | } 29 | 30 | public static Map mapMap(Map map, Function1, Map.Entry> f) { 31 | Collection> mapView = View.asCollection(map); 32 | Map result = new HashMap(); 33 | _map(mapView, f, View.asCollection(result)); 34 | return result; 35 | } 36 | 37 | public static B foldl(Iterable it, B init, Function2 f) { 38 | B result = init; 39 | for(A a:it) result = f.call(result, a); 40 | return result; 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/onion/View.java: -------------------------------------------------------------------------------- 1 | package onion; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | 8 | public class View { 9 | public static Collection> asCollection( 10 | final Map map 11 | ) { 12 | return new Collection>() { 13 | public boolean add(Entry e) { 14 | map.put(e.getKey(), e.getValue()); 15 | return true; 16 | } 17 | 18 | public boolean addAll(Collection> c) { 19 | for(Map.Entry e:c) add(e); 20 | return true; 21 | } 22 | 23 | public void clear() { 24 | map.clear(); 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | public boolean contains(Object o) { 29 | if(o == null || !(o instanceof Map.Entry)) return false; 30 | Map.Entry m = (Map.Entry)o; 31 | return map.containsKey(m.getKey()) 32 | && map.get(m.getKey()).equals(m.getValue()); 33 | } 34 | 35 | public boolean containsAll(Collection c) { 36 | for(Object o:c) if(!contains(o)) return false; 37 | return true; 38 | } 39 | 40 | public boolean isEmpty() { 41 | return map.isEmpty(); 42 | } 43 | 44 | public Iterator> iterator() { 45 | return map.entrySet().iterator(); 46 | } 47 | 48 | @SuppressWarnings("unchecked") 49 | public boolean remove(Object o) { 50 | if(!(o instanceof Map.Entry)) return false; 51 | map.remove(((Map.Entry) o).getKey()); 52 | return true; 53 | } 54 | 55 | public boolean removeAll(Collection c) { 56 | for(Object o:c) remove(o); 57 | return true; 58 | } 59 | 60 | public boolean retainAll(Collection c) { 61 | for(Object o:c) { 62 | if(!contains(o)) remove(o); 63 | } 64 | return true; 65 | } 66 | 67 | public int size() { 68 | return map.size(); 69 | } 70 | 71 | public Object[] toArray() { 72 | return map.entrySet().toArray(); 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | public T[] toArray(T[] a) { 77 | return map.entrySet().toArray(a); 78 | } 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/onion/compiler/parser/ASTBuilderAdapter.java: -------------------------------------------------------------------------------- 1 | package onion.compiler.parser; 2 | 3 | import onion.compiler.*; 4 | import scala.collection.immutable.List; 5 | import scala.collection.mutable.ArrayBuffer; 6 | import scala.Option; 7 | import scala.Option$; 8 | 9 | /** 10 | * Java adapter for the Scala ASTBuilder trait to be used from JavaCC parser. 11 | * This class handles the Java-Scala interop complexities. 12 | */ 13 | public class ASTBuilderAdapter { 14 | private final ASTBuilder builder; 15 | 16 | public ASTBuilderAdapter(ASTBuilder builder) { 17 | this.builder = builder; 18 | } 19 | 20 | public ASTBuilderAdapter() { 21 | this(new DefaultASTBuilder()); 22 | } 23 | 24 | // Helper methods for Java-Scala interop 25 | @SuppressWarnings("unchecked") 26 | public static List toList(ArrayBuffer buffer) { 27 | return (List)buffer.toList(); 28 | } 29 | 30 | @SuppressWarnings("unchecked") 31 | public static List asList(A element) { 32 | ArrayBuffer buffer = new ArrayBuffer(); 33 | AST.append(buffer, element); 34 | return (List)buffer.toList(); 35 | } 36 | 37 | public static Option option(T value) { 38 | return Option$.MODULE$.apply(value); 39 | } 40 | 41 | public static Option none() { 42 | return Option$.MODULE$.empty(); 43 | } 44 | 45 | // Delegation methods 46 | public AST.CompilationUnit createCompilationUnit( 47 | Location location, 48 | String sourceFile, 49 | AST.ModuleDeclaration module, 50 | AST.ImportClause imports, 51 | List toplevels 52 | ) { 53 | return builder.createCompilationUnit(location, sourceFile, module, imports, toplevels); 54 | } 55 | 56 | public AST.ModuleDeclaration createModuleDeclaration(Location location, String name) { 57 | return builder.createModuleDeclaration(location, name); 58 | } 59 | 60 | public AST.ImportClause createImportClause(Location location, List> mapping) { 61 | return builder.createImportClause(location, mapping); 62 | } 63 | 64 | public AST.ClassDeclaration createClassDeclaration( 65 | Location location, 66 | int modifiers, 67 | String name, 68 | AST.TypeNode superClass, 69 | List interfaces, 70 | Option defaultSection, 71 | List sections 72 | ) { 73 | return builder.createClassDeclaration(location, modifiers, name, superClass, interfaces, defaultSection, sections); 74 | } 75 | 76 | public AST.InterfaceDeclaration createInterfaceDeclaration( 77 | Location location, 78 | int modifiers, 79 | String name, 80 | List superTypes, 81 | List methods 82 | ) { 83 | return builder.createInterfaceDeclaration(location, modifiers, name, superTypes, methods); 84 | } 85 | 86 | public AST.RecordDeclaration createRecordDeclaration( 87 | Location location, 88 | int modifiers, 89 | String name, 90 | List args 91 | ) { 92 | return builder.createRecordDeclaration(location, modifiers, name, args); 93 | } 94 | 95 | public AST.MethodDeclaration createMethodDeclaration( 96 | Location location, 97 | int modifiers, 98 | String name, 99 | List args, 100 | AST.TypeNode returnType, 101 | AST.BlockExpression body 102 | ) { 103 | return builder.createMethodDeclaration(location, modifiers, name, args, returnType, body); 104 | } 105 | 106 | public AST.ConstructorDeclaration createConstructorDeclaration( 107 | Location location, 108 | int modifiers, 109 | List args, 110 | List params, 111 | AST.BlockExpression body 112 | ) { 113 | return builder.createConstructorDeclaration(location, modifiers, args, params, body); 114 | } 115 | 116 | public AST.FieldDeclaration createFieldDeclaration( 117 | Location location, 118 | int modifiers, 119 | String name, 120 | AST.TypeNode typeRef, 121 | AST.Expression init 122 | ) { 123 | return builder.createFieldDeclaration(location, modifiers, name, typeRef, init); 124 | } 125 | 126 | public AST.BlockExpression createBlockExpression( 127 | Location location, 128 | List statements 129 | ) { 130 | return builder.createBlockExpression(location, statements); 131 | } 132 | 133 | public AST.Expression createBinaryExpression( 134 | Location location, 135 | String operator, 136 | AST.Expression lhs, 137 | AST.Expression rhs 138 | ) { 139 | return builder.createBinaryExpression(location, operator, lhs, rhs); 140 | } 141 | 142 | public AST.Expression createUnaryExpression( 143 | Location location, 144 | String operator, 145 | AST.Expression operand 146 | ) { 147 | return builder.createUnaryExpression(location, operator, operand); 148 | } 149 | 150 | public AST.Expression createLiteral(Location location, Object value) { 151 | return builder.createLiteral(location, value); 152 | } 153 | 154 | public AST.Id createIdentifier(Location location, String name) { 155 | return builder.createIdentifier(location, name); 156 | } 157 | 158 | public AST.TypeNode createTypeNode( 159 | Location location, 160 | AST.TypeDescriptor desc, 161 | boolean isRelaxed 162 | ) { 163 | return builder.createTypeNode(location, desc, isRelaxed); 164 | } 165 | 166 | public AST.Argument createArgument( 167 | Location location, 168 | String name, 169 | AST.TypeNode typeRef 170 | ) { 171 | return builder.createArgument(location, name, typeRef); 172 | } 173 | 174 | public AST.AccessSection createAccessSection( 175 | Location location, 176 | int modifiers, 177 | List members 178 | ) { 179 | return builder.createAccessSection(location, modifiers, members); 180 | } 181 | 182 | // Additional convenience methods for creating specific expressions 183 | public AST.Expression createAddition(Location location, AST.Expression lhs, AST.Expression rhs) { 184 | return new AST.Addition(location, lhs, rhs); 185 | } 186 | 187 | public AST.Expression createSubtraction(Location location, AST.Expression lhs, AST.Expression rhs) { 188 | return new AST.Subtraction(location, lhs, rhs); 189 | } 190 | 191 | public AST.Expression createMultiplication(Location location, AST.Expression lhs, AST.Expression rhs) { 192 | return new AST.Multiplication(location, lhs, rhs); 193 | } 194 | 195 | public AST.Expression createDivision(Location location, AST.Expression lhs, AST.Expression rhs) { 196 | return new AST.Division(location, lhs, rhs); 197 | } 198 | 199 | public AST.Expression createAssignment(Location location, AST.Expression lhs, AST.Expression rhs) { 200 | return new AST.Assignment(location, lhs, rhs); 201 | } 202 | 203 | public AST.Expression createMethodCall( 204 | Location location, 205 | AST.Expression receiver, 206 | String name, 207 | List args 208 | ) { 209 | return new AST.MethodCall(location, receiver, name, args); 210 | } 211 | 212 | public AST.Expression createNewObject( 213 | Location location, 214 | AST.TypeNode typeRef, 215 | List args 216 | ) { 217 | return new AST.NewObject(location, typeRef, args); 218 | } 219 | 220 | public AST.Expression createCurrentInstance(Location location) { 221 | return new AST.CurrentInstance(location); 222 | } 223 | 224 | public AST.Expression createCast(Location location, AST.Expression src, AST.TypeNode to) { 225 | return new AST.Cast(location, src, to); 226 | } 227 | 228 | public AST.Expression createIsInstance(Location location, AST.Expression target, AST.TypeNode typeRef) { 229 | return new AST.IsInstance(location, target, typeRef); 230 | } 231 | 232 | public AST.Expression createIndexing(Location location, AST.Expression lhs, AST.Expression rhs) { 233 | return new AST.Indexing(location, lhs, rhs); 234 | } 235 | 236 | public AST.Expression createListLiteral(Location location, List elements) { 237 | return new AST.ListLiteral(location, elements); 238 | } 239 | 240 | public AST.Expression createStringInterpolation(Location location, List parts, List expressions) { 241 | return new AST.StringInterpolation(location, parts, expressions); 242 | } 243 | 244 | // Statement creation methods 245 | public AST.ReturnExpression createReturn(Location location, AST.Expression value) { 246 | return new AST.ReturnExpression(location, value); 247 | } 248 | 249 | public AST.BreakExpression createBreak(Location location) { 250 | return new AST.BreakExpression(location); 251 | } 252 | 253 | public AST.ContinueExpression createContinue(Location location) { 254 | return new AST.ContinueExpression(location); 255 | } 256 | 257 | public AST.IfExpression createIf( 258 | Location location, 259 | AST.Expression cond, 260 | AST.BlockExpression pos, 261 | AST.BlockExpression neg 262 | ) { 263 | return new AST.IfExpression(location, cond, pos, neg); 264 | } 265 | 266 | public AST.WhileExpression createWhile( 267 | Location location, 268 | AST.Expression cond, 269 | AST.BlockExpression body 270 | ) { 271 | return new AST.WhileExpression(location, cond, body); 272 | } 273 | 274 | public AST.ForExpression createFor( 275 | Location location, 276 | AST.CompoundExpression init, 277 | AST.Expression cond, 278 | AST.Expression update, 279 | AST.BlockExpression body 280 | ) { 281 | return new AST.ForExpression(location, init, cond, update, body); 282 | } 283 | 284 | public AST.ForeachExpression createForeach( 285 | Location location, 286 | AST.Argument arg, 287 | AST.Expression collection, 288 | AST.BlockExpression body 289 | ) { 290 | return new AST.ForeachExpression(location, arg, collection, body); 291 | } 292 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/Manifest.mf: -------------------------------------------------------------------------------- 1 | Main-Class: onion.tools.CompilerFrontend 2 | Class-Path: lib/scala-compiler.jar lib/scala-library.jar lib/onion-library.jar 3 | -------------------------------------------------------------------------------- /src/main/resources/errorMessage.properties: -------------------------------------------------------------------------------- 1 | error.count={0} errors are found. 2 | error.command.noArgument={0} requires argument. 3 | error.command.invalidArgument={0} is invalid argument. 4 | error.command.requireNaturalNumber=value of {0} is required to natural number. 5 | error.command.invalidEncoding=value of {0} is not valid encoding name. 6 | error.parsing.read_error=read error: cannot read file {0}. 7 | error.parsing.syntax_error=Syntax error. Encountered "{0}", but expecting {1} ... or ... 8 | error.semantic.duplicatedClass=duplicated class definition {0}. 9 | error.semantic.duplicatedVariable=duplicated local variable definition {0}. 10 | error.semantic.duplicatedField=duplicated field definition {0}.{1}. 11 | error.semantic.duplicatedMethod=duplicated method definition {0}.{1}({2}). 12 | error.semantic.duplicatedConstructor=duplicated constructor definition {0}({1}). 13 | error.semantic.duplicatedGlobalVariable=duplicated global variable definition {0}. 14 | error.semantic.duplicatedFunction=duplicated function definition {0}. 15 | error.semantic.cyclicInheritance=inheritance relations which includes {0} have cyclicity. 16 | error.semantic.illegalInheritance=class {0} do inheritance illegally. 17 | error.semantic.illegalMethodCall=method {0}.{1} cannot be called. 18 | error.semantic.classNotFound=type {0} is not found. 19 | error.semantic.fieldNotFound=field {0}.{1} is not found. 20 | error.semantic.variableNotFound=local variable {0} is not found. 21 | error.semantic.methodNotFound=method applicable for {0}.{1}({2}) is not found. 22 | error.semantic.constructorNotFound=constructor applicable for {0}({1}) is not found. 23 | error.semantic.incompatibleOperandType=operator {0} is not applicable for type {1}. 24 | error.semantic.incompatibleType=type {0} is expected, but type {1} is used. 25 | error.semantic.ambiguousMethod=method call is applicable for both {0}.{1}({2}) and {3}.{4}({5}). 26 | error.semantic.ambiguousConstructor=constructor call is applicable for both {0}({1}) and {2}({3}). 27 | error.semantic.methodNotAccessible=method {0}.{1}({2}) is not accessible from class {3}. 28 | error.semantic.fieldNotAccessible=field {0}.{1} is not accessible from class {2}. 29 | error.semantic.classNotAccessible=class {0} is not accessible from class {1}. 30 | error.semantic.cyclicDelegation=this field definition has cyclic delegation. 31 | error.semantic.cannotReturnValue=this method cannot return value. 32 | error.semantic.interfaceRequired=interface required, but type {0} is used. 33 | error.semantic.unimplementedFeature=unimplemented feature is used. 34 | error.semantic.duplicateGeneratedMethod=duplicated generated method {0}.{1}({2}). 35 | error.semantic.isNotBoxableType=type {0} is not boxable type. 36 | error.semantic.lValueRequired=lvalue is required. -------------------------------------------------------------------------------- /src/main/resources/errorMessage_ja.properties: -------------------------------------------------------------------------------- 1 | error.count={0} \u500B\u306E\u30A8\u30E9\u30FC\u304C\u767A\u898B\u3055\u308C\u307E\u3057\u305F\u3002 2 | error.command.noArgument={0} \u306F\u5F15\u6570\u3092\u8981\u6C42\u3057\u307E\u3059\u3002 3 | error.command.invalidArgument={0} \u306F\u4E0D\u6B63\u306A\u5F15\u6570\u3067\u3059\u3002 4 | error.command.requireNaturalNumber={0} \u306E\u5024\u306F\u6B63\u306E\u6574\u6570\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093\u3002 5 | error.command.invalidEncoding={0} \u306E\u5024\u306F\u4E0D\u6B63\u306A\u30A8\u30F3\u30B3\u30FC\u30C7\u30A3\u30F3\u30B0\u540D\u3067\u3059\u3002 6 | error.parsing.read_error=\u8AAD\u307F\u8FBC\u307F\u30A8\u30E9\u30FC: \u30D5\u30A1\u30A4\u30EB {0} \u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002 7 | error.parsing.syntax_error=\u69CB\u6587\u30A8\u30E9\u30FC\uFF0EEncountered "{0}", but expecting {1} ... or ... 8 | error.semantic.duplicatedClass=\u30AF\u30E9\u30B9 {0} \u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 9 | error.semantic.duplicatedVariable=\u30ED\u30FC\u30AB\u30EB\u5909\u6570 {0} \u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 10 | error.semantic.duplicatedField=\u30D5\u30A3\u30FC\u30EB\u30C9 {0}.{1}\u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 11 | error.semantic.duplicatedMethod=\u30E1\u30BD\u30C3\u30C9 {0}.{1}({2})\u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 12 | error.semantic.duplicatedConstructor=\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF {0}({1})\u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 13 | error.semantic.duplicatedGlobalVariable=\u30B0\u30ED\u30FC\u30D0\u30EB\u5909\u6570{0}\u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 14 | error.semantic.duplicatedFunction=\u95A2\u6570{0}({2})\u306E\u5B9A\u7FA9\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 15 | error.semantic.cyclicInheritance={0} \u3092\u542B\u3080\u7D99\u627F\u95A2\u4FC2\u304C\u5FAA\u74B0\u3057\u3066\u3044\u307E\u3059\u3002 16 | error.semantic.illegalInheritance=\u30AF\u30E9\u30B9 {0} \u304C\u4E0D\u6B63\u306A\u7D99\u627F\u3092\u884C\u3063\u3066\u3044\u307E\u3059\u3002 17 | error.semantic.illegalMethodCall=\u30E1\u30BD\u30C3\u30C9 {0}.{1} \u3092\u547C\u3073\u51FA\u3059\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3002 18 | error.semantic.classNotFound=\u578B {0} \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 19 | error.semantic.fieldNotFound=\u30D5\u30A3\u30FC\u30EB\u30C9 {0}.{1} \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 20 | error.semantic.variableNotFound=\u30ED\u30FC\u30AB\u30EB\u5909\u6570 {0} \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 21 | error.semantic.methodNotFound=\u30E1\u30BD\u30C3\u30C9\u547C\u3073\u51FA\u3057{0}.{1}({2})\u306B\u9069\u5408\u3059\u308B\u30E1\u30BD\u30C3\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 22 | error.semantic.constructorNotFound=\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u547C\u3073\u51FA\u3057{0}({1})\u306B\u9069\u5408\u3059\u308B\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 23 | error.semantic.incompatibleOperandType=\u6F14\u7B97\u5B50 {0} \u306F\u3001\u578B {1} \u306B\u9069\u7528\u3067\u304D\u307E\u305B\u3093\u3002 24 | error.semantic.incompatibleType=\u578B {0} \u304C\u671F\u5F85\u3055\u308C\u3066\u3044\u308B\u7B87\u6240\u306B\u3001\u578B {1} \u3092\u4F7F\u7528\u3057\u307E\u3057\u305F\u3002 25 | error.semantic.ambiguousMethod=\u30E1\u30BD\u30C3\u30C9\u547C\u3073\u51FA\u3057\u304C\u3042\u3044\u307E\u3044\u3067\u3059\u3002{0}.{1}({2})\u3068{3}.{4}({5})\u306E\u4E21\u65B9\u306B\u9069\u5408\u3057\u307E\u3059\u3002 26 | error.semantic.ambiguousConstructor=\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u547C\u3073\u51FA\u3057\u304C\u3042\u3044\u307E\u3044\u3067\u3059\u3002{0}({1})\u3068{2}({3})\u306E\u4E21\u65B9\u306B\u9069\u5408\u3057\u307E\u3059\u3002 27 | error.semantic.methodNotAccessible=\u30E1\u30BD\u30C3\u30C9{0}.{1}({2})\u306F\u3001\u30AF\u30E9\u30B9{3}\u304B\u3089\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3002 28 | error.semantic.fieldNotAccessible=\u30D5\u30A3\u30FC\u30EB\u30C9{0}.{1}\u306F\u3001\u30AF\u30E9\u30B9{2}\u304B\u3089\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3002 29 | error.semantic.classNotAccessible=\u30AF\u30E9\u30B9{0}\u306F\u3001\u30AF\u30E9\u30B9{1}\u304B\u3089\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u305B\u3093\u3002 30 | error.semantic.cyclicDelegation=\u59D4\u8B72\u95A2\u4FC2\u304C\u5FAA\u74B0\u3057\u3066\u3044\u307E\u3059\u3002 31 | error.semantic.cannotReturnValue=\u3053\u306E\u30E1\u30BD\u30C3\u30C9\u304B\u3089\u5024\u3092\u8FD4\u3059\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002 32 | error.semantic.interfaceRequired=\u30A4\u30F3\u30BF\u30D5\u30A7\u30FC\u30B9\u304C\u8981\u6C42\u3055\u308C\u3066\u3044\u307E\u3059\u304C\u3001\u578B{0}\u3092\u4F7F\u7528\u3057\u307E\u3057\u305F\u3002 33 | error.semantic.unimplementedFeature=\u672A\u5B9F\u88C5\u306E\u6A5F\u80FD\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002 34 | error.semantic.duplicateGeneratedMethod=\u751F\u6210\u3055\u308C\u305F\u30E1\u30BD\u30C3\u30C9{0}.{1}({2})\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059\u3002 35 | error.semantic.isNotBoxableType=\u578B {0} \u306F\u3001\u30DC\u30C3\u30AF\u30B9\u5316\u53EF\u80FD\u306A\u578B\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002 36 | error.semantic.lValueRequired=lvalue required. -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/ASTBuilder.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import scala.collection.immutable.List 4 | 5 | trait ASTBuilder { 6 | def createCompilationUnit( 7 | location: Location, 8 | sourceFile: String, 9 | module: AST.ModuleDeclaration, 10 | imports: AST.ImportClause, 11 | toplevels: List[AST.Toplevel] 12 | ): AST.CompilationUnit 13 | 14 | def createModuleDeclaration(location: Location, name: String): AST.ModuleDeclaration 15 | 16 | def createImportClause(location: Location, mapping: List[(String, String)]): AST.ImportClause 17 | 18 | def createClassDeclaration( 19 | location: Location, 20 | modifiers: Int, 21 | name: String, 22 | superClass: AST.TypeNode, 23 | interfaces: List[AST.TypeNode], 24 | defaultSection: Option[AST.AccessSection], 25 | sections: List[AST.AccessSection] 26 | ): AST.ClassDeclaration 27 | 28 | def createInterfaceDeclaration( 29 | location: Location, 30 | modifiers: Int, 31 | name: String, 32 | superTypes: List[AST.TypeNode], 33 | methods: List[AST.MethodDeclaration] 34 | ): AST.InterfaceDeclaration 35 | 36 | def createRecordDeclaration( 37 | location: Location, 38 | modifiers: Int, 39 | name: String, 40 | args: List[AST.Argument] 41 | ): AST.RecordDeclaration 42 | 43 | def createMethodDeclaration( 44 | location: Location, 45 | modifiers: Int, 46 | name: String, 47 | args: List[AST.Argument], 48 | returnType: AST.TypeNode, 49 | body: AST.BlockExpression 50 | ): AST.MethodDeclaration 51 | 52 | def createConstructorDeclaration( 53 | location: Location, 54 | modifiers: Int, 55 | args: List[AST.Argument], 56 | params: List[AST.Expression], 57 | body: AST.BlockExpression 58 | ): AST.ConstructorDeclaration 59 | 60 | def createFieldDeclaration( 61 | location: Location, 62 | modifiers: Int, 63 | name: String, 64 | typeRef: AST.TypeNode, 65 | init: AST.Expression 66 | ): AST.FieldDeclaration 67 | 68 | def createBlockExpression( 69 | location: Location, 70 | statements: List[AST.CompoundExpression] 71 | ): AST.BlockExpression 72 | 73 | def createBinaryExpression( 74 | location: Location, 75 | operator: String, 76 | lhs: AST.Expression, 77 | rhs: AST.Expression 78 | ): AST.Expression 79 | 80 | def createUnaryExpression( 81 | location: Location, 82 | operator: String, 83 | operand: AST.Expression 84 | ): AST.Expression 85 | 86 | def createLiteral(location: Location, value: Any): AST.Expression 87 | 88 | def createIdentifier(location: Location, name: String): AST.Id 89 | 90 | def createTypeNode( 91 | location: Location, 92 | desc: AST.TypeDescriptor, 93 | isRelaxed: Boolean 94 | ): AST.TypeNode 95 | 96 | def createArgument( 97 | location: Location, 98 | name: String, 99 | typeRef: AST.TypeNode 100 | ): AST.Argument 101 | 102 | def createAccessSection( 103 | location: Location, 104 | modifiers: Int, 105 | members: List[AST.MemberDeclaration] 106 | ): AST.AccessSection 107 | } 108 | 109 | class DefaultASTBuilder extends ASTBuilder { 110 | def createCompilationUnit( 111 | location: Location, 112 | sourceFile: String, 113 | module: AST.ModuleDeclaration, 114 | imports: AST.ImportClause, 115 | toplevels: List[AST.Toplevel] 116 | ): AST.CompilationUnit = { 117 | AST.CompilationUnit(location, sourceFile, module, imports, toplevels) 118 | } 119 | 120 | def createModuleDeclaration(location: Location, name: String): AST.ModuleDeclaration = { 121 | AST.ModuleDeclaration(location, name) 122 | } 123 | 124 | def createImportClause(location: Location, mapping: List[(String, String)]): AST.ImportClause = { 125 | AST.ImportClause(location, mapping) 126 | } 127 | 128 | def createClassDeclaration( 129 | location: Location, 130 | modifiers: Int, 131 | name: String, 132 | superClass: AST.TypeNode, 133 | interfaces: List[AST.TypeNode], 134 | defaultSection: Option[AST.AccessSection], 135 | sections: List[AST.AccessSection] 136 | ): AST.ClassDeclaration = { 137 | AST.ClassDeclaration(location, modifiers, name, superClass, interfaces, defaultSection, sections) 138 | } 139 | 140 | def createInterfaceDeclaration( 141 | location: Location, 142 | modifiers: Int, 143 | name: String, 144 | superTypes: List[AST.TypeNode], 145 | methods: List[AST.MethodDeclaration] 146 | ): AST.InterfaceDeclaration = { 147 | AST.InterfaceDeclaration(location, modifiers, name, superTypes, methods) 148 | } 149 | 150 | def createRecordDeclaration( 151 | location: Location, 152 | modifiers: Int, 153 | name: String, 154 | args: List[AST.Argument] 155 | ): AST.RecordDeclaration = { 156 | AST.RecordDeclaration(location, modifiers, name, args) 157 | } 158 | 159 | def createMethodDeclaration( 160 | location: Location, 161 | modifiers: Int, 162 | name: String, 163 | args: List[AST.Argument], 164 | returnType: AST.TypeNode, 165 | body: AST.BlockExpression 166 | ): AST.MethodDeclaration = { 167 | AST.MethodDeclaration(location, modifiers, name, args, returnType, body) 168 | } 169 | 170 | def createConstructorDeclaration( 171 | location: Location, 172 | modifiers: Int, 173 | args: List[AST.Argument], 174 | params: List[AST.Expression], 175 | body: AST.BlockExpression 176 | ): AST.ConstructorDeclaration = { 177 | AST.ConstructorDeclaration(location, modifiers, args, params, body) 178 | } 179 | 180 | def createFieldDeclaration( 181 | location: Location, 182 | modifiers: Int, 183 | name: String, 184 | typeRef: AST.TypeNode, 185 | init: AST.Expression 186 | ): AST.FieldDeclaration = { 187 | AST.FieldDeclaration(location, modifiers, name, typeRef, init) 188 | } 189 | 190 | def createBlockExpression( 191 | location: Location, 192 | statements: List[AST.CompoundExpression] 193 | ): AST.BlockExpression = { 194 | AST.BlockExpression(location, statements) 195 | } 196 | 197 | def createBinaryExpression( 198 | location: Location, 199 | operator: String, 200 | lhs: AST.Expression, 201 | rhs: AST.Expression 202 | ): AST.Expression = { 203 | operator match { 204 | case "+" => AST.Addition(location, lhs, rhs) 205 | case "-" => AST.Subtraction(location, lhs, rhs) 206 | case "*" => AST.Multiplication(location, lhs, rhs) 207 | case "/" => AST.Division(location, lhs, rhs) 208 | case "%" => AST.Modulo(location, lhs, rhs) 209 | case "==" => AST.Equal(location, lhs, rhs) 210 | case "!=" => AST.NotEqual(location, lhs, rhs) 211 | case "<" => AST.LessThan(location, lhs, rhs) 212 | case "<=" => AST.LessOrEqual(location, lhs, rhs) 213 | case ">" => AST.GreaterThan(location, lhs, rhs) 214 | case ">=" => AST.GreaterOrEqual(location, lhs, rhs) 215 | case "&&" => AST.LogicalAnd(location, lhs, rhs) 216 | case "||" => AST.LogicalOr(location, lhs, rhs) 217 | case "=" => AST.Assignment(location, lhs, rhs) 218 | case "+=" => AST.AdditionAssignment(location, lhs, rhs) 219 | case "-=" => AST.SubtractionAssignment(location, lhs, rhs) 220 | case "*=" => AST.MultiplicationAssignment(location, lhs, rhs) 221 | case "/=" => AST.DivisionAssignment(location, lhs, rhs) 222 | case "%=" => AST.ModuloAssignment(location, lhs, rhs) 223 | case ":?" => AST.Elvis(location, lhs, rhs) 224 | case _ => throw new IllegalArgumentException(s"Unknown binary operator: $operator") 225 | } 226 | } 227 | 228 | def createUnaryExpression( 229 | location: Location, 230 | operator: String, 231 | operand: AST.Expression 232 | ): AST.Expression = { 233 | operator match { 234 | case "+" => AST.Posit(location, operand) 235 | case "-" => AST.Negate(location, operand) 236 | case "!" => AST.Not(location, operand) 237 | case "++" => AST.PostIncrement(location, operand) // Note: Parser needs to handle pre/post 238 | case "--" => AST.PostDecrement(location, operand) // Note: Parser needs to handle pre/post 239 | case _ => throw new IllegalArgumentException(s"Unknown unary operator: $operator") 240 | } 241 | } 242 | 243 | def createLiteral(location: Location, value: Any): AST.Expression = { 244 | value match { 245 | case b: Boolean => AST.BooleanLiteral(location, b) 246 | case c: Char => AST.CharacterLiteral(location, c) 247 | case i: Int => AST.IntegerLiteral(location, i) 248 | case l: Long => AST.LongLiteral(location, l) 249 | case f: Float => AST.FloatLiteral(location, f) 250 | case d: Double => AST.DoubleLiteral(location, d) 251 | case s: String => AST.StringLiteral(location, s) 252 | case null => AST.NullLiteral(location) 253 | case _ => throw new IllegalArgumentException(s"Unknown literal type: ${value.getClass}") 254 | } 255 | } 256 | 257 | def createIdentifier(location: Location, name: String): AST.Id = { 258 | AST.Id(location, name) 259 | } 260 | 261 | def createTypeNode( 262 | location: Location, 263 | desc: AST.TypeDescriptor, 264 | isRelaxed: Boolean 265 | ): AST.TypeNode = { 266 | AST.TypeNode(location, desc, isRelaxed) 267 | } 268 | 269 | def createArgument( 270 | location: Location, 271 | name: String, 272 | typeRef: AST.TypeNode 273 | ): AST.Argument = { 274 | AST.Argument(location, name, typeRef) 275 | } 276 | 277 | def createAccessSection( 278 | location: Location, 279 | modifiers: Int, 280 | members: List[AST.MemberDeclaration] 281 | ): AST.AccessSection = { 282 | AST.AccessSection(location, modifiers, members) 283 | } 284 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/AbstractTable.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import scala.collection.mutable 4 | 5 | class AbstractTable[E <: Named](protected val mapping: mutable.Map[String, E]) extends Iterable[E] { 6 | def add(entry: E): Unit = mapping(entry.name) = entry 7 | def get(key: String): Option[E] = mapping.get(key) 8 | def values: Seq[E] = mapping.values.toList 9 | def iterator: Iterator[E] = mapping.values.iterator 10 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/BytecodeGenerator.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | trait BytecodeGenerator: 4 | def process(classes: Seq[TypedAST.ClassDefinition]): Seq[CompiledClass] 5 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/ClassTable.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.util.{HashMap => JHashMap} 11 | import onion.compiler.environment.AsmRefs.AsmClassType 12 | import onion.compiler.environment.ClassFileTable 13 | import onion.compiler.environment.ReflectionRefs.ReflectClassType 14 | 15 | /** 16 | * @author Kota Mizushima 17 | * 18 | */ 19 | class ClassTable(classPath: String) { 20 | val classes = new OrderedTable[IRT.ClassDefinition] 21 | private val classFiles = new JHashMap[String, IRT.ClassType] 22 | private val arrayClasses = new JHashMap[String, IRT.ArrayType] 23 | private val table = new ClassFileTable(classPath) 24 | 25 | def loadArray(component: IRT.Type, dimension: Int): IRT.ArrayType = { 26 | val arrayName = "[" * dimension + component.name 27 | var array: IRT.ArrayType = arrayClasses.get(arrayName) 28 | if (array != null) return array 29 | array = new IRT.ArrayType(component, dimension, this) 30 | arrayClasses.put(arrayName, array) 31 | array 32 | } 33 | 34 | def load(className: String): IRT.ClassType = { 35 | var clazz: IRT.ClassType = lookup(className) 36 | if (clazz == null) { 37 | val bytes = table.loadBytes(className) 38 | if (bytes != null) { 39 | clazz = new AsmClassType(bytes, this) 40 | classFiles.put(clazz.name, clazz) 41 | } else { 42 | try { 43 | clazz = new ReflectClassType(Class.forName(className, true, Thread.currentThread.getContextClassLoader), this) 44 | classFiles.put(clazz.name, clazz) 45 | } 46 | catch { 47 | case e: ClassNotFoundException => {} 48 | } 49 | } 50 | } 51 | clazz 52 | } 53 | 54 | def rootClass: IRT.ClassType = load("java.lang.Object") 55 | 56 | def lookup(className: String): IRT.ClassType = { 57 | classes.get(className) match { 58 | case Some(ref) => ref 59 | case None => classFiles.get(className) 60 | } 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/ClosureLocalBinding.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | 4 | /** 5 | * @author Kota Mizushima 6 | * 7 | */ 8 | class ClosureLocalBinding(val frameIndex: Int, index: Int, `type`: IRT.Type) extends LocalBinding(index, `type`) { 9 | override def equals(other: Any): Boolean = { 10 | other match { 11 | case bind: ClosureLocalBinding => 12 | if (frameIndex != bind.frameIndex) false 13 | if (index != bind.index) false 14 | if (tp ne bind.tp) false 15 | else true 16 | case _ => 17 | false 18 | } 19 | } 20 | 21 | override def hashCode: Int = frameIndex + index + tp.hashCode 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/CompileError.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | case class CompileError(sourceFile: String, location: Location, message: String) 15 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/CompiledClass.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | import scala.beans.BeanProperty 10 | 11 | /** 12 | * @author Kota Mizushima 13 | * 14 | */ 15 | case class CompiledClass(@BeanProperty className: String, @BeanProperty outputPath: String, @BeanProperty content: Array[Byte]) 16 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/CompilerConfig.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | 11 | /** 12 | * @author Kota Mizushima 13 | * 14 | */ 15 | case class CompilerConfig(classPath: Seq[String], superClass: String, encoding: String, outputDirectory: String, maxErrorReports: Int) 16 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/FileInputSource.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.io.Reader 11 | import onion.compiler.toolbox.Inputs 12 | 13 | class FileInputSource(val name: String) extends InputSource { 14 | private lazy val reader: Reader = Inputs.newReader(name) 15 | 16 | def openReader: Reader = reader 17 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/ImportItem.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | case class ImportItem(simpleName : String, fqcn: Seq[String]) { 15 | val isOnDemand: Boolean = simpleName == "*" 16 | 17 | /** 18 | * generate fully qualified name from simple name. 19 | * @param simpleName 20 | * @return fqcn. if simpleName is not matched, then return null. 21 | */ 22 | def matches(simpleName: String): Option[String] = { 23 | if (isOnDemand) { 24 | if(fqcn.length == 0) None 25 | else Some(fqcn.take(fqcn.length - 1).appended(simpleName).mkString(".")) 26 | } else if (this.simpleName == simpleName) { 27 | Some(fqcn.mkString(("."))) 28 | } else { 29 | None 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/InputSource.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.io.Reader 11 | 12 | trait InputSource { 13 | def openReader: Reader 14 | def name: String 15 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/LocalBinding.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | /** 11 | * @author Kota Mizushima 12 | */ 13 | case class LocalBinding(index: Int, tp: IRT.Type) 14 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/LocalContext.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import toolbox.SymbolGenerator 11 | 12 | /** 13 | * @author Kota Mizushima 14 | * 15 | */ 16 | class LocalContext { 17 | private var contextFrame = new LocalFrame(null) 18 | private val generator = new SymbolGenerator("symbol#") 19 | var isClosure: Boolean = false 20 | var isStatic: Boolean = false 21 | var isGlobal: Boolean = false 22 | var method: IRT.Method = _ 23 | var constructor: IRT.ConstructorDefinition = _ 24 | private var isMethod: Boolean = false 25 | 26 | def setGlobal(isGlobal: Boolean): Unit = { 27 | this.isGlobal = isGlobal 28 | } 29 | 30 | def setClosure(isClosure: Boolean): Unit = { 31 | this.isClosure = isClosure 32 | } 33 | 34 | def setStatic(isStatic: Boolean): Unit = { 35 | this.isStatic = isStatic 36 | } 37 | 38 | def newName: String = { 39 | generator.generate 40 | } 41 | 42 | def returnType: IRT.Type = { 43 | if (isMethod) { 44 | method.returnType 45 | } else { 46 | IRT.BasicType.VOID 47 | } 48 | } 49 | 50 | def setMethod(method: IRT.Method): Unit = { 51 | this.method = method 52 | this.isMethod = true 53 | } 54 | 55 | def setConstructor(constructor: IRT.ConstructorDefinition): Unit = { 56 | this.constructor = constructor 57 | this.isMethod = false 58 | } 59 | 60 | def openFrame[A](block: => A): A = try { 61 | contextFrame = new LocalFrame(contextFrame) 62 | block 63 | } finally { 64 | contextFrame = contextFrame.parent 65 | } 66 | 67 | def depth: Int = { 68 | if (contextFrame == null) { 69 | -1 70 | } else { 71 | contextFrame.depth 72 | } 73 | } 74 | 75 | def getContextFrame: LocalFrame = { 76 | contextFrame 77 | } 78 | 79 | def setContextFrame(frame: LocalFrame): Unit = { 80 | this.contextFrame = frame 81 | } 82 | 83 | def openScope[A](body: => A): A = contextFrame.open(body) 84 | 85 | def lookup(name: String): ClosureLocalBinding = { 86 | contextFrame.lookup(name) 87 | } 88 | 89 | def lookupOnlyCurrentScope(name: String): ClosureLocalBinding = { 90 | contextFrame.lookupOnlyCurrentScope(name) 91 | } 92 | 93 | def add(name: String, `type` : IRT.Type): Int = { 94 | contextFrame.add(name, `type`) 95 | } 96 | 97 | def add(`type` : IRT.Type): String = { 98 | val name = newName 99 | contextFrame.add(name, `type`) 100 | name 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/LocalFrame.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.util.{Arrays => JArrays} 11 | import scala.collection.mutable 12 | 13 | /** 14 | * @author Kota Mizushima 15 | * 16 | */ 17 | class LocalFrame(val parent: LocalFrame) { 18 | var scope = new LocalScope(null) 19 | var closed: Boolean = false 20 | 21 | private val allScopes: mutable.Buffer[LocalScope] = mutable.Buffer[LocalScope]() 22 | private var maxIndex: Int = 0 23 | 24 | allScopes += scope 25 | 26 | def open[A](block: => A): A = { 27 | try { 28 | scope = new LocalScope(scope) 29 | allScopes += scope 30 | block 31 | } finally { 32 | scope = scope.parent 33 | } 34 | } 35 | 36 | def entries: Seq[LocalBinding] = { 37 | val binds: Array[LocalBinding] = entrySet.toArray 38 | JArrays.sort(binds, (b1: LocalBinding, b2: LocalBinding) => { 39 | val i1 = b1.index 40 | val i2 = b2.index 41 | if (i1 < i2) -1 else if (i1 > i2) 1 else 0 42 | }) 43 | binds.toSeq 44 | } 45 | 46 | def add(name: String, `type` : IRT.Type): Int = { 47 | val bind = scope.get(name) 48 | bind match { 49 | case Some(_) => -1 50 | case None => 51 | val index = maxIndex 52 | maxIndex += 1 53 | scope.put(name, LocalBinding(index, `type`)) 54 | index 55 | } 56 | } 57 | 58 | def lookup(name: String): ClosureLocalBinding = { 59 | var frame: LocalFrame = this 60 | var frameIndex: Int = 0 61 | while (frame != null) { 62 | val binding = frame.scope.lookup(name) 63 | if (binding != null) { 64 | return new ClosureLocalBinding(frameIndex, binding.index, binding.tp) 65 | } 66 | frameIndex += 1 67 | frame = frame.parent 68 | } 69 | null 70 | } 71 | 72 | def lookupOnlyCurrentScope(name: String): ClosureLocalBinding = { 73 | for(binding <- scope.get(name)) { 74 | return new ClosureLocalBinding(0, binding.index, binding.tp) 75 | } 76 | null 77 | } 78 | 79 | def setAllClosed(closed: Boolean): Unit = { 80 | var frame: LocalFrame = this 81 | while (frame != null) { 82 | frame.closed = closed 83 | frame = frame.parent 84 | } 85 | } 86 | 87 | def depth: Int = { 88 | var frame: LocalFrame = this 89 | var depth: Int = -1 90 | while (frame != null) { 91 | depth += 1 92 | frame = frame.parent 93 | } 94 | depth 95 | } 96 | 97 | private def entrySet: mutable.Set[LocalBinding] = { 98 | val entries = new mutable.HashSet[LocalBinding]() 99 | mutable.Set[LocalBinding]() ++ (for (s1 <- allScopes; s2 <- s1.entries) yield s2) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/LocalScope.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler; 9 | 10 | 11 | 12 | import onion.compiler.toolbox.Systems 13 | import scala.collection.mutable 14 | ; 15 | 16 | /** 17 | * a Local variable table. 18 | * @author Kota Mizushima 19 | */ 20 | class LocalScope(val parent: LocalScope) { 21 | private val bindings = mutable.HashMap[String, LocalBinding]() 22 | 23 | 24 | /** 25 | * Gets registered binding objects. 26 | * @return Set object which element is LocalBinding object 27 | */ 28 | def entries: mutable.Set[LocalBinding] = { 29 | mutable.HashSet[LocalBinding]() ++ bindings.values 30 | } 31 | 32 | /** 33 | * Tests if this scope contains entry for the given name. 34 | * @param name 35 | * @return true if this scope has entry, false otherwise 36 | */ 37 | def contains(name: String): Boolean = bindings.contains(name) 38 | 39 | /** 40 | * Registers binding object to this scope for the given name. 41 | * @param name 42 | * @param binding 43 | * @return true if already putted for given name, false otherwise 44 | */ 45 | def put(name: String, binding: LocalBinding): Boolean = { 46 | if(bindings.contains(name)){ 47 | true 48 | } else { 49 | bindings.put(name, binding) 50 | false 51 | } 52 | } 53 | 54 | /** 55 | * Gets the registered binding object from this scope for given name. 56 | * @param name 57 | * @return the LocalBinding object if registered, null otherwise 58 | */ 59 | def get(name: String): Option[LocalBinding] = bindings.get(name) 60 | 61 | /** 62 | * Finds the registered binding object from this scope and its ancestors 63 | * for given name. 64 | * @param name 65 | * @return the LocalBinding object if found, null otherwise 66 | */ 67 | def lookup(name: String): LocalBinding = { 68 | def find(table: LocalScope): LocalBinding = { 69 | if (table == null) { 70 | null 71 | } else if(table.contains(name)) { 72 | table.get(name).get 73 | } else { 74 | find(table.parent) 75 | } 76 | } 77 | find(this) 78 | } 79 | 80 | override def toString(): String = { 81 | val separator = Systems.lineSeparator 82 | val lines = for(name <- bindings.keySet) yield s" ${name}:${bindings(name).tp}" 83 | lines.mkString(s"[${separator}", separator, "]") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Location.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler; 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * Location in a Scala code. 13 | * @param line line number in the source. Note that it is 1 origin. 14 | * @param column column number in the source. It is 1 origin, too. 15 | */ 16 | final case class Location(line: Int, column: Int) 17 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Modifier.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler; 2 | /* ************************************************************** * 3 | * * 4 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 5 | * * 6 | * * 7 | * This software is distributed under the modified BSD License. * 8 | * ************************************************************** */ 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | object Modifier { 15 | 16 | final val INTERNAL = 1 17 | final val SYNCHRONIZED = 2 18 | final val FINAL = 4 19 | final val ABSTRACT = 8 20 | final val VOLATILE = 16 21 | final val STATIC = 32 22 | final val INHERITED = 64 23 | final val PUBLIC = 128 24 | final val PROTECTED = 256 25 | final val PRIVATE = 512 26 | final val FORWARDED = 1024 27 | 28 | def check(modifier: Int, bitFlag: Int): Boolean = { 29 | (modifier & bitFlag) != 0 30 | } 31 | 32 | def isInternal(modifier: Int): Boolean = { 33 | check(modifier, INTERNAL) 34 | } 35 | 36 | def isStatic(modifier: Int): Boolean = { 37 | check(modifier, STATIC) 38 | } 39 | 40 | def isSynchronized(modifier: Int): Boolean = { 41 | check(modifier, SYNCHRONIZED) 42 | } 43 | 44 | def isFinal(modifier: Int): Boolean = { 45 | check(modifier, FINAL) 46 | } 47 | 48 | def isAbstract(modifier: Int): Boolean = { 49 | check(modifier, ABSTRACT) 50 | } 51 | 52 | def isVolatile(modifier: Int): Boolean = { 53 | check(modifier, VOLATILE) 54 | } 55 | 56 | def isInherited(modifier: Int): Boolean = { 57 | check(modifier, INHERITED) 58 | } 59 | 60 | def isPublic(modifier: Int): Boolean = { 61 | check(modifier, PUBLIC) 62 | } 63 | 64 | def isProtected(modifier: Int): Boolean = { 65 | check(modifier, PROTECTED) 66 | } 67 | 68 | def isPrivate(modifier: Int): Boolean = { 69 | check(modifier, PRIVATE) 70 | } 71 | 72 | def isForwarded(modifier: Int): Boolean = { 73 | check(modifier, FORWARDED) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/MultiTable.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import scala.collection.Iterable 4 | import scala.collection.Iterator 5 | import scala.collection.mutable 6 | 7 | class MultiTable[E <: Named] extends Iterable[E] { 8 | private[this] final val mapping = new mutable.HashMap[String, mutable.Buffer[E]] 9 | 10 | def add(entry: E): Boolean = { 11 | mapping.get(entry.name) match { 12 | case Some(v) => 13 | v += entry 14 | true 15 | case None => 16 | val v = mutable.Buffer[E]() 17 | v += entry 18 | mapping(entry.name) = v 19 | false 20 | } 21 | } 22 | 23 | def get(key: String): Seq[E] = { 24 | (mapping.get(key) match { 25 | case None => 26 | val v = mutable.Buffer[E]() 27 | mapping(key) = v 28 | v 29 | case Some(v) => 30 | v 31 | }).toList 32 | } 33 | 34 | def values: Seq[E] = mapping.values.toList.flatten 35 | 36 | def iterator: Iterator[E] = values.iterator 37 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Named.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | trait Named { 4 | def name: String 5 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/OnionClassLoader.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.io.File 11 | import java.net._ 12 | import java.net.URL 13 | import java.net.URLClassLoader 14 | 15 | /** 16 | * @author Kota Mizushima 17 | * 18 | */ 19 | class OnionClassLoader @throws(classOf[MalformedURLException]) (parent: ClassLoader, classPath: Seq[String], classes: Seq[CompiledClass]) extends 20 | URLClassLoader(classPath.map(cp => new File(cp).toURI.toURL).toArray, parent) { 21 | 22 | classes.map(k => (k.className, k.content)).foreach{ case (className, content) => 23 | defineClass(className, content, 0, content.length) 24 | } 25 | 26 | @throws(classOf[ClassNotFoundException]) 27 | protected override def findClass(name: String): Class[_] = super.findClass(name) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/OnionCompiler.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import _root_.scala.collection.JavaConverters._ 11 | import _root_.scala.collection.Iterator 12 | import java.io.BufferedReader 13 | import java.io.IOException 14 | import java.io.FileNotFoundException 15 | import java.text.MessageFormat 16 | import onion.compiler.toolbox._ 17 | import onion.compiler.exceptions.CompilationException 18 | 19 | /** 20 | * @author Kota Mizushima 21 | * 22 | */ 23 | class OnionCompiler(val config: CompilerConfig) { 24 | 25 | def compile(fileNames: Array[String]): Seq[CompiledClass] = { 26 | compile(fileNames.map{new FileInputSource(_)}.toSeq) 27 | } 28 | 29 | def compile(srcs: Seq[InputSource]): Seq[CompiledClass] = { 30 | try { 31 | (new Parsing(config) 32 | andThen new Rewriting(config) 33 | andThen new Typing(config) 34 | andThen new TypedGenerating(config)).process(srcs) 35 | } catch { 36 | case e: CompilationException => 37 | for (error <- e.problems) printError(error) 38 | System.err.println(Message("error.count", e.size)) 39 | null 40 | } 41 | } 42 | 43 | private def printError(error: CompileError): Unit = { 44 | val location = error.location 45 | val sourceFile = error.sourceFile 46 | val message = new StringBuffer 47 | 48 | if (sourceFile == null) { 49 | message.append(MessageFormat.format("{0}", error.message)) 50 | } else { 51 | var line: String = null 52 | var lineNum: String = null 53 | try { 54 | line = if (location != null && sourceFile != null) getLine(sourceFile, location.line) else "" 55 | lineNum = if (location != null) Integer.toString(location.line) else "" 56 | } 57 | catch { 58 | case e: IOException => { 59 | e.printStackTrace 60 | } 61 | } 62 | message.append(MessageFormat.format("{0}:{1}: {2}", sourceFile, lineNum, error.message)) 63 | message.append(Systems.lineSeparator) 64 | message.append("\t\t") 65 | message.append(line) 66 | message.append(Systems.lineSeparator) 67 | message.append("\t\t") 68 | if (location != null) message.append(getCursor(location.column)) 69 | } 70 | System.err.println(new String(message)) 71 | } 72 | 73 | private def getCursor(column: Int): String = " " * (column - 1) + "^" 74 | 75 | private def getLine(sourceFile: String, lineNumber: Int): String = { 76 | try { 77 | val reader = Inputs.newReader(sourceFile) 78 | try { 79 | val line = Iterator.continually(reader.readLine()).takeWhile(_ != null).zipWithIndex.map { case (e, i) => (e, i + 1) }.find { 80 | case (e, i) => i == lineNumber 81 | } 82 | line.get._1 83 | } finally { 84 | reader.close 85 | } 86 | } catch { 87 | case e:FileNotFoundException => "" 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/OnionTypeConversion.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.util.HashMap 11 | import java.util.Map 12 | import org.objectweb.asm.Type 13 | 14 | /** 15 | * @author Kota Mizushima 16 | * 17 | */ 18 | object OnionTypeConversion { 19 | private final val asmTypeTable: Map[Int, IRT.BasicType] = new HashMap[Int, IRT.BasicType] 20 | private final val c2t: Map[Class[_], IRT.BasicType] = new HashMap[Class[_], IRT.BasicType] 21 | 22 | asmTypeTable.put(Type.BYTE, IRT.BasicType.BYTE) 23 | asmTypeTable.put(Type.SHORT, IRT.BasicType.SHORT) 24 | asmTypeTable.put(Type.CHAR, IRT.BasicType.CHAR) 25 | asmTypeTable.put(Type.INT, IRT.BasicType.INT) 26 | asmTypeTable.put(Type.LONG, IRT.BasicType.LONG) 27 | asmTypeTable.put(Type.FLOAT, IRT.BasicType.FLOAT) 28 | asmTypeTable.put(Type.DOUBLE, IRT.BasicType.DOUBLE) 29 | asmTypeTable.put(Type.BOOLEAN, IRT.BasicType.BOOLEAN) 30 | asmTypeTable.put(Type.VOID, IRT.BasicType.VOID) 31 | 32 | c2t.put(classOf[Byte], IRT.BasicType.BYTE) 33 | c2t.put(classOf[Short], IRT.BasicType.SHORT) 34 | c2t.put(classOf[Char], IRT.BasicType.CHAR) 35 | c2t.put(classOf[Int], IRT.BasicType.INT) 36 | c2t.put(classOf[Long], IRT.BasicType.LONG) 37 | c2t.put(classOf[Float], IRT.BasicType.FLOAT) 38 | c2t.put(classOf[Double], IRT.BasicType.DOUBLE) 39 | c2t.put(classOf[Boolean], IRT.BasicType.BOOLEAN) 40 | c2t.put(classOf[Unit], IRT.BasicType.VOID) 41 | } 42 | 43 | class OnionTypeConversion(table: ClassTable) { 44 | import OnionTypeConversion._ 45 | 46 | def toOnionType(klass: Class[_]): IRT.Type = { 47 | val returnType: IRT.Type = c2t.get(klass).asInstanceOf[IRT.Type] 48 | if (returnType != null) return returnType 49 | if (!klass.isArray) { 50 | val symbol: IRT.ClassType = table.load(klass.getName) 51 | if (symbol != null) { 52 | return symbol 53 | } 54 | else { 55 | return null 56 | } 57 | } 58 | if (klass.isArray) { 59 | var dimension: Int = 0 60 | var component: Class[_] = klass 61 | while(component.isArray) { 62 | dimension += 1 63 | component = component.getComponentType 64 | } 65 | val componentType: IRT.Type = toOnionType(component) 66 | return table.loadArray(componentType, dimension) 67 | } 68 | null 69 | } 70 | 71 | def toOnionType(asmType: Type): IRT.Type = { 72 | asmType.getSort match { 73 | case Type.VOID => IRT.BasicType.VOID 74 | case Type.BOOLEAN => IRT.BasicType.BOOLEAN 75 | case Type.BYTE => IRT.BasicType.BYTE 76 | case Type.SHORT => IRT.BasicType.SHORT 77 | case Type.CHAR => IRT.BasicType.CHAR 78 | case Type.INT => IRT.BasicType.INT 79 | case Type.LONG => IRT.BasicType.LONG 80 | case Type.FLOAT => IRT.BasicType.FLOAT 81 | case Type.DOUBLE => IRT.BasicType.DOUBLE 82 | case Type.OBJECT => 83 | val className = asmType.getClassName 84 | table.load(className) 85 | case Type.ARRAY => 86 | val elementType = asmType.getElementType 87 | val dimension = asmType.getDimensions 88 | val componentType = toOnionType(elementType) 89 | if (componentType != null) { 90 | table.loadArray(componentType, dimension) 91 | } else { 92 | null 93 | } 94 | case _ => null 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/OrderedTable.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import scala.collection.mutable 4 | 5 | class OrderedTable[E <: Named] extends AbstractTable[E](new mutable.LinkedHashMap[String, E]) 6 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Parser.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | trait Parser 11 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Parsing.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import collection.mutable.ArrayBuffer 4 | import collection.JavaConverters._ 5 | import java.io.{IOException, Reader} 6 | import java.util.Arrays.ArrayList 7 | import java.util.Collections 8 | 9 | import _root_.onion.compiler.toolbox.Message 10 | import _root_.onion.compiler.exceptions.CompilationException 11 | import _root_.onion.compiler.parser.{JJOnionParser, ParseException} 12 | 13 | class Parsing(config: CompilerConfig) extends AnyRef 14 | with Processor[Seq[InputSource], Seq[AST.CompilationUnit]] { 15 | type Environment = Null 16 | def newEnvironment(source: Seq[InputSource]): Null = null 17 | def processBody(source: Seq[InputSource], environment: Null): Seq[AST.CompilationUnit] = { 18 | def parse(reader: Reader, fileName: String): AST.CompilationUnit = { 19 | new JJOnionParser(reader).unit().copy(sourceFile = fileName) 20 | } 21 | val buffer = new ArrayBuffer[AST.CompilationUnit]() 22 | val problems = new ArrayBuffer[CompileError]() 23 | for(i <- source.indices) { 24 | try { 25 | buffer += parse(source(i).openReader, source(i).name) 26 | } catch { 27 | case e: IOException => 28 | problems += CompileError(null, null, Message("error.parsing.read_error", source(i).name)) 29 | case e: ParseException => 30 | val error = e.currentToken.next 31 | val expected = e.tokenImage(e.expectedTokenSequences(0)(0)) 32 | problems += CompileError(source(i).name, new Location(error.beginLine, error.beginColumn), Message("error.parsing.syntax_error", error.image, expected)) 33 | } 34 | } 35 | if(problems.nonEmpty) throw new CompilationException(problems.toSeq) 36 | buffer.toSeq 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Processor.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | trait Processor[A, B] {self => 4 | type Environment 5 | def newEnvironment(source: A): Environment 6 | protected def processBody(source: A, environment: Environment): B 7 | protected def preprocess(source: A): Unit = {} 8 | protected def postprocess(source: A, result: B): Unit = {} 9 | 10 | final def process(source: A): B = { 11 | preprocess(source) 12 | val result = processBody(source, newEnvironment(source)) 13 | postprocess(source, result) 14 | result 15 | } 16 | 17 | def andThen[C](nextUnit: Processor[B, C]): Processor[A, C] = new Processor[A, C] { 18 | type Environment = Null 19 | def newEnvironment(source: A): Environment = null 20 | def processBody(source: A, environment: Null): C = nextUnit.process(self.process(source)) 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Rewriting.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import java.util.{TreeSet => JTreeSet} 4 | 5 | import _root_.onion.compiler.IRT.BinaryTerm.Constants._ 6 | import _root_.onion.compiler.IRT.UnaryTerm.Constants._ 7 | import _root_.onion.compiler.IRT._ 8 | import _root_.onion.compiler.SemanticError._ 9 | import _root_.onion.compiler.exceptions.CompilationException 10 | import _root_.onion.compiler.toolbox.{Boxing, Classes, Paths, Systems} 11 | import onion.compiler.AST.{ClassDeclaration, InterfaceDeclaration, RecordDeclaration} 12 | 13 | import _root_.scala.collection.JavaConverters._ 14 | import scala.collection.mutable.{Buffer, Map, Set => MutableSet} 15 | 16 | class Rewriting(config: CompilerConfig) extends AnyRef with Processor[Seq[AST.CompilationUnit], Seq[AST.CompilationUnit]] { 17 | 18 | class TypingEnvironment 19 | 20 | type Environment = TypingEnvironment 21 | 22 | def newEnvironment(source: Seq[AST.CompilationUnit]) = new TypingEnvironment 23 | 24 | def processBody(source: Seq[AST.CompilationUnit], environment: TypingEnvironment): Seq[AST.CompilationUnit] = { 25 | val rewritten = Buffer.empty[AST.CompilationUnit] 26 | for (unit <- source) { 27 | rewritten += rewrite(unit) 28 | } 29 | rewritten.toSeq 30 | } 31 | 32 | def rewrite(unit: AST.CompilationUnit): AST.CompilationUnit = { 33 | val newToplevels = Buffer.empty[AST.Toplevel] 34 | for (top <- unit.toplevels) top match { 35 | case declaration: AST.ClassDeclaration => 36 | newToplevels += rewriteClassDeclaration(declaration) 37 | case declaration: AST.InterfaceDeclaration => 38 | newToplevels += rewriteInterfaceDeclaration(declaration) 39 | case declaration: AST.RecordDeclaration => 40 | newToplevels += rewriteRecordDeclaration(declaration) 41 | case otherwise => 42 | newToplevels += otherwise 43 | } 44 | unit.copy(toplevels = newToplevels.toList) 45 | } 46 | 47 | def rewriteClassDeclaration(declaration: ClassDeclaration): ClassDeclaration = { 48 | declaration 49 | } 50 | 51 | def rewriteInterfaceDeclaration(declaration: AST.InterfaceDeclaration): InterfaceDeclaration = { 52 | declaration 53 | } 54 | 55 | def rewriteRecordDeclaration(declaration: AST.RecordDeclaration): RecordDeclaration = { 56 | declaration 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/SemanticError.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | object SemanticError { 4 | case object INCOMPATIBLE_TYPE extends SemanticError(0) 5 | case object INCOMPATIBLE_OPERAND_TYPE extends SemanticError(1) 6 | case object VARIABLE_NOT_FOUND extends SemanticError(2) 7 | case object CLASS_NOT_FOUND extends SemanticError(3) 8 | case object FIELD_NOT_FOUND extends SemanticError(4) 9 | case object METHOD_NOT_FOUND extends SemanticError(5) 10 | case object AMBIGUOUS_METHOD extends SemanticError(6) 11 | case object DUPLICATE_LOCAL_VARIABLE extends SemanticError(7) 12 | case object DUPLICATE_CLASS extends SemanticError(8) 13 | case object DUPLICATE_FIELD extends SemanticError(9) 14 | case object DUPLICATE_METHOD extends SemanticError(10) 15 | case object DUPLICATE_GLOBAL_VARIABLE extends SemanticError(11) 16 | case object DUPLICATE_FUNCTION extends SemanticError(12) 17 | case object METHOD_NOT_ACCESSIBLE extends SemanticError(13) 18 | case object FIELD_NOT_ACCESSIBLE extends SemanticError(14) 19 | case object CLASS_NOT_ACCESSIBLE extends SemanticError(15) 20 | case object CYCLIC_INHERITANCE extends SemanticError(16) 21 | case object CYCLIC_DELEGATION extends SemanticError(17) 22 | case object ILLEGAL_INHERITANCE extends SemanticError(18) 23 | case object ILLEGAL_METHOD_CALL extends SemanticError(19) 24 | case object CANNOT_RETURN_VALUE extends SemanticError(20) 25 | case object CONSTRUCTOR_NOT_FOUND extends SemanticError(21) 26 | case object AMBIGUOUS_CONSTRUCTOR extends SemanticError(22) 27 | case object INTERFACE_REQUIRED extends SemanticError(23) 28 | case object UNIMPLEMENTED_FEATURE extends SemanticError(24) 29 | case object DUPLICATE_CONSTRUCTOR extends SemanticError(25) 30 | case object DUPLICATE_GENERATED_METHOD extends SemanticError(26) 31 | case object IS_NOT_BOXABLE_TYPE extends SemanticError(27) 32 | case object LVALUE_REQUIRED extends SemanticError(28) 33 | } 34 | sealed abstract class SemanticError(code: Int) -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/StaticImportItem.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | class StaticImportItem(val name: String, val fqcn: Boolean) { 15 | 16 | /** 17 | * returns name. 18 | */ 19 | def getName: String = name 20 | 21 | /** 22 | * returns whether name() is FQCN or not. 23 | * @return 24 | */ 25 | def isFqcn: Boolean = fqcn 26 | 27 | /** 28 | * matches name() with name. 29 | * @param name 30 | * @return if name is matched, then return true. 31 | */ 32 | def `match`(name: String): Boolean = { 33 | if (fqcn) { 34 | this.name.equals(name) 35 | } 36 | else { 37 | this.name.lastIndexOf(name) == this.name.length - name.length 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/StaticImportList.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.util.ArrayList 11 | import java.util.List 12 | import collection.mutable.ArrayBuffer 13 | 14 | /** 15 | * A type safe import list. 16 | * @author Kota Mizushima 17 | * 18 | */ 19 | class StaticImportList { 20 | private val items = new ArrayBuffer[StaticImportItem] 21 | 22 | def add(item: StaticImportItem): Unit = items += item 23 | 24 | def get(index: Int): StaticImportItem = items(index) 25 | 26 | def getItems: Array[StaticImportItem] = items.toArray 27 | 28 | def size: Int = items.size 29 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/StreamInputSource.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import java.io.Reader 11 | 12 | class StreamInputSource(val reader: Reader, val name: String) extends InputSource { 13 | def openReader: Reader = reader 14 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/StringInputSource.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | import java.io.{Reader, StringReader} 3 | 4 | case class StringInputSource(input: String) extends InputSource { 5 | override def openReader: Reader = new StringReader(input) 6 | 7 | override def name: String = "" 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/Symbol.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler 9 | 10 | import collection.mutable.HashMap 11 | 12 | /** 13 | * 14 | * @author Kota Mizushima 15 | * 16 | */ 17 | final object Symbol { 18 | private val symbols = new HashMap[String, Symbol] 19 | def apply(name: String): Symbol = { 20 | symbols.get(name) match { 21 | case Some(symbol) => symbol 22 | case None => 23 | val symbol = new Symbol(name) 24 | symbols(name) = symbol 25 | symbol 26 | } 27 | } 28 | 29 | } 30 | 31 | /** 32 | * Users cannot create Symbol object directly. 33 | * @param name 34 | */ 35 | final class Symbol private (val name: String) extends AnyRef { 36 | override def equals(obj: Any): Boolean = this eq obj.asInstanceOf[AnyRef] 37 | override def hashCode(): Int = super.hashCode() 38 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/TypedASTVisitor.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import TypedAST._ 4 | 5 | /** 6 | * Visitor pattern for TypedAST traversal. 7 | * This trait defines visit methods for all TypedAST node types. 8 | */ 9 | trait TypedASTVisitor[T]: 10 | // Expression visitors 11 | def visitArrayLength(node: ArrayLength): T 12 | def visitRefArray(node: RefArray): T 13 | def visitSetArray(node: SetArray): T 14 | def visitBegin(node: Begin): T 15 | def visitBinaryTerm(node: BinaryTerm): T 16 | def visitBoolValue(node: BoolValue): T 17 | def visitByteValue(node: ByteValue): T 18 | def visitCall(node: Call): T 19 | def visitCallStatic(node: CallStatic): T 20 | def visitCallSuper(node: CallSuper): T 21 | def visitAsInstanceOf(node: AsInstanceOf): T 22 | def visitCharacterValue(node: CharacterValue): T 23 | def visitDoubleValue(node: DoubleValue): T 24 | def visitFloatValue(node: FloatValue): T 25 | def visitInstanceOf(node: InstanceOf): T 26 | def visitIntValue(node: IntValue): T 27 | def visitListLiteral(node: ListLiteral): T 28 | def visitRefLocal(node: RefLocal): T 29 | def visitSetLocal(node: SetLocal): T 30 | def visitNewClosure(node: NewClosure): T 31 | def visitLongValue(node: LongValue): T 32 | def visitRefField(node: RefField): T 33 | def visitSetField(node: SetField): T 34 | def visitNewObject(node: NewObject): T 35 | def visitNewArray(node: NewArray): T 36 | def visitNullValue(node: NullValue): T 37 | def visitShortValue(node: ShortValue): T 38 | def visitRefStaticField(node: RefStaticField): T 39 | def visitSetStaticField(node: SetStaticField): T 40 | def visitStringValue(node: StringValue): T 41 | def visitOuterThis(node: OuterThis): T 42 | def visitThis(node: This): T 43 | def visitUnaryTerm(node: UnaryTerm): T 44 | 45 | // Statement visitors 46 | def visitStatementBlock(node: StatementBlock): T 47 | def visitBreak(node: Break): T 48 | def visitContinue(node: Continue): T 49 | def visitExpressionActionStatement(node: ExpressionActionStatement): T 50 | def visitIfStatement(node: IfStatement): T 51 | def visitConditionalLoop(node: ConditionalLoop): T 52 | def visitNOP(node: NOP): T 53 | def visitReturn(node: Return): T 54 | def visitSynchronized(node: Synchronized): T 55 | def visitThrow(node: Throw): T 56 | def visitTry(node: Try): T 57 | 58 | // Helper method to visit any Term 59 | def visitTerm(term: Term): T = term match 60 | case n: ArrayLength => visitArrayLength(n) 61 | case n: RefArray => visitRefArray(n) 62 | case n: SetArray => visitSetArray(n) 63 | case n: Begin => visitBegin(n) 64 | case n: BinaryTerm => visitBinaryTerm(n) 65 | case n: BoolValue => visitBoolValue(n) 66 | case n: ByteValue => visitByteValue(n) 67 | case n: Call => visitCall(n) 68 | case n: CallStatic => visitCallStatic(n) 69 | case n: CallSuper => visitCallSuper(n) 70 | case n: AsInstanceOf => visitAsInstanceOf(n) 71 | case n: CharacterValue => visitCharacterValue(n) 72 | case n: DoubleValue => visitDoubleValue(n) 73 | case n: FloatValue => visitFloatValue(n) 74 | case n: InstanceOf => visitInstanceOf(n) 75 | case n: IntValue => visitIntValue(n) 76 | case n: ListLiteral => visitListLiteral(n) 77 | case n: RefLocal => visitRefLocal(n) 78 | case n: SetLocal => visitSetLocal(n) 79 | case n: NewClosure => visitNewClosure(n) 80 | case n: LongValue => visitLongValue(n) 81 | case n: RefField => visitRefField(n) 82 | case n: SetField => visitSetField(n) 83 | case n: NewObject => visitNewObject(n) 84 | case n: NewArray => visitNewArray(n) 85 | case n: NullValue => visitNullValue(n) 86 | case n: ShortValue => visitShortValue(n) 87 | case n: RefStaticField => visitRefStaticField(n) 88 | case n: SetStaticField => visitSetStaticField(n) 89 | case n: StringValue => visitStringValue(n) 90 | case n: OuterThis => visitOuterThis(n) 91 | case n: This => visitThis(n) 92 | case n: UnaryTerm => visitUnaryTerm(n) 93 | 94 | // Helper method to visit any ActionStatement 95 | def visitStatement(stmt: ActionStatement): T = stmt match 96 | case n: StatementBlock => visitStatementBlock(n) 97 | case n: Break => visitBreak(n) 98 | case n: Continue => visitContinue(n) 99 | case n: ExpressionActionStatement => visitExpressionActionStatement(n) 100 | case n: IfStatement => visitIfStatement(n) 101 | case n: ConditionalLoop => visitConditionalLoop(n) 102 | case n: NOP => visitNOP(n) 103 | case n: Return => visitReturn(n) 104 | case n: Synchronized => visitSynchronized(n) 105 | case n: Throw => visitThrow(n) 106 | case n: Try => visitTry(n) 107 | 108 | /** 109 | * Base implementation of TypedASTVisitor that provides no-op default implementations. 110 | * Subclasses can override only the methods they need. 111 | */ 112 | abstract class DefaultTypedASTVisitor[T] extends TypedASTVisitor[T]: 113 | def defaultValue: T 114 | 115 | override def visitArrayLength(node: ArrayLength): T = defaultValue 116 | override def visitRefArray(node: RefArray): T = defaultValue 117 | override def visitSetArray(node: SetArray): T = defaultValue 118 | override def visitBegin(node: Begin): T = defaultValue 119 | override def visitBinaryTerm(node: BinaryTerm): T = defaultValue 120 | override def visitBoolValue(node: BoolValue): T = defaultValue 121 | override def visitByteValue(node: ByteValue): T = defaultValue 122 | override def visitCall(node: Call): T = defaultValue 123 | override def visitCallStatic(node: CallStatic): T = defaultValue 124 | override def visitCallSuper(node: CallSuper): T = defaultValue 125 | override def visitAsInstanceOf(node: AsInstanceOf): T = defaultValue 126 | override def visitCharacterValue(node: CharacterValue): T = defaultValue 127 | override def visitDoubleValue(node: DoubleValue): T = defaultValue 128 | override def visitFloatValue(node: FloatValue): T = defaultValue 129 | override def visitInstanceOf(node: InstanceOf): T = defaultValue 130 | override def visitIntValue(node: IntValue): T = defaultValue 131 | override def visitListLiteral(node: ListLiteral): T = defaultValue 132 | override def visitRefLocal(node: RefLocal): T = defaultValue 133 | override def visitSetLocal(node: SetLocal): T = defaultValue 134 | override def visitNewClosure(node: NewClosure): T = defaultValue 135 | override def visitLongValue(node: LongValue): T = defaultValue 136 | override def visitRefField(node: RefField): T = defaultValue 137 | override def visitSetField(node: SetField): T = defaultValue 138 | override def visitNewObject(node: NewObject): T = defaultValue 139 | override def visitNewArray(node: NewArray): T = defaultValue 140 | override def visitNullValue(node: NullValue): T = defaultValue 141 | override def visitShortValue(node: ShortValue): T = defaultValue 142 | override def visitRefStaticField(node: RefStaticField): T = defaultValue 143 | override def visitSetStaticField(node: SetStaticField): T = defaultValue 144 | override def visitStringValue(node: StringValue): T = defaultValue 145 | override def visitOuterThis(node: OuterThis): T = defaultValue 146 | override def visitThis(node: This): T = defaultValue 147 | override def visitUnaryTerm(node: UnaryTerm): T = defaultValue 148 | override def visitStatementBlock(node: StatementBlock): T = defaultValue 149 | override def visitBreak(node: Break): T = defaultValue 150 | override def visitContinue(node: Continue): T = defaultValue 151 | override def visitExpressionActionStatement(node: ExpressionActionStatement): T = defaultValue 152 | override def visitIfStatement(node: IfStatement): T = defaultValue 153 | override def visitConditionalLoop(node: ConditionalLoop): T = defaultValue 154 | override def visitNOP(node: NOP): T = defaultValue 155 | override def visitReturn(node: Return): T = defaultValue 156 | override def visitSynchronized(node: Synchronized): T = defaultValue 157 | override def visitThrow(node: Throw): T = defaultValue 158 | override def visitTry(node: Try): T = defaultValue -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/TypedGenerating.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | /** 4 | * Bridge generator that accepts typed AST and delegates to the old 5 | * CodeGeneration using IRT nodes. This eases migration away from IRT 6 | * without rewriting the backend in one go. 7 | */ 8 | class TypedGenerating(config: CompilerConfig) 9 | extends AnyRef 10 | with Processor[Seq[TypedAST.ClassDefinition], Seq[CompiledClass]]: 11 | class TypedGeneratingEnvironment 12 | type Environment = TypedGeneratingEnvironment 13 | def newEnvironment(source: Seq[TypedAST.ClassDefinition]): TypedGeneratingEnvironment = 14 | new TypedGeneratingEnvironment 15 | 16 | private val generator: BytecodeGenerator = new AsmCodeGeneration(config) 17 | 18 | def processBody(source: Seq[TypedAST.ClassDefinition], environment: TypedGeneratingEnvironment): Seq[CompiledClass] = 19 | generator.process(source) 20 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/environment/AsmRefs.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.environment 2 | 3 | import onion.compiler.{IRT, Modifier, OnionTypeConversion, MultiTable, OrderedTable, ClassTable} 4 | import org.objectweb.asm.{ClassReader, Opcodes, Type} 5 | import org.objectweb.asm.tree.{ClassNode, MethodNode, FieldNode} 6 | 7 | object AsmRefs { 8 | private def toOnionModifier(access: Int): Int = { 9 | var mod = 0 10 | if ((access & Opcodes.ACC_PRIVATE) != 0) mod |= Modifier.PRIVATE 11 | if ((access & Opcodes.ACC_PROTECTED) != 0) mod |= Modifier.PROTECTED 12 | if ((access & Opcodes.ACC_PUBLIC) != 0) mod |= Modifier.PUBLIC 13 | if ((access & Opcodes.ACC_STATIC) != 0) mod |= Modifier.STATIC 14 | if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) mod |= Modifier.SYNCHRONIZED 15 | if ((access & Opcodes.ACC_ABSTRACT) != 0) mod |= Modifier.ABSTRACT 16 | if ((access & Opcodes.ACC_FINAL) != 0) mod |= Modifier.FINAL 17 | mod 18 | } 19 | 20 | final val CONSTRUCTOR_NAME = "" 21 | 22 | class AsmMethodRef(method: MethodNode, override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.Method { 23 | override val modifier: Int = toOnionModifier(method.access) 24 | override val name: String = method.name 25 | private val argTypes = { 26 | val asmTypes = Type.getArgumentTypes(method.desc) 27 | val result = new Array[IRT.Type](asmTypes.length) 28 | var i = 0 29 | while (i < asmTypes.length) { 30 | result(i) = bridge.toOnionType(asmTypes(i)) 31 | i += 1 32 | } 33 | result 34 | } 35 | override def arguments: Array[IRT.Type] = argTypes.clone() 36 | override val returnType: IRT.Type = bridge.toOnionType(Type.getReturnType(method.desc)) 37 | val underlying: MethodNode = method 38 | } 39 | 40 | class AsmFieldRef(field: FieldNode, override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.FieldRef { 41 | override val modifier: Int = toOnionModifier(field.access) 42 | override val name: String = field.name 43 | override val `type`: IRT.Type = bridge.toOnionType(Type.getType(field.desc)) 44 | val underlying: FieldNode = field 45 | } 46 | 47 | class AsmConstructorRef(method: MethodNode, override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.ConstructorRef { 48 | override val modifier: Int = toOnionModifier(method.access) 49 | override val name: String = CONSTRUCTOR_NAME 50 | private val args0 = { 51 | val asmTypes = Type.getArgumentTypes(method.desc) 52 | val result = new Array[IRT.Type](asmTypes.length) 53 | var i = 0 54 | while (i < asmTypes.length) { 55 | result(i) = bridge.toOnionType(asmTypes(i)) 56 | i += 1 57 | } 58 | result 59 | } 60 | override def getArgs: Array[IRT.Type] = args0.clone() 61 | val underlying: MethodNode = method 62 | } 63 | 64 | class AsmClassType(classBytes: Array[Byte], table: ClassTable) extends IRT.AbstractClassType { 65 | private val node = { 66 | val cr = new ClassReader(classBytes) 67 | val n = new ClassNode() 68 | cr.accept(n, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) 69 | n 70 | } 71 | 72 | private val bridge = new OnionTypeConversion(table) 73 | private val modifier_ = toOnionModifier(node.access) 74 | 75 | private lazy val methods_ : MultiTable[IRT.Method] = { 76 | val m = new MultiTable[IRT.Method] 77 | import scala.jdk.CollectionConverters._ 78 | for (method <- node.methods.asInstanceOf[java.util.List[MethodNode]].asScala if method.name != CONSTRUCTOR_NAME) { 79 | m.add(new AsmMethodRef(method, this, bridge)) 80 | } 81 | m 82 | } 83 | 84 | private lazy val fields_ : OrderedTable[IRT.FieldRef] = { 85 | val f = new OrderedTable[IRT.FieldRef] 86 | import scala.jdk.CollectionConverters._ 87 | for (field <- node.fields.asInstanceOf[java.util.List[FieldNode]].asScala) { 88 | f.add(new AsmFieldRef(field, this, bridge)) 89 | } 90 | f 91 | } 92 | 93 | private lazy val constructors_ : Seq[IRT.ConstructorRef] = { 94 | import scala.jdk.CollectionConverters._ 95 | node.methods.asInstanceOf[java.util.List[MethodNode]].asScala.collect { 96 | case m if m.name == CONSTRUCTOR_NAME => new AsmConstructorRef(m, this, bridge) 97 | }.toSeq 98 | } 99 | 100 | def isInterface: Boolean = (node.access & Opcodes.ACC_INTERFACE) != 0 101 | def modifier: Int = modifier_ 102 | def name: String = node.name.replace('/', '.') 103 | def superClass: IRT.ClassType = { 104 | if (node.superName == null) null else table.load(node.superName.replace('/', '.')) 105 | } 106 | def interfaces: Seq[IRT.ClassType] = { 107 | import scala.jdk.CollectionConverters._ 108 | node.interfaces.asInstanceOf[java.util.List[String]].asScala.map(n => table.load(n.replace('/', '.'))).toIndexedSeq 109 | } 110 | 111 | def methods: Seq[IRT.Method] = methods_.values 112 | def methods(name: String): Array[IRT.Method] = methods_.get(name).toArray 113 | def fields: Array[IRT.FieldRef] = fields_.values.toArray 114 | def field(name: String): IRT.FieldRef = fields_.get(name).orNull 115 | def constructors: Array[IRT.ConstructorRef] = constructors_.toArray 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/environment/ClassFileTable.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.environment 9 | 10 | import java.io._ 11 | import java.net.{URL, URLClassLoader} 12 | import java.nio.file.{Files, Paths} 13 | import scala.jdk.CollectionConverters._ 14 | 15 | /** 16 | * @author Kota Mizushima 17 | * 18 | */ 19 | class ClassFileTable(classPathString: String) { 20 | private val classLoader: ClassLoader = createClassLoader(classPathString) 21 | 22 | private def createClassLoader(classPath: String): ClassLoader = { 23 | val urls = classPath.split(File.pathSeparator).map { path => 24 | val file = new File(path) 25 | file.toURI.toURL 26 | } 27 | new URLClassLoader(urls, Thread.currentThread().getContextClassLoader) 28 | } 29 | 30 | /** 31 | * Load class bytes for the given class name 32 | * @param className fully qualified class name 33 | * @return byte array of the class file, or null if not found 34 | */ 35 | def loadBytes(className: String): Array[Byte] = { 36 | val resourcePath = className.replace('.', '/') + ".class" 37 | val inputStream = classLoader.getResourceAsStream(resourcePath) 38 | if (inputStream == null) return null 39 | 40 | try { 41 | val out = new ByteArrayOutputStream() 42 | val buf = new Array[Byte](8192) 43 | var len = inputStream.read(buf) 44 | while (len != -1) { 45 | out.write(buf, 0, len) 46 | len = inputStream.read(buf) 47 | } 48 | out.toByteArray 49 | } catch { 50 | case _: IOException => null 51 | } finally { 52 | inputStream.close() 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/environment/ReflectionRefs.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.environment 9 | 10 | import onion.compiler.{IRT, Modifier, OnionTypeConversion, MultiTable, OrderedTable, ClassTable} 11 | import java.lang.reflect.{Constructor, Field, Method} 12 | import java.util.{ArrayList, List} 13 | 14 | object ReflectionRefs { 15 | private def toOnionModifier(src: Int): Int = { 16 | ((if ((src & java.lang.reflect.Modifier.PRIVATE) != 0) Modifier.PRIVATE else 0) 17 | | (if ((src & java.lang.reflect.Modifier.PROTECTED) != 0) Modifier.PROTECTED else 0) 18 | | (if ((src & java.lang.reflect.Modifier.PUBLIC) != 0) Modifier.PUBLIC else 0) 19 | | (if ((src & java.lang.reflect.Modifier.STATIC) != 0) Modifier.STATIC else 0) 20 | | (if ((src & java.lang.reflect.Modifier.SYNCHRONIZED) != 0) Modifier.SYNCHRONIZED else 0) 21 | | (if ((src & java.lang.reflect.Modifier.ABSTRACT) != 0) Modifier.ABSTRACT else 0) 22 | | (if ((src & java.lang.reflect.Modifier.FINAL) != 0) Modifier.FINAL else 0)) 23 | } 24 | 25 | final val CONSTRUCTOR_NAME = "" 26 | 27 | class ReflectMethodRef(method: Method, override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.Method { 28 | override val modifier: Int = toOnionModifier(method.getModifiers) 29 | override val name: String = method.getName 30 | private val argTypes: Array[IRT.Type] = method.getParameterTypes.map(t => bridge.toOnionType(t)) 31 | override def arguments: Array[IRT.Type] = argTypes.clone() 32 | override val returnType: IRT.Type = bridge.toOnionType(method.getReturnType) 33 | val underlying: Method = method 34 | } 35 | 36 | class ReflectFieldRef(field: Field, override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.FieldRef { 37 | override val modifier: Int = toOnionModifier(field.getModifiers) 38 | override val name: String = field.getName 39 | override val `type`: IRT.Type = bridge.toOnionType(field.getType) 40 | val underlying: Field = field 41 | } 42 | 43 | class ReflectConstructorRef(constructor: Constructor[_], override val affiliation: IRT.ClassType, bridge: OnionTypeConversion) extends IRT.ConstructorRef { 44 | override val modifier: Int = toOnionModifier(constructor.getModifiers) 45 | override val name: String = "" 46 | private val args0: Array[IRT.Type] = constructor.getParameterTypes.map(t => bridge.toOnionType(t)) 47 | override def getArgs: Array[IRT.Type] = args0.clone() 48 | val underlying: Constructor[_] = constructor 49 | } 50 | 51 | class ReflectClassType(klass: Class[_], table: ClassTable) extends IRT.AbstractClassType { 52 | private val bridge: OnionTypeConversion = new OnionTypeConversion(table) 53 | private val modifier_ : Int = toOnionModifier(klass.getModifiers) 54 | private var methods_ : MultiTable[IRT.Method] = _ 55 | private var fields_ : OrderedTable[IRT.FieldRef] = _ 56 | private var constructors_ : List[IRT.ConstructorRef] = _ 57 | 58 | def isInterface: Boolean = (klass.getModifiers & java.lang.reflect.Modifier.INTERFACE) != 0 59 | 60 | def modifier: Int = modifier_ 61 | 62 | def name: String = klass.getName 63 | 64 | def superClass: IRT.ClassType = { 65 | val superKlass: Class[_] = klass.getSuperclass 66 | if (superKlass == null) return table.rootClass 67 | val superClass: IRT.ClassType = table.load(superKlass.getName) 68 | if (superClass eq this) null else superClass 69 | } 70 | 71 | def interfaces: Seq[IRT.ClassType] = { 72 | val interfacesArr: Array[Class[_]] = klass.getInterfaces 73 | val interfaceSyms: Array[IRT.ClassType] = new Array[IRT.ClassType](interfacesArr.length) 74 | for(i <- interfacesArr.indices) { 75 | interfaceSyms(i) = table.load(interfacesArr(i).getName) 76 | } 77 | interfaceSyms.toIndexedSeq 78 | } 79 | 80 | def methods: Seq[IRT.Method] = { 81 | requireMethodTable() 82 | methods_.values 83 | } 84 | 85 | def methods(name: String): Array[IRT.Method] = { 86 | requireMethodTable() 87 | methods_.get(name).toArray 88 | } 89 | 90 | private def requireMethodTable(): Unit = { 91 | if (methods_ == null) { 92 | methods_ = new MultiTable[IRT.Method] 93 | for (method <- klass.getMethods if method.getName != CONSTRUCTOR_NAME) { 94 | methods_.add(new ReflectMethodRef(method, this, bridge)) 95 | } 96 | } 97 | } 98 | 99 | def fields: Array[IRT.FieldRef] = { 100 | requireFieldTable() 101 | fields_.values.toArray 102 | } 103 | 104 | def field(name: String): IRT.FieldRef = { 105 | requireFieldTable() 106 | fields_.get(name).orNull 107 | } 108 | 109 | private def requireFieldTable(): Unit = { 110 | if (fields_ == null) { 111 | fields_ = new OrderedTable[IRT.FieldRef] 112 | for (field <- klass.getFields) { 113 | fields_.add(new ReflectFieldRef(field, this, bridge)) 114 | } 115 | } 116 | } 117 | 118 | def constructors: Array[IRT.ConstructorRef] = { 119 | if (constructors_ == null) { 120 | constructors_ = new ArrayList[IRT.ConstructorRef] 121 | for (ctor <- klass.getConstructors) { 122 | constructors_.add(new ReflectConstructorRef(ctor, this, bridge)) 123 | } 124 | } 125 | constructors_.toArray(new Array[IRT.ConstructorRef](0)) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/exceptions/CompilationException.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.exceptions 9 | 10 | import onion.compiler.CompileError 11 | 12 | /** 13 | * @author Kota Mizushima 14 | * Exception thrown when compilation errors occured. 15 | */ 16 | class CompilationException(val problems : Seq[CompileError]) extends RuntimeException with Iterable[CompileError] { 17 | 18 | override def size: Int = problems.size 19 | 20 | override def iterator: Iterator[CompileError] = problems.iterator 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/exceptions/ScriptException.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.exceptions 9 | 10 | /** 11 | * This class represents an exception while script is running. 12 | * 13 | * @author Kota Mizushima 14 | * 15 | */ 16 | class ScriptException(message: String, cause: Throwable) extends RuntimeException(message, cause) { 17 | /** 18 | * @param cause 19 | */ 20 | def this(cause: Throwable) = { 21 | this("", cause) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Boxing.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import onion.compiler.IRT 11 | import onion.compiler.ClassTable 12 | 13 | /** 14 | * @author Kota Mizushima 15 | * 16 | */ 17 | object Boxing { 18 | private final val TABLE: Array[Array[AnyRef]] = Array( 19 | Array[AnyRef](IRT.BasicType.BOOLEAN, "java.lang.Boolean"), 20 | Array[AnyRef](IRT.BasicType.BYTE, "java.lang.Byte"), 21 | Array[AnyRef](IRT.BasicType.SHORT, "java.lang.Short"), 22 | Array[AnyRef](IRT.BasicType.CHAR, "java.lang.Character"), 23 | Array[AnyRef](IRT.BasicType.INT, "java.lang.Integer"), 24 | Array[AnyRef](IRT.BasicType.LONG, "java.lang.Long"), 25 | Array[AnyRef](IRT.BasicType.FLOAT, "java.lang.Float"), 26 | Array[AnyRef](IRT.BasicType.DOUBLE, "java.lang.Double") 27 | ) 28 | 29 | private def boxedType(table: ClassTable, `type`: IRT.BasicType): IRT.ClassType = { 30 | for (row <- TABLE) { 31 | if (row(0) eq `type`) return table.load(row(1).asInstanceOf[String]) 32 | } 33 | throw new RuntimeException("") 34 | } 35 | 36 | def boxing(table: ClassTable, node: IRT.Term): IRT.Term = { 37 | val `type`: IRT.Type = node.`type` 38 | if ((!`type`.isBasicType) || (`type` eq IRT.BasicType.VOID)) { 39 | throw new IllegalArgumentException("node type must be boxable type") 40 | } 41 | val aBoxedType: IRT.ClassType = boxedType(table, `type`.asInstanceOf[IRT.BasicType]) 42 | val cs: Array[IRT.ConstructorRef] = aBoxedType.constructors 43 | for(i <- cs.indices) { 44 | val args: Array[IRT.Type] = cs(i).getArgs 45 | if ((args.length == 1) && (args(i) eq `type`)) { 46 | return new IRT.NewObject(cs(i), Array[IRT.Term](node)) 47 | } 48 | } 49 | throw new RuntimeException("couldn't find matched constructor") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Classes.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import java.util.Arrays 11 | import java.util.Set 12 | import java.util.TreeSet 13 | import onion.compiler.IRT 14 | 15 | /** 16 | * @author Kota Mizushima 17 | * 18 | */ 19 | object Classes { 20 | def getInterfaceMethods(`type`: IRT.ClassType): Set[IRT.Method] = { 21 | val methods: Set[IRT.Method] = new TreeSet[IRT.Method](new IRT.MethodComparator) 22 | collectInterfaceMethods(`type`, methods) 23 | methods 24 | } 25 | 26 | private def collectInterfaceMethods(`type`: IRT.ClassType, set: Set[IRT.Method]): Unit = { 27 | val methods = `type`.methods 28 | set.addAll(Arrays.asList(methods:_*)) 29 | val interfaces: Seq[IRT.ClassType] = `type`.interfaces 30 | for (anInterface <- interfaces) { 31 | collectInterfaceMethods(anInterface, set) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Inputs.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import java.io._ 11 | import java.io.BufferedReader 12 | 13 | /** 14 | * Utility class for IO. 15 | * @author Kota Mizushima 16 | * 17 | */ 18 | object Inputs { 19 | def newReader(path: String): BufferedReader = new BufferedReader(new FileReader(new File(path))) 20 | 21 | def newReader(path: String, encoding: String): BufferedReader = { 22 | new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)), encoding)) 23 | } 24 | 25 | def newWriter(path: String): PrintWriter = { 26 | new PrintWriter(new BufferedWriter(new FileWriter(path))) 27 | } 28 | 29 | def newInputStream(path: String): BufferedInputStream = { 30 | new BufferedInputStream(new FileInputStream(path)) 31 | } 32 | 33 | def newOutputStream(path: String): BufferedOutputStream = { 34 | new BufferedOutputStream(new FileOutputStream(path)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/InvocationException.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.toolbox 2 | 3 | class InvocationException(reason: Exception) extends RuntimeException(reason) 4 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Message.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import java.text.MessageFormat 11 | import java.util.ResourceBundle 12 | 13 | /** 14 | * @author Kota Mizushima 15 | * 16 | */ 17 | object Message { 18 | private[this] val ERROR_MESSAGES: ResourceBundle = ResourceBundle.getBundle("errorMessage") 19 | 20 | def apply(property: String): String = ERROR_MESSAGES.getString(property) 21 | 22 | def apply(property: String, arguments: Array[Any]): String = MessageFormat.format(apply(property), arguments.map(_.asInstanceOf[AnyRef]):_*) 23 | 24 | def apply(property: String, arg1: Any): String = MessageFormat.format(apply(property), arg1.asInstanceOf[AnyRef]) 25 | 26 | def apply(property: String, arg1: Any, arg2: Any): String = MessageFormat.format(apply(property), arg1.asInstanceOf[AnyRef], arg2.asInstanceOf[AnyRef]) 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/MethodInvoker.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import java.lang.reflect.InvocationTargetException 11 | import java.lang.reflect.Method 12 | 13 | /** 14 | * @author Kota Mizushima 15 | * 16 | */ 17 | object MethodInvoker { 18 | def call(target: AnyRef, name: String, args: Array[AnyRef]): AnyRef = { 19 | try { 20 | getMethod(target.getClass, name, args).invoke(target, args) 21 | } catch { 22 | case e: NoSuchMethodException => 23 | throw new InvocationException(e) 24 | case e: IllegalArgumentException => 25 | throw new InvocationException(e) 26 | case e: IllegalAccessException => 27 | throw new InvocationException(e) 28 | case e: InvocationTargetException => 29 | throw new InvocationException(e) 30 | } 31 | } 32 | 33 | def callStatic(target: Class[_], name: String, args: Array[AnyRef]): AnyRef = { 34 | try { 35 | getMethod(target, name, args).invoke(null, args) 36 | } 37 | catch { 38 | case e: NoSuchMethodException => 39 | throw new InvocationException(e) 40 | case e: IllegalArgumentException => 41 | throw new InvocationException(e) 42 | case e: IllegalAccessException => 43 | throw new InvocationException(e) 44 | case e: InvocationTargetException => 45 | throw new InvocationException(e) 46 | } 47 | } 48 | 49 | private def getMethod(target: Class[_], name: String, args: Array[AnyRef]): Method = { 50 | val argsClasses = args.map {_.getClass()} 51 | target.getMethod(name, argsClasses:_*) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Paths.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | import java.io.File 11 | 12 | /** 13 | * @author Kota Mizushima 14 | * 15 | */ 16 | object Paths { 17 | def nameOf(path: String): String = new File(path).getName 18 | 19 | def cutExtension(path: String): String = { 20 | val name = nameOf(path) 21 | name.lastIndexOf('.') match { 22 | case n if n < 0 => name 23 | case n => name.substring(0, n) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/SymbolGenerator.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | class SymbolGenerator(val prefix: String) { 15 | private var count_ : Int = 0 16 | 17 | def count: Int = count_ 18 | 19 | def generate: String = { 20 | val newSymbol: String = prefix + count_ 21 | count_ += 1 22 | newSymbol 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/scala/onion/compiler/toolbox/Systems.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.compiler.toolbox 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | object Systems { 15 | lazy val lineSeparator: String = System.getProperty("line.separator") 16 | 17 | def lineSeparatorTimes(count: Int): String = { 18 | val separator = lineSeparator 19 | List.fill(count)(separator).mkString("") 20 | } 21 | 22 | lazy val pathSeparator: String = System.getProperty("path.separator") 23 | 24 | lazy val fileSeparator: String = System.getProperty("file.separator") 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/CompilerFrontend.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.tools 9 | 10 | import java.io.BufferedOutputStream 11 | import java.io.File 12 | import java.io.FileOutputStream 13 | import java.io.IOException 14 | import java.io.UnsupportedEncodingException 15 | import onion.compiler.CompiledClass 16 | import onion.compiler.OnionCompiler 17 | import onion.compiler.CompilerConfig 18 | import onion.compiler.exceptions.ScriptException 19 | import onion.compiler.toolbox.Message 20 | import onion.compiler.toolbox.Systems 21 | import onion.tools.option._ 22 | 23 | /** 24 | * 25 | * @author Kota Mizushima 26 | * 27 | */ 28 | object CompilerFrontend { 29 | private def config(option: String, requireArg: Boolean): OptionConfig = new OptionConfig(option, requireArg) 30 | 31 | private def pathArray(path: String): Array[String] = path.split(Systems.pathSeparator) 32 | 33 | private def printError(message: String): Unit = System.err.println(message) 34 | 35 | def main(args: Array[String]): Unit = { 36 | try { 37 | new CompilerFrontend().run(args) 38 | } catch { 39 | case e: ScriptException => throw e.getCause 40 | } 41 | } 42 | 43 | private final val CLASSPATH: String = "-classpath" 44 | private final val SCRIPT_SUPER_CLASS: String = "-super" 45 | private final val ENCODING: String = "-encoding" 46 | private final val OUTPUT: String = "-d" 47 | private final val MAX_ERROR: String = "-maxErrorReport" 48 | private final val DEFAULT_CLASSPATH: Array[String] = Array[String](".") 49 | private final val DEFAULT_ENCODING: String = System.getProperty("file.encoding") 50 | private final val DEFAULT_OUTPUT: String = "." 51 | private final val DEFAULT_MAX_ERROR: Int = 10 52 | } 53 | 54 | class CompilerFrontend { 55 | 56 | import CompilerFrontend._ 57 | 58 | private val commandLineParser = new CommandLineParser(config(CLASSPATH, true), config(SCRIPT_SUPER_CLASS, true), config(ENCODING, true), config(OUTPUT, true), config(MAX_ERROR, true)) 59 | 60 | def run(commandLine: Array[String]): Int = { 61 | if (commandLine.length == 0) { 62 | printUsage() 63 | return -1 64 | } 65 | val result: Option[ParseSuccess] = parseCommandLine(commandLine) 66 | result match { 67 | case None => -1 68 | case Some(success) => 69 | val config: Option[CompilerConfig] = createConfig(success) 70 | val params: Array[String] = success.arguments.toArray 71 | if (params.length == 0) { 72 | printUsage() 73 | return -1 74 | } 75 | config match { 76 | case None => -1 77 | case Some(config) => 78 | val classes = compile(config, params) 79 | (for (cs <- classes) yield { 80 | val succeed = generateFiles(cs) 81 | if (succeed) 0 else -1 82 | }).getOrElse(-1) 83 | } 84 | } 85 | } 86 | 87 | private def simpleNameOf(fqcn: String): String = { 88 | val index = fqcn.lastIndexOf(".") 89 | if (fqcn.lastIndexOf(".") < 0) fqcn else fqcn.substring(index + 1, fqcn.length) 90 | } 91 | 92 | private def outputPathOf(outDir: String, fqcn: String): String = outDir + Systems.fileSeparator + simpleNameOf(fqcn)+ ".class" 93 | 94 | private def generateFiles(binaries: Seq[CompiledClass]): Boolean = { 95 | val generated: java.util.List[File] = new java.util.ArrayList[File] 96 | for(binary <- binaries) { 97 | val outDir: String = binary.outputPath 98 | new File(outDir).mkdirs 99 | val outPath: String = outputPathOf(outDir, binary.className) 100 | val targetFile: File = new File(outPath) 101 | try { 102 | if (!targetFile.exists) targetFile.createNewFile 103 | generated.add(targetFile) 104 | using(new BufferedOutputStream(new FileOutputStream(targetFile))){out => 105 | out.write(binary.content) 106 | } 107 | } catch { 108 | case e: IOException => 109 | val it = generated.iterator 110 | while (it.hasNext) { 111 | it.next.delete 112 | } 113 | return false 114 | } 115 | } 116 | true 117 | } 118 | 119 | protected def printUsage(): Unit = { 120 | printError( 121 | """Usage: onionc [-options] source_file ... 122 | |options: 123 | | -super specify script's super class 124 | | -d specify output directory 125 | | -classpath specify classpath 126 | | -encoding specify source file encoding 127 | | -maxErrorReport set number of errors reported""".stripMargin) 128 | } 129 | 130 | private def parseCommandLine(commandLine: Array[String]): Option[ParseSuccess] = { 131 | val result = commandLineParser.parse(commandLine) 132 | result match { 133 | case success: ParseSuccess => Some(success) 134 | case failure: ParseFailure => 135 | val lackedOptions = failure.lackedOptions 136 | val invalidOptions = failure.invalidOptions 137 | invalidOptions.foreach{opt => printError(Message.apply("error.command.invalidArgument", opt)) } 138 | lackedOptions.foreach{opt => printError(Message.apply("error.command..noArgument", opt)) } 139 | None 140 | } 141 | } 142 | 143 | private def createConfig(result: ParseSuccess): Option[CompilerConfig] = { 144 | val option: Map[String, CommandLineParam] = result.options.toMap 145 | val classpath: Array[String] = checkClasspath( 146 | option.get(CLASSPATH).collect{ case ValuedParam(value) => value } 147 | ) 148 | val encoding: Option[String] = checkEncoding( 149 | option.get(ENCODING).collect{ case ValuedParam(value) => value } 150 | ) 151 | val outputDirectory: String = checkOutputDirectory( 152 | option.get(OUTPUT).collect{ case ValuedParam(value) => value} 153 | ) 154 | val maxErrorReport: Option[Int] = checkMaxErrorReport( 155 | option.get(MAX_ERROR).collect{ case ValuedParam(value) => value} 156 | ) 157 | for (e <- encoding; m <- maxErrorReport) yield { 158 | new CompilerConfig(classpath.toIndexedSeq, "", e, outputDirectory, m) 159 | } 160 | } 161 | 162 | private def compile(config: CompilerConfig, fileNames: Array[String]): Option[Seq[CompiledClass]] = { 163 | Option(new OnionCompiler(config).compile(fileNames)) 164 | } 165 | 166 | private def checkClasspath(classpath: Option[String]): Array[String] = { 167 | (for (c <- classpath) yield pathArray(c)).getOrElse(DEFAULT_CLASSPATH) 168 | } 169 | 170 | private def checkOutputDirectory(outputDirectory: Option[String]): String = outputDirectory.getOrElse(DEFAULT_OUTPUT) 171 | 172 | private def checkEncoding(encoding: Option[String]): Option[String] = { 173 | try { 174 | (for (e <- encoding) yield { 175 | "".getBytes(e) 176 | e 177 | }).orElse(Some(DEFAULT_ENCODING)) 178 | } catch { 179 | case e: UnsupportedEncodingException => { 180 | printError(Message.apply("error.command.invalidEncoding", ENCODING)) 181 | None 182 | } 183 | } 184 | } 185 | 186 | private def checkMaxErrorReport(maxErrorReport: Option[String]): Option[Int] = { 187 | try { 188 | maxErrorReport match { 189 | case Some(m) => 190 | val value = Integer.parseInt(m) 191 | if (value > 0) Some(value) else None 192 | case None => Some(DEFAULT_MAX_ERROR) 193 | } 194 | } catch { 195 | case e: NumberFormatException => 196 | printError(Message.apply("error.command.requireNaturalNumber", MAX_ERROR)) 197 | None 198 | } 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/ScriptRunner.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.tools 9 | 10 | import java.io.UnsupportedEncodingException 11 | import java.lang.System.err 12 | import onion.compiler._ 13 | import onion.compiler.exceptions.ScriptException 14 | import onion.compiler.toolbox.Message 15 | import onion.compiler.toolbox.Systems 16 | import onion.tools.option._ 17 | 18 | /** 19 | * 20 | * @author Kota Mizushima 21 | * 22 | */ 23 | object ScriptRunner { 24 | private def conf(optionName: String, requireArgument: Boolean) = OptionConfig(optionName, requireArgument) 25 | 26 | private def pathArray(path: String): Array[String] = path.split(Systems.pathSeparator) 27 | 28 | def main(args: Array[String]): Unit = { 29 | try { 30 | new ScriptRunner().run(args) 31 | } 32 | catch { 33 | case e: ScriptException => { 34 | throw e.getCause 35 | } 36 | } 37 | } 38 | 39 | private final val CLASSPATH: String = "-classpath" 40 | private final val SCRIPT_SUPER_CLASS: String = "-super" 41 | private final val ENCODING: String = "-encoding" 42 | private final val MAX_ERROR: String = "-maxErrorReport" 43 | private final val DEFAULT_CLASSPATH: Array[String] = Array[String](".") 44 | private final val DEFAULT_ENCODING: String = System.getProperty("file.encoding") 45 | private final val DEFAULT_OUTPUT: String = "." 46 | private final val DEFAULT_MAX_ERROR: Int = 10 47 | } 48 | 49 | class ScriptRunner { 50 | import ScriptRunner._ 51 | private[this] val parser = new CommandLineParser(conf(CLASSPATH, true), conf(SCRIPT_SUPER_CLASS, true), conf(ENCODING, true), conf(MAX_ERROR, true)) 52 | 53 | def run(commandLine: Array[String]): Int = { 54 | if (commandLine.length == 0) { 55 | printUsage() 56 | return -1 57 | } 58 | parser.parse(commandLine) match { 59 | case failure@ParseFailure(_, _) => 60 | printFailure(failure) 61 | return -1 62 | case success@ParseSuccess(_, _) => 63 | val config = createConfig(success) 64 | if (config.isEmpty) return -1 65 | createConfig(success) match { 66 | case None => return -1 67 | case Some (config) => 68 | val params = success.arguments 69 | if(params.length == 0) { 70 | printUsage() 71 | return -1 72 | } 73 | val scriptParams: Array[String] = new Array[String](params.length - 1) 74 | val classes = compile(config, Array(params(0))) 75 | if(classes == null) return -1 76 | for(i <- 1 until params.length) { 77 | scriptParams(i - 1) = params(i) 78 | } 79 | new Shell(classOf[OnionClassLoader].getClassLoader, config.classPath).run(classes, scriptParams) match { 80 | case Shell.Success(_) => 0 81 | case Shell.Failure(code) => code 82 | } 83 | } 84 | } 85 | } 86 | 87 | protected def printUsage(): Unit = { 88 | err.println("""Usage: onion [-options] 89 | |options: 90 | |-super specify script's super class 91 | | -classpath specify classpath 92 | | -encoding specify source file encoding 93 | | -maxErrorReport set number of errors reported""".stripMargin) 94 | } 95 | 96 | private def printFailure(failure: ParseFailure): Unit = { 97 | failure.lackedOptions.zipWithIndex.foreach{ case (lackedOption, i) => 98 | err.println(Message("error.command.noArgument", lackedOption)) 99 | } 100 | } 101 | 102 | private def createConfig(result: ParseSuccess): Option[CompilerConfig] = { 103 | val option: Map[String, CommandLineParam] = result.options.toMap 104 | val classpath: Array[String] = checkClasspath(option.get(CLASSPATH)) 105 | for( 106 | encoding <- checkEncoding(option.get(ENCODING)); 107 | maxErrorReport <- checkMaxErrorReport(option.get(MAX_ERROR)) 108 | ) yield (new CompilerConfig(classpath.toIndexedSeq, "", encoding, ".", maxErrorReport)) 109 | } 110 | 111 | private def compile(config: CompilerConfig, fileNames: Array[String]): Seq[CompiledClass] = { 112 | new OnionCompiler(config).compile(fileNames) 113 | } 114 | 115 | private def checkClasspath(optClasspath: Option[CommandLineParam]): Array[String] = { 116 | optClasspath match { 117 | case Some(ValuedParam(classpath)) => pathArray(classpath) 118 | case Some(NoValuedParam) | None => DEFAULT_CLASSPATH 119 | } 120 | } 121 | 122 | private def checkEncoding(optEncoding: Option[CommandLineParam]): Option[String] = { 123 | optEncoding match { 124 | case None | Some(NoValuedParam) => 125 | Some(System.getProperty("file.encoding")) 126 | case Some(ValuedParam(encoding)) => 127 | try { 128 | "".getBytes(encoding) 129 | Some(encoding) 130 | } catch { 131 | case e: UnsupportedEncodingException => 132 | err.println(Message.apply("error.command.invalidEncoding", ENCODING)) 133 | None 134 | } 135 | } 136 | } 137 | 138 | private def checkMaxErrorReport(optMaxErrorReport: Option[CommandLineParam]): Option[Int] = { 139 | optMaxErrorReport match { 140 | case None | Some(NoValuedParam) => Some(DEFAULT_MAX_ERROR) 141 | case Some(ValuedParam(maxErrorReport)) => 142 | val value: Option[Int] = try { 143 | Some(Integer.parseInt(maxErrorReport)) 144 | } catch { 145 | case e: NumberFormatException => None 146 | } 147 | value match { 148 | case Some(v) if v > 0 => Some(v) 149 | case None => 150 | err.println(Message("error.command.requireNaturalNumber", MAX_ERROR)) 151 | None 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/Shell.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.tools 9 | 10 | import java.io.StringReader 11 | import java.lang.reflect.InvocationTargetException 12 | import java.lang.reflect.Method 13 | import java.lang.reflect.Modifier 14 | import java.net.MalformedURLException 15 | import onion.compiler._ 16 | import onion.compiler.exceptions.ScriptException 17 | 18 | class Shell (val classLoader: ClassLoader, val classpath: Seq[String]) { 19 | private val config = new CompilerConfig(classpath, null, "Shift_JIS", "", 10) 20 | def run(script: String, fileName: String, args: Array[String]): Shell.Result = { 21 | val compiler: OnionCompiler = new OnionCompiler(config) 22 | Thread.currentThread.setContextClassLoader(classLoader) 23 | val classes: Seq[CompiledClass] = compiler.compile(Seq(new StreamInputSource(new StringReader(script), fileName))) 24 | run(classes, args) 25 | } 26 | 27 | def run(classes: Seq[CompiledClass], args: Array[String]): Shell.Result = { 28 | try { 29 | val loader = new OnionClassLoader(classLoader, classpath, classes) 30 | Thread.currentThread.setContextClassLoader(loader) 31 | val main = findFirstMainMethod(loader, classes) 32 | main match { 33 | case Some(method) => Shell.Success(method.invoke(null, args)) 34 | case None => Shell.Failure(-1) 35 | } 36 | } catch { 37 | case _: ClassNotFoundException | _: IllegalAccessException | _: MalformedURLException => Shell.Failure(-1) 38 | case e: InvocationTargetException => throw new ScriptException(e.getCause) 39 | } 40 | } 41 | 42 | private def findFirstMainMethod(loader: OnionClassLoader, classes: Seq[CompiledClass]): Option[Method] = { 43 | for (i <- 0 until classes.length) { 44 | val className = classes(i).className 45 | val clazz = Class.forName(className, true, loader) 46 | try { 47 | val main = clazz.getMethod("main", classOf[Array[String]]) 48 | val modifier = main.getModifiers 49 | if ((modifier & Modifier.PUBLIC) != 0 && (modifier & Modifier.STATIC) != 0) { 50 | return Some(main) 51 | } 52 | } catch { 53 | case _: NoSuchMethodException => 54 | } 55 | } 56 | None 57 | } 58 | 59 | } 60 | 61 | object Shell { 62 | def apply(classpath: Seq[String]): Shell = { 63 | new Shell(classOf[OnionClassLoader].getClassLoader, classpath) 64 | } 65 | sealed abstract class Result 66 | case class Success(value: Any) extends Result 67 | case class Failure(code: Int) extends Result 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/option/CommandLineParam.scala: -------------------------------------------------------------------------------- 1 | package onion.tools.option 2 | 3 | /** 4 | * @author Kota Mizushima 5 | */ 6 | sealed trait CommandLineParam 7 | case class ValuedParam(value: String) extends CommandLineParam 8 | case object NoValuedParam extends CommandLineParam 9 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/option/CommandLineParser.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.tools.option 9 | 10 | import scala.collection.mutable 11 | 12 | /** 13 | * @author Kota Mizushima 14 | * 15 | */ 16 | class CommandLineParser(val configs: OptionConfig*) { 17 | 18 | def parse(cmdline: Array[String]): ParseResult = { 19 | val opts = mutable.Map[String, CommandLineParam]() 20 | val args = mutable.Buffer[String]() 21 | val lackedOptNames = mutable.Buffer[ValuedParam]() 22 | val invalidOptNames = mutable.Buffer[ValuedParam]() 23 | var i = 0 24 | 25 | while (i < cmdline.length) { 26 | if (cmdline(i).startsWith("-")) { 27 | val param = cmdline(i) 28 | val config: Option[OptionConfig] = configs.find(_.optionName == param) 29 | config match { 30 | case None => 31 | invalidOptNames += ValuedParam(param) 32 | i += 1 33 | case Some(config) => 34 | if (config.hasArgument) { 35 | if (i + 1 >= cmdline.length) { 36 | lackedOptNames += ValuedParam(param) 37 | } else { 38 | opts(param) = ValuedParam(cmdline(i + 1)) 39 | } 40 | i += 2 41 | } else { 42 | opts(param) = NoValuedParam 43 | i += 1 44 | } 45 | } 46 | } else { 47 | args += cmdline(i) 48 | i += 1 49 | } 50 | } 51 | 52 | if (lackedOptNames.size == 0 && invalidOptNames.size == 0) 53 | new ParseSuccess(opts, args.toArray) 54 | else 55 | new ParseFailure(lackedOptNames.toArray, invalidOptNames.toArray) 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/scala/onion/tools/option/OptionConfig.scala: -------------------------------------------------------------------------------- 1 | /* ************************************************************** * 2 | * * 3 | * Copyright (c) 2016-, Kota Mizushima, All rights reserved. * 4 | * * 5 | * * 6 | * This software is distributed under the modified BSD License. * 7 | * ************************************************************** */ 8 | package onion.tools.option 9 | 10 | /** 11 | * @author Kota Mizushima 12 | * 13 | */ 14 | case class OptionConfig(optionName: String, hasArgument : Boolean) 15 | -------------------------------------------------------------------------------- /src/main/scala/onion/tools/option/ParseResult.scala: -------------------------------------------------------------------------------- 1 | package onion.tools.option 2 | import scala.collection.mutable.{Map, Seq} 3 | 4 | sealed trait ParseResult { 5 | def status: Int 6 | } 7 | 8 | case class ParseSuccess(options: Map[String, CommandLineParam], arguments: Array[String]) extends ParseResult { 9 | def status: Int = ParseResult.SUCCEED 10 | } 11 | 12 | case class ParseFailure(lackedOptions: Array[ValuedParam], invalidOptions: Array[ValuedParam]) extends ParseResult { 13 | def status: Int = ParseResult.FAILURE 14 | } 15 | 16 | object ParseResult { 17 | final val SUCCEED: Int = 0 18 | final val FAILURE: Int = 1 19 | } -------------------------------------------------------------------------------- /src/main/scala/onion/tools/package.scala: -------------------------------------------------------------------------------- 1 | package onion 2 | import java.io.Closeable 3 | 4 | package object tools { 5 | def using[T <: Closeable, U](resource: T)(block: T => U): U = try { 6 | block(resource) 7 | } finally { 8 | resource.close() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/dummy.txt: -------------------------------------------------------------------------------- 1 | This is a dummy file. 2 | -------------------------------------------------------------------------------- /src/test/run/Hello.on: -------------------------------------------------------------------------------- 1 | class Hello { 2 | public: 3 | static main(args :String[]){ 4 | System::out.println("Hello"); 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/run/Hello.out: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/TestASTBuilder.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.diagrams.Diagrams 5 | import scala.collection.immutable.List 6 | 7 | /** 8 | * Test implementation of ASTBuilder that demonstrates how the builder pattern 9 | * allows for custom AST construction behavior without modifying the parser. 10 | */ 11 | class TestASTBuilder extends DefaultASTBuilder { 12 | 13 | // Track all method declarations for analysis 14 | var methodCount = 0 15 | var methodNames = List.empty[String] 16 | 17 | override def createMethodDeclaration( 18 | location: Location, 19 | modifiers: Int, 20 | name: String, 21 | args: List[AST.Argument], 22 | returnType: AST.TypeNode, 23 | body: AST.BlockExpression 24 | ): AST.MethodDeclaration = { 25 | // Custom behavior: track method statistics 26 | methodCount += 1 27 | methodNames = methodNames :+ name 28 | 29 | // Could also perform transformations here, e.g.: 30 | // - Add logging to method bodies 31 | // - Inject security checks 32 | // - Optimize certain patterns 33 | 34 | super.createMethodDeclaration(location, modifiers, name, args, returnType, body) 35 | } 36 | 37 | // Example: Automatically add toString methods to records 38 | override def createRecordDeclaration( 39 | location: Location, 40 | modifiers: Int, 41 | name: String, 42 | args: List[AST.Argument] 43 | ): AST.RecordDeclaration = { 44 | // Could inject additional methods here 45 | super.createRecordDeclaration(location, modifiers, name, args) 46 | } 47 | } 48 | 49 | /** 50 | * Logging AST builder that prints AST construction for debugging 51 | */ 52 | class LoggingASTBuilder extends DefaultASTBuilder { 53 | 54 | override def createClassDeclaration( 55 | location: Location, 56 | modifiers: Int, 57 | name: String, 58 | superClass: AST.TypeNode, 59 | interfaces: List[AST.TypeNode], 60 | defaultSection: Option[AST.AccessSection], 61 | sections: List[AST.AccessSection] 62 | ): AST.ClassDeclaration = { 63 | println(s"Creating class: $name at $location") 64 | super.createClassDeclaration(location, modifiers, name, superClass, interfaces, defaultSection, sections) 65 | } 66 | 67 | override def createMethodDeclaration( 68 | location: Location, 69 | modifiers: Int, 70 | name: String, 71 | args: List[AST.Argument], 72 | returnType: AST.TypeNode, 73 | body: AST.BlockExpression 74 | ): AST.MethodDeclaration = { 75 | println(s"Creating method: $name with ${args.length} arguments at $location") 76 | super.createMethodDeclaration(location, modifiers, name, args, returnType, body) 77 | } 78 | } 79 | 80 | /** 81 | * Validating AST builder that enforces coding standards 82 | */ 83 | class ValidatingASTBuilder extends DefaultASTBuilder { 84 | 85 | override def createMethodDeclaration( 86 | location: Location, 87 | modifiers: Int, 88 | name: String, 89 | args: List[AST.Argument], 90 | returnType: AST.TypeNode, 91 | body: AST.BlockExpression 92 | ): AST.MethodDeclaration = { 93 | // Enforce naming conventions 94 | if (!name.matches("[a-z][a-zA-Z0-9]*")) { 95 | throw new IllegalArgumentException(s"Method name '$name' does not follow camelCase convention at $location") 96 | } 97 | 98 | // Enforce parameter limits 99 | if (args.length > 10) { 100 | throw new IllegalArgumentException(s"Method '$name' has too many parameters (${args.length}) at $location") 101 | } 102 | 103 | super.createMethodDeclaration(location, modifiers, name, args, returnType, body) 104 | } 105 | 106 | override def createClassDeclaration( 107 | location: Location, 108 | modifiers: Int, 109 | name: String, 110 | superClass: AST.TypeNode, 111 | interfaces: List[AST.TypeNode], 112 | defaultSection: Option[AST.AccessSection], 113 | sections: List[AST.AccessSection] 114 | ): AST.ClassDeclaration = { 115 | // Enforce naming conventions 116 | if (!name.matches("[A-Z][a-zA-Z0-9]*")) { 117 | throw new IllegalArgumentException(s"Class name '$name' does not follow PascalCase convention at $location") 118 | } 119 | 120 | super.createClassDeclaration(location, modifiers, name, superClass, interfaces, defaultSection, sections) 121 | } 122 | } 123 | 124 | class ASTBuilderSpec extends AnyFunSpec with Diagrams { 125 | describe("ASTBuilder pattern") { 126 | it("allows custom AST construction behavior") { 127 | val testBuilder = new TestASTBuilder() 128 | val location = new Location(1, 1) 129 | 130 | // Create some methods 131 | testBuilder.createMethodDeclaration( 132 | location, 133 | AST.M_PUBLIC, 134 | "doSomething", 135 | List.empty, 136 | null, 137 | AST.BlockExpression(location, List.empty) 138 | ) 139 | 140 | testBuilder.createMethodDeclaration( 141 | location, 142 | AST.M_PRIVATE, 143 | "helper", 144 | List.empty, 145 | null, 146 | AST.BlockExpression(location, List.empty) 147 | ) 148 | 149 | assert(testBuilder.methodCount == 2) 150 | assert(testBuilder.methodNames == List("doSomething", "helper")) 151 | } 152 | 153 | it("supports validation during AST construction") { 154 | val validatingBuilder = new ValidatingASTBuilder() 155 | val location = new Location(1, 1) 156 | 157 | // Valid method name 158 | validatingBuilder.createMethodDeclaration( 159 | location, 160 | AST.M_PUBLIC, 161 | "validMethodName", 162 | List.empty, 163 | null, 164 | AST.BlockExpression(location, List.empty) 165 | ) 166 | 167 | // Invalid method name should throw 168 | intercept[IllegalArgumentException] { 169 | validatingBuilder.createMethodDeclaration( 170 | location, 171 | AST.M_PUBLIC, 172 | "InvalidMethodName", // Starts with capital 173 | List.empty, 174 | null, 175 | AST.BlockExpression(location, List.empty) 176 | ) 177 | } 178 | 179 | // Too many parameters should throw 180 | val tooManyArgs = (1 to 11).map(i => AST.Argument(location, s"arg$i", null)).toList 181 | intercept[IllegalArgumentException] { 182 | validatingBuilder.createMethodDeclaration( 183 | location, 184 | AST.M_PUBLIC, 185 | "methodWithTooManyArgs", 186 | tooManyArgs, 187 | null, 188 | AST.BlockExpression(location, List.empty) 189 | ) 190 | } 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/AbstractShellSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | import org.scalatest.diagrams.Diagrams 5 | import org.scalatest.funspec.AnyFunSpec 6 | 7 | class AbstractShellSpec extends AnyFunSpec with Diagrams { 8 | val shell = Shell(Seq()) 9 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/BeanSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class BeanSpec extends AbstractShellSpec { 6 | describe("Bean class with setter function as member reference") { 7 | it("compiles") { 8 | val resultBean = shell.run( 9 | """ 10 | | class Bean <: Serializable { 11 | | @value :Int 12 | | public: 13 | | def this { 14 | | } 15 | | 16 | | def this(value :Int){ 17 | | @value = value 18 | | } 19 | | 20 | | def getValue :Int = @value 21 | | 22 | | def setValue(value :Int) { 23 | | @value = value 24 | | } 25 | | 26 | | def toString :String = "Bean(value = " + @value + ")" 27 | | 28 | | static def main(args: String[]): String { 29 | | bean = new Bean 30 | | bean.value = 200 // The reference of setter functions! 31 | | return JInteger::toString(bean.value) 32 | | } 33 | | } 34 | """.stripMargin, 35 | "None", 36 | Array() 37 | ) 38 | assert(Shell.Success("200") == resultBean) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/BreakContinueSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class BreakContinueSpec extends AbstractShellSpec { 6 | describe("Break and Continue statements") { 7 | it("break exits loop early") { 8 | val result = shell.run( 9 | """ 10 | |class BreakTest { 11 | |public: 12 | | static def main(args: String[]): Int { 13 | | count = 0 14 | | i = 0 15 | | while i < 10 { 16 | | if i == 5 { 17 | | break 18 | | } 19 | | count = count + 1 20 | | i = i + 1 21 | | } 22 | | return count 23 | | } 24 | |} 25 | """.stripMargin, 26 | "None", 27 | Array() 28 | ) 29 | assert(Shell.Success(5) == result) 30 | } 31 | 32 | it("continue skips to next iteration") { 33 | val result = shell.run( 34 | """ 35 | |class ContinueTest { 36 | |public: 37 | | static def main(args: String[]): Int { 38 | | count = 0 39 | | i = 0 40 | | while i < 10 { 41 | | i = i + 1 42 | | if i % 2 == 0 { 43 | | continue 44 | | } 45 | | count = count + 1 46 | | } 47 | | return count 48 | | } 49 | |} 50 | """.stripMargin, 51 | "None", 52 | Array() 53 | ) 54 | assert(Shell.Success(5) == result) // Count odd numbers: 1, 3, 5, 7, 9 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/CountersSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class CountersSpec extends AbstractShellSpec { 6 | describe("Counters class") { 7 | it("creates a Counter using closure") { 8 | val result = shell.run( 9 | """ 10 | | interface Counter { 11 | | def count :Int 12 | | } 13 | | 14 | | class Counters { 15 | | public: 16 | | static def counter(begin :Int, up :Int) :Counter = 17 | | #Counter.count { 18 | | return begin = 19 | | begin + up 20 | | } 21 | | 22 | | static def main(args: String[]): Int { 23 | | c = counter(1, 10) 24 | | c.count 25 | | c.count 26 | | return c.count 27 | | } 28 | |} 29 | |""".stripMargin, 30 | "None", 31 | Array() 32 | ) 33 | assert(Shell.Success(31) == result) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/DecrementSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class DecrementSpec extends AbstractShellSpec { 6 | describe("Decrement class") { 7 | it("demonstrate decrement(--) feature") { 8 | val result = shell.run( 9 | """ 10 | | class Decrement { 11 | | public: 12 | | static def main(args: String[]): Int { 13 | | i = 10; 14 | | for ; i >= 0; i-- { } 15 | | return i; 16 | | } 17 | | } 18 | """.stripMargin, 19 | "None", 20 | Array() 21 | ) 22 | assert(Shell.Success(-1) == result) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/FactorialSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class FactorialSpec extends AbstractShellSpec { 6 | describe("Factorial class") { 7 | it("shows result of 5!") { 8 | val resultFac5 = shell.run( 9 | """ 10 | |class Factorial { 11 | | static def factorial(n: Int): Int { 12 | | if n < 2 { return 1; } else { return n * factorial(n - 1); } 13 | | } 14 | |public: 15 | | static def main(args: String[]): Int { 16 | | return factorial(5); 17 | | } 18 | |} 19 | """.stripMargin, 20 | "None", 21 | Array() 22 | ) 23 | def factorial(n: Int): Int = if(n < 2) 1 else n * factorial(n - 1) 24 | val answer = factorial(5) 25 | assert(Shell.Success(answer) == resultFac5) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/ForeachSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class ForeachSpec extends AbstractShellSpec { 6 | describe("Cat") { 7 | it("shows result of cat using foreach") { 8 | val resultCat = shell.run( 9 | """ 10 | |class Cat { 11 | |public: 12 | | static def main(args: String[]): String { 13 | | list = ["A", "B", "C", "D"] 14 | | result = "" 15 | | foreach s:String in list { 16 | | result = result + s; 17 | | } 18 | | return result 19 | | } 20 | |} 21 | """.stripMargin, 22 | "None", 23 | Array() 24 | ) 25 | def cat(list: List[String]): String = list.mkString 26 | val answer = cat(List("A", "B", "C", "D")) 27 | assert(Shell.Success(answer) == resultCat) 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/FunctionWithExpressionBodySpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class FunctionWithExpressionBodySpec extends AbstractShellSpec { 6 | describe("ExpressionBody class") { 7 | it("returns String a value") { 8 | val resultExpressionBody = shell.run( 9 | """ 10 | |class ExpressionBody { 11 | |public: 12 | | static def main(args: String[]): String = "ExpressionBody" 13 | |} 14 | """.stripMargin, 15 | "None", 16 | Array() 17 | ) 18 | assert(Shell.Success("ExpressionBody") == resultExpressionBody) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/HelloWorldSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | import org.scalatest._ 5 | 6 | class HelloWorldSpec extends AbstractShellSpec { 7 | describe("HelloWorld class") { 8 | it("shows 'Hello, World'") { 9 | val resultHelloWorld = shell.run( 10 | """ 11 | |class HelloWorld { 12 | |public: 13 | | static def main(args: String[]): String { 14 | | return "Hello, World" 15 | | } 16 | |} 17 | |""".stripMargin, 18 | "None", 19 | Array() 20 | ) 21 | assert(Shell.Success("Hello, World") == resultHelloWorld) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/ImportSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class ImportSpec extends AbstractShellSpec { 6 | describe("Import a class") { 7 | it("import java.util.*") { 8 | val result = shell.run( 9 | """ 10 | | import { 11 | | java.util.* 12 | | } 13 | | class Increment { 14 | | public: 15 | | static def main(args: String[]): Int { 16 | | xs = new ArrayList() 17 | | xs.add(new Integer(2)) 18 | | return xs.get(0)$Integer.intValue() 19 | | } 20 | | } 21 | """.stripMargin, 22 | "None", 23 | Array() 24 | ) 25 | assert(Shell.Success(2) == result) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/IncrementSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class IncrementSpec extends AbstractShellSpec { 6 | describe("Increment class") { 7 | it("demonstrate increment(++) feature") { 8 | val result = shell.run( 9 | """ 10 | | class Increment { 11 | | public: 12 | | static def main(args: String[]): Int { 13 | | i = 0; 14 | | for i = 0; i < 10; i++ { } 15 | | return i; 16 | | } 17 | | } 18 | """.stripMargin, 19 | "None", 20 | Array() 21 | ) 22 | assert(Shell.Success(10) == result) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/StringInterpolationSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | import org.scalatest._ 5 | 6 | class StringInterpolationSpec extends AbstractShellSpec { 7 | describe("string interpolation") { 8 | it("should interpolate simple expressions") { 9 | val result = shell.run( 10 | """ 11 | |class Test { 12 | |public: 13 | | static def main(args: String[]): String = "The sum of #{5} and #{10} is #{5 + 10}" 14 | |} 15 | """.stripMargin, 16 | "None", 17 | Array() 18 | ) 19 | assert(Shell.Success("The sum of 5 and 10 is 15") == result) 20 | } 21 | 22 | it("should interpolate variables in methods") { 23 | val result = shell.run( 24 | """ 25 | |class Test { 26 | |public: 27 | | static def main(args: String[]): String { 28 | | x: Int = 5 29 | | y: Int = 10 30 | | return "The sum of #{x} and #{y} is #{x + y}" 31 | | } 32 | |} 33 | """.stripMargin, 34 | "None", 35 | Array() 36 | ) 37 | assert(Shell.Success("The sum of 5 and 10 is 15") == result) 38 | } 39 | 40 | it("should handle empty strings between interpolations") { 41 | val result = shell.run( 42 | """ 43 | |class Test { 44 | |public: 45 | | static def main(args: String[]): String { 46 | | a: Int = 1 47 | | b: Int = 2 48 | | return "#{a}#{b}" 49 | | } 50 | |} 51 | """.stripMargin, 52 | "None", 53 | Array() 54 | ) 55 | assert(Shell.Success("12") == result) 56 | } 57 | 58 | it("should handle string interpolation with no expressions") { 59 | val result = shell.run( 60 | """ 61 | |class Test { 62 | |public: 63 | | static def main(args: String[]): String = "No interpolation here" 64 | |} 65 | """.stripMargin, 66 | "None", 67 | Array() 68 | ) 69 | assert(Shell.Success("No interpolation here") == result) 70 | } 71 | 72 | it("should handle multiple interpolations") { 73 | val result = shell.run( 74 | """ 75 | |class Test { 76 | |public: 77 | | static def main(args: String[]): String { 78 | | a: Int = 1 79 | | b: Int = 2 80 | | c: Int = 3 81 | | return "a=#{a}, b=#{b}, c=#{c}, sum=#{a+b+c}" 82 | | } 83 | |} 84 | """.stripMargin, 85 | "None", 86 | Array() 87 | ) 88 | assert(Shell.Success("a=1, b=2, c=3, sum=6") == result) 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/test/scala/onion/compiler/tools/TerminatorSpec.scala: -------------------------------------------------------------------------------- 1 | package onion.compiler.tools 2 | 3 | import onion.tools.Shell 4 | 5 | class TerminatorSpec extends AbstractShellSpec { 6 | describe("Newlines as statement terminators") { 7 | it("local variable declaration") { 8 | val result = shell.run( 9 | """ 10 | | class LocalVar { 11 | | public: 12 | | static def main(args: String[]): Int { 13 | | i: Int = 10 14 | | j: Int = 15 | | 20 16 | | return 0; 17 | | } 18 | | } 19 | """.stripMargin, 20 | "None", 21 | Array() 22 | ) 23 | assert(Shell.Success(0) == result) 24 | } 25 | 26 | it("return statement") { 27 | val result = shell.run( 28 | """ 29 | | class ReturnStatement { 30 | | public: 31 | | static def main(args: String[]): Int { 32 | | return 20 33 | | } 34 | | } 35 | """.stripMargin, 36 | "None", 37 | Array() 38 | ) 39 | assert(Shell.Success(20) == result) 40 | } 41 | } 42 | } --------------------------------------------------------------------------------