Repository: fredoverflow/skorbut-release Branch: master Commit: b9eff51240d4 Files: 63 Total size: 292.6 KB Directory structure: gitextract_y9tqg1bi/ ├── .gitignore ├── README.md ├── pom.xml ├── sloc └── src/ ├── main/ │ └── kotlin/ │ ├── Main.kt │ ├── common/ │ │ ├── Counter.kt │ │ ├── Diagnostic.kt │ │ └── Maps.kt │ ├── interpreter/ │ │ ├── BasicBlock.kt │ │ ├── BuildControlFlowGraph.kt │ │ ├── Console.kt │ │ ├── FlatStatements.kt │ │ ├── Interpreter.kt │ │ ├── Memory.kt │ │ ├── Segment.kt │ │ └── Value.kt │ ├── semantic/ │ │ ├── Linter.kt │ │ ├── LinterBase.kt │ │ ├── SymbolTable.kt │ │ ├── TypeChecker.kt │ │ ├── TypeSpecifiers.kt │ │ └── types/ │ │ ├── Arithmetic.kt │ │ ├── Array.kt │ │ ├── Enum.kt │ │ ├── Function.kt │ │ ├── Pointer.kt │ │ ├── Struct.kt │ │ ├── Type.kt │ │ ├── Typedef.kt │ │ └── Void.kt │ ├── syntax/ │ │ ├── lexer/ │ │ │ ├── Characters.kt │ │ │ ├── Identifiers.kt │ │ │ ├── Lexer.kt │ │ │ ├── NextToken.kt │ │ │ ├── Numbers.kt │ │ │ ├── SkipComments.kt │ │ │ ├── Token.kt │ │ │ ├── TokenKind.kt │ │ │ └── TokenKindSet.kt │ │ ├── parser/ │ │ │ ├── Autocompletion.kt │ │ │ ├── Declarations.kt │ │ │ ├── Expressions.kt │ │ │ ├── ExternalDefinitions.kt │ │ │ ├── LeftDenotations.kt │ │ │ ├── NullDenotations.kt │ │ │ ├── Parser.kt │ │ │ └── Statements.kt │ │ └── tree/ │ │ ├── DeclarationSpecifier.kt │ │ ├── Declarator.kt │ │ ├── Expression.kt │ │ ├── External.kt │ │ ├── Node.kt │ │ └── Statement.kt │ ├── text/ │ │ ├── Char.kt │ │ └── String.kt │ └── ui/ │ ├── Flexer.kt │ ├── MainFrame.kt │ └── MemoryUI.kt └── test/ ├── kotlin/ │ ├── interpreter/ │ │ └── InterpreterTest.kt │ ├── semantic/ │ │ └── types/ │ │ └── TypeToStringTest.kt │ └── syntax/ │ ├── lexer/ │ │ └── LexerTest.kt │ └── parser/ │ └── AutocompletionTest.kt └── resources/ └── junit-platform.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /target/ /.idea/ /*.iml ================================================ FILE: README.md ================================================ ![swap](skorbut.png) ## What is Skorbut? Skorbut is a simple teaching environment for a subset of C with a memory visualizer. If you ever had trouble visualizing arrays and pointers, you have come to the right place. *Skorbut* is German for *scurvy*, an illness that is caused by a lack of Vitamin C. Skorbut also lacks C in the sense that it implements only a restricted subset of C. ## Getting started Please take the time to **read the following instructions carefully.** Most problems stem from skipping or misunderstanding important steps. ### ☕ Windows & macOS 1. Visit https://adoptium.net 2. Click "Latest release" button to download Java installer 3. Wait for download to finish 4. Open the `Downloads` folder (via Windows Explorer or Finder/Spotlight, respectively) and double-click `OpenJDK...` to start Java installer 5. Click Next, Next, Install, Finish 6. Click [skorbut.jar](https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar) to download Skorbut
**If Skorbut fails to download**, continue with ⚠️ Troubleshooting *Windows*, or ⚠️ Troubleshooting *macOS* 7. Open the `Downloads` folder and double-click `skorbut.jar` to start Skorbut
**If Skorbut fails to start**, continue with ⚠️ Troubleshooting *Windows*, or ⚠️ Troubleshooting *macOS* ### ⚠️ Troubleshooting *Windows* Steps 1 through 5 (install Java) worked, but steps 6 (download Skorbut) or 7 (start Skorbut) failed? Then read on. - Move your mouse over the script below - A button appears in the top right corner of the script - Click that button to copy the script ```cmd cd Downloads if exist skorbut.jar.zip erase skorbut.jar.zip curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar echo java -version > skorbut.cmd echo java -jar skorbut.jar >> skorbut.cmd skorbut.cmd ``` - Press the Windows key (the key on the bottom left with the Windows logo ⊞ on it) - Write `cmd` and confirm with Enter - A terminal appears - Right-click anywhere inside that terminal to paste and execute the script From now on, simply double-click `skorbut.cmd` in the `Downloads` folder to start Skorbut.
Feel free to move `skorbut.jar` and `skorbut.cmd` to the Desktop or any other folder you prefer. ### ⚠️ Troubleshooting *macOS* Steps 1 through 5 (install Java) worked, but steps 6 (download Skorbut) or 7 (start Skorbut) failed? Then read on. - Move your mouse over the script below - A button appears in the top right corner of the script - Click that button to copy the script ```sh cd Downloads curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar chmod +x skorbut.jar echo java -version > skorbut.sh echo java -jar skorbut.jar >> skorbut.sh chmod +x skorbut.sh ./skorbut.sh ``` - Press `Command⌘ Space` (or click the magnifying glass 🔍 in the top right corner of the screen) to open Spotlight - Write `terminal` and confirm with Enter - A terminal appears - Press `Command⌘ V` to paste and execute the script From now on, simply double-click `skorbut.sh` in the `Downloads` folder to start Skorbut.
Feel free to move `skorbut.jar` and `skorbut.sh` to the Desktop or any other folder you prefer. ### 🐧 Ubuntu, Linux Mint, Debian... ```sh sudo apt install default-jdk cd Downloads curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar chmod +x skorbut.jar echo java -version > skorbut.sh echo java -jar -Dsun.java2d.opengl=True skorbut.jar >> skorbut.sh chmod +x skorbut.sh ./skorbut.sh ``` From now on, simply double-click `skorbut.sh` in the `Downloads` folder to start Skorbut.
Feel free to move `skorbut.jar` and `skorbut.sh` to the Desktop or any other folder you prefer. ### I would rather compile Skorbut from source! ``` git clone https://github.com/fredoverflow/freditor cd freditor mvn install cd .. git clone https://github.com/fredoverflow/skorbut cd skorbut mvn package ``` The executable `skorbut.jar` will be located inside the `target` folder. ## How do I save my code? The code is automatically saved to a new file each time you click the start button. The save folder is named `skorbut`, and it is located in your home directory. The full path is displayed in the title bar. ## Does Skorbut support auto-indentation? Yes, just hit Enter or Tab. ## What about auto-completion? Ctrl+Space after an identifier auto-completes to the longest common prefix of all identifiers in scope. For example, if `foo`, `bar` and `baz` are in scope, then `f` will be auto-completed to `foo`, but `b` will only be auto-completed to `ba`, because both `bar` and `baz` start with `ba`. Skorbut has scope-aware auto-completion for all identifiers *except* `struct` members; those are auto-completed globally, i.e. after `.` or `->`, *all* `struct` members are considered for auto-completion. (That's because auto-completion has no type information available yet. Changing this would be a Herculean task.) ## What features of C are currently missing? Non-exhaustive list off the top of my head: | Feature | Priority | | ------------------- | -------- | | preprocessor | very low | | variadic functions | very low | | compound assignment | low | | null pointer | medium | | union | very low | | return struct | low | ## Wait, no preprocessor? How do I `#include `? You don't need to include anything, the following standard library functions are already available: - printf - scanf - puts - putchar - getchar - malloc - free - realloc - qsort - bsearch - strlen - strcmp - pow - time ## What about my own header files? Skorbut does not support multiple translation units, so there would be no point in supporting header files. ## How do I define constants without `#define`? For integral constants, you can use anonymous enumerations like `enum { N = 10 };` ## Keyboard shortcuts | Windows | Effect | Macintosh | | -----------: | :-------------------: | ---------------- | | F1 | show type | F1 | | F3 | declaration
usages | F3 | | F5 | step into | F5 | | F6 | step over | F6 | | F7 | step return | F7 | | Tab
Enter | auto-indent | Tab
Enter | | Ctrl Space | auto-complete | Control (Shift) Space | | Ctrl Alt R | rename symbol | Command Option R | | Ctrl D | delete line | Command D | | Ctrl C | copy | Command C | | Ctrl X | cut | Command X | | Ctrl V | paste | Command V | | Ctrl Z | undo | Command Z | | Ctrl Y | redo | Command Y | ================================================ FILE: pom.xml ================================================ 4.0.0 fredoverflow skorbut 0.1.0-SNAPSHOT UTF-8 official 2.2.21 1.8 1.8 1.8 MainKt ${java.home}/lib/rt.jar java-modules [9,) ${java.home}/jmods(!**.jar;!module-info.class) fredoverflow freditor 0.1.0-SNAPSHOT org.jetbrains.kotlin kotlin-stdlib ${kotlin.version} org.junit.jupiter junit-jupiter-api 5.14.0 test src/main/kotlin src/test/kotlin org.jetbrains.kotlin kotlin-maven-plugin ${kotlin.version} compile compile test-compile test-compile maven-surefire-plugin 3.5.4 maven-jar-plugin 3.4.2 ${main.class} com.github.wvengen proguard-maven-plugin 2.6.1 package proguard true META-INF/MANIFEST.MF,!META-INF/**,!**.kotlin_* ${project.artifactId}.jar ================================================ FILE: sloc ================================================ # sloc: count significant lines of code # INsignificant lines contain only spaces and/or braces # $1 directory # $2 extension function countInDirectory { lines=$(find "src/$1" -name "*.$2" -exec grep -vP "^[{ }]*\r?$" {} + | wc -l) if [ $lines -gt 0 ] then printf "$1: $lines\n" fi } # $1 language # $2 extension function countForLanguage { printf "$1\n" printf "==========\n" countInDirectory main $2 countInDirectory test $2 printf "\n" } countForLanguage "Kotlin" "kt" ================================================ FILE: src/main/kotlin/Main.kt ================================================ import freditor.SwingConfig import ui.MainFrame import java.awt.EventQueue fun main() { SwingConfig.metalWithDefaultFont(SwingConfig.SANS_SERIF_PLAIN_16) EventQueue.invokeLater(::MainFrame) } ================================================ FILE: src/main/kotlin/common/Counter.kt ================================================ package common class Counter { private val counter = HashMap() fun count(x: Any): Int { val soFar = counter.getOrElse(x) { 0 } counter[x] = soFar + 1 return soFar } } ================================================ FILE: src/main/kotlin/common/Diagnostic.kt ================================================ package common data class Diagnostic( val position: Int, override val message: String, val secondPosition: Int = -1, val columnDelta: Int = 0 ) : Exception(message) { override fun toString(): String { return if (secondPosition < 0) { message } else { "$message \uD83D\uDDB0 toggle position" } } } ================================================ FILE: src/main/kotlin/common/Maps.kt ================================================ package common fun > M.puts(keys: Array, value: V): M { for (key in keys) { put(key, value) } return this } fun > M.puts(k1: K, k2: K, value: V): M { put(k1, value) put(k2, value) return this } fun > M.puts(k1: K, k2: K, k3: K, k4: K, value: V): M { put(k1, value) put(k2, value) put(k3, value) put(k4, value) return this } fun > M.puts(k1: K, k2: K, k3: K, k4: K, k5: K, k6: K, value: V): M { put(k1, value) put(k2, value) put(k3, value) put(k4, value) put(k5, value) put(k6, value) return this } ================================================ FILE: src/main/kotlin/interpreter/BasicBlock.kt ================================================ package interpreter class BasicBlock { private val statements = ArrayList() fun getStatements(): List = statements fun isOpen(): Boolean { return !isClosed() } fun isClosed(): Boolean { return statements.lastOrNull() is TransferringControl } fun isEmpty(): Boolean { return statements.isEmpty() } fun add(statement: FlatStatement) { assert(isOpen()) statements.add(statement) } fun replaceSwitchPlaceholderWithRealSwitch(replacement: HashSwitch) { assert(statements.last() === SwitchPlaceholder) statements[statements.lastIndex] = replacement } var isReachable = false private set fun exploreReachability(resolve: (String) -> BasicBlock) { if (!isReachable) { isReachable = true statements.lastOrNull()?.forEachSuccessor { label -> resolve(label).exploreReachability(resolve) } } } } ================================================ FILE: src/main/kotlin/interpreter/BuildControlFlowGraph.kt ================================================ package interpreter import semantic.types.ArithmeticType import syntax.lexer.missingIdentifier import syntax.tree.* val unusedEmptyHashMap = HashMap() class State( val continueTarget: String, val breakTarget: String, val switchControlType: ArithmeticType?, val cases: HashMap, var default: String? ) { constructor() : this("", "", null, unusedEmptyHashMap, null) fun openLoop(continueTarget: String, breakTarget: String): State { return State(continueTarget, breakTarget, switchControlType, cases, default) } fun openSwitch(controlType: ArithmeticType, breakTarget: String): State { return State(continueTarget, breakTarget, controlType, HashMap(), null) } } class BuildControlFlowGraph(function: FunctionDefinition) { private val controlFlowGraph = LinkedHashMap() private var lastGeneratedLabel = -1 private var currentLabelStr = "" private var currentBasicBlock = BasicBlock() private fun generateLabel(): String { return "${++lastGeneratedLabel}" } private fun insertLabel(label: String) { if (!currentBasicBlock.isEmpty()) { jumpIfOpen(label) currentBasicBlock = BasicBlock() } currentLabelStr = label controlFlowGraph[currentLabelStr] = currentBasicBlock } private fun add(statement: FlatStatement) { if (currentBasicBlock.isClosed()) { currentBasicBlock = BasicBlock() currentLabelStr = generateLabel() controlFlowGraph[currentLabelStr] = currentBasicBlock } currentBasicBlock.add(statement) } private fun jumpIfOpen(target: String) { if (currentBasicBlock.isOpen()) { add(Jump(missingIdentifier, target)) } } init { currentLabelStr = generateLabel() insertLabel(currentLabelStr) function.body.flatten(State()) val entry = controlFlowGraph.values.first() entry.exploreReachability { controlFlowGraph[it]!! } function.controlFlowGraph = controlFlowGraph } private fun List.flatten(state: State) { for (statement in this) { statement.flatten(state) } } private fun Statement.flatten(state: State) { when (this) { is Block -> { statements.flatten(state) } is Declaration -> { add(FlatDeclaration(specifiers, namedDeclarators)) } is ExpressionStatement -> { add(FlatExpressionStatement(expression)) } is LabeledStatement -> { insertLabel(label.text) statement.flatten(state) } is Goto -> { add(Jump(goto, label.text)) } is IfThenElse -> { if (e1se == null) { val execute = generateLabel() val done = generateLabel() add(JumpIf(condition, execute, done)) insertLabel(execute) th3n.flatten(state) insertLabel(done) } else { val executeThen = generateLabel() val executeElse = generateLabel() val done = generateLabel() add(JumpIf(condition, executeThen, executeElse)) insertLabel(executeThen) th3n.flatten(state) jumpIfOpen(done) insertLabel(executeElse) e1se.flatten(state) insertLabel(done) } } is Switch -> { val done = generateLabel() @Suppress("NAME_SHADOWING") val state = state.openSwitch(control.type.unqualified() as ArithmeticType, breakTarget = done) add(SwitchPlaceholder) val basicBlock = currentBasicBlock body.flatten(state) basicBlock.replaceSwitchPlaceholderWithRealSwitch( HashSwitch(control, state.cases, state.default ?: done) ) insertLabel(done) } is Case -> { if (state.switchControlType == null) { case.error("case label must be nested inside a switch") } val caseLabel = generateLabel() insertLabel(caseLabel) val previous = state.cases.put( state.switchControlType.integralPromotions().cast(choice.value as ArithmeticValue), caseLabel ) if (previous != null) { case.error("duplicate case label") } body.flatten(state) } is Default -> { if (state.switchControlType == null) { default.error("default label must be nested inside a switch") } if (state.default != null) { default.error("duplicate default label") } val defaultLabel = generateLabel() insertLabel(defaultLabel) state.default = defaultLabel body.flatten(state) } is Do -> { val bodyStart = generateLabel() val checkCondition = generateLabel() val done = generateLabel() @Suppress("NAME_SHADOWING") val state = state.openLoop(continueTarget = checkCondition, breakTarget = done) insertLabel(bodyStart) body.flatten(state) insertLabel(checkCondition) add(JumpIf(condition, bodyStart, done)) insertLabel(done) } is While -> { val checkCondition = generateLabel() val bodyStart = generateLabel() val done = generateLabel() @Suppress("NAME_SHADOWING") val state = state.openLoop(continueTarget = checkCondition, breakTarget = done) insertLabel(checkCondition) add(JumpIf(condition, bodyStart, done)) insertLabel(bodyStart) body.flatten(state) add(ImplicitContinue(whi1e, checkCondition)) insertLabel(done) } is For -> { when (init) { is ExpressionStatement -> { add(FlatExpressionStatement(init.expression)) } is Declaration -> { add(FlatDeclaration(init.specifiers, init.namedDeclarators)) } } val checkCondition = generateLabel() val loopStart = generateLabel() val updateCounter = generateLabel() val done = generateLabel() @Suppress("NAME_SHADOWING") val state = state.openLoop(continueTarget = updateCounter, breakTarget = done) if (condition != null) { insertLabel(checkCondition) add(JumpIf(condition, loopStart, done)) insertLabel(loopStart) body.flatten(state) insertLabel(updateCounter) if (update != null) { add(FlatExpressionStatement(update)) } add(ImplicitContinue(f0r, checkCondition)) insertLabel(done) } else { insertLabel(loopStart) body.flatten(state) insertLabel(updateCounter) if (update != null) { add(FlatExpressionStatement(update)) } add(ImplicitContinue(f0r, loopStart)) insertLabel(done) } } is Continue -> { if (state.continueTarget.isEmpty()) { continu3.error("continue must be nested inside a loop") } add(Jump(continu3, state.continueTarget)) } is Break -> { if (state.breakTarget.isEmpty()) { br3ak.error("break must be nested inside a loop or switch") } add(Jump(br3ak, state.breakTarget)) } is Return -> { add(FlatReturn(r3turn, result)) } is Assert -> { add(FlatAssert(condition)) } else -> { error("no flatten for $this") } } } } ================================================ FILE: src/main/kotlin/interpreter/Console.kt ================================================ package interpreter import semantic.types.DoubleType import semantic.types.FloatType import semantic.types.SignedCharType import semantic.types.SignedIntType import syntax.lexer.Token import text.skipDigits import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.atomic.AtomicBoolean class Console { private val output = StringBuilder() var isDirty: Boolean = false private set private var input = StringBuilder() private val queue = LinkedBlockingDeque() private val blocked = AtomicBoolean(false) fun isBlocked(): Boolean = blocked.get() var update: Function0? = null fun puts(str: PointerValue) { print(stringStartingAt(str)) putchar('\n') } fun putchar(x: Char) { output.append(x) isDirty = true } fun print(x: CharSequence) { output.append(x) isDirty = true } fun printf(format: Token, arguments: List): Int { val sb = StringBuilder() val args = arguments.iterator() val fmt = format.text var i = 0 var k = fmt.indexOf('%') while (k != -1) { sb.append(fmt, i, k) if (fmt[++k] == '%') { sb.append('%') } else { i = k k = fmt.skipDigits(i) // width if (fmt[k] == '.') { k = fmt.skipDigits(k + 1) // precision } val specifier = fmt.substring(i - 1, k + 1) sb.append(formatValue(args.next(), specifier)) } i = k + 1 k = fmt.indexOf('%', i) } sb.append(fmt, i, fmt.length) print(sb) return sb.length } private fun formatValue(value: Value, specifier: String): String { return when (specifier.last()) { 'c' -> specifier.format((value as ArithmeticValue).value.toLong().toInt().and(0xff)) 'i' -> specifier.replace('i', 'd').format((value as ArithmeticValue).value.toLong().toInt()) 'u' -> specifier.replace('u', 'd').format((value as ArithmeticValue).value.toLong().and(0xffffffff)) 'd', 'o', 'x', 'X' -> specifier.format((value as ArithmeticValue).value.toLong().toInt()) 'e', 'E', 'f', 'g', 'G' -> specifier.format(java.util.Locale.ENGLISH, (value as ArithmeticValue).value) 's' -> specifier.format(stringStartingAt(value as PointerValue)) 'p' -> specifier.replace('p', 's').format(value.show()) else -> error("illegal conversion specifier %${specifier.last()}") } } private fun stringStartingAt(start: PointerValue): CharSequence { val sb = StringBuilder() var ptr = start var x = (ptr.referenced.evaluate() as ArithmeticValue).value while (x != 0.0) { sb.append(x.toInt().and(0xff).toChar()) ptr += 1 if (ptr.referenced.isSentinel()) error("missing NUL terminator") x = (ptr.referenced.evaluate() as ArithmeticValue).value } return sb } fun scanf(format: Token, arguments: List, after: Function0?): Int { val fmt = format.text var i = 0 var a = 0 while (i < fmt.length) { when (fmt[i++]) { '\t', '\n', ' ' -> skipWhitespace() '%' -> { val percent = i - 1 if (i == fmt.length) format.stringErrorAt(percent, "incomplete conversion specifier") if (a == arguments.size) format.stringErrorAt(percent, "missing argument after format string") val arg = arguments[a++] as PointerValue val referenced = arg.referenced val type = referenced.type when (fmt[i++]) { 'd' -> { if (type !== SignedIntType) format.stringErrorAt(percent, "%d expects int*, not ${type.pointer()}") skipWhitespace() val x = scanInt() ?: return a - 1 referenced.assign(Value.signedInt(x)) } 'f' -> { if (type !== FloatType) format.stringErrorAt(percent, "%f expects float*, not ${type.pointer()}") skipWhitespace() val x = scanDouble() ?: return a - 1 referenced.assign(Value.float(x.toFloat())) } 'l' -> { if (i == fmt.length || fmt[i] != 'f') format.stringErrorAt(i, "missing f after %l") ++i if (type !== DoubleType) format.stringErrorAt(percent, "%lf expects double*, not ${type.pointer()}") skipWhitespace() val x = scanDouble() ?: return a - 1 referenced.assign(Value.double(x)) } 'c' -> { if (type !== SignedCharType) format.stringErrorAt(percent, "%c expects char*, not ${type.pointer()}") referenced.assign(Value.signedChar(getchar())) } 's' -> { if (type !== SignedCharType) format.stringErrorAt(percent, "%s expects char*, not ${type.pointer()}") val maxLen = referenced.bound - referenced.index - 1 format.stringErrorAt(percent, "%s is unsafe\nuse %${maxLen}s instead") } '1', '2', '3', '4', '5', '6', '7', '8', '9' -> { var len = fmt[i - 1] - '0' while (i < fmt.length && fmt[i] in '0'..'9') { len = len * 10 + (fmt[i++] - '0') } if (i == fmt.length || fmt[i] != 's') format.stringErrorAt(i, "missing s after %$len") ++i if (type !== SignedCharType) format.stringErrorAt(percent, "%s expects char*, not ${type.pointer()}") val maxLen = referenced.bound - referenced.index - 1 if (len > maxLen) format.stringErrorAt(percent, "%${len}s is ${len - maxLen} too long\nuse %${maxLen}s instead") skipWhitespace() val x = scanString(len) var obj = referenced for (c in x) { obj.assign(Value.signedChar(c)) obj += 1 } obj.assign(Value.NUL) } else -> format.stringErrorAt(percent, "illegal conversion specifier %${fmt[i - 1]}") } after?.invoke() } else -> if (getchar() != fmt[i - 1]) { unget() return a } } } return arguments.size } private fun skipWhitespace() { @Suppress("ControlFlowWithEmptyBody") while (getchar().isWhitespace()) { } unget() } private fun scanInt(): Int? { var c = getchar() var sign = 1 if (c == '-') { sign = -1 c = getchar() } if (c !in '0'..'9') return null var x = c - '0' while (getchar() in '0'..'9') { x = x * 10 + (current - '0') } unget() return sign * x } private fun scanDouble(): Double? { var c = getchar() var sign = 1 if (c == '-') { sign = -1 c = getchar() } if (c !in '0'..'9') return null var x = (c - '0').toDouble() while (getchar() in '0'..'9') { x = x * 10 + (current - '0') } if (current == '.') { var decimal = 1.0 while (getchar() in '0'..'9') { decimal /= 10 x += (current - '0') * decimal } } unget() return sign * x } private fun scanString(len: Int): CharSequence { val sb = StringBuilder() while (getchar() > ' ') { sb.append(current) if (sb.length == len) return sb } unget() return sb } fun getText(): String { isDirty = false if (!isBlocked()) return output.toString() val result = StringBuilder() result.append(output) result.append(input) result.append('_') return result.toString() } fun keyTyped(x: Char) { when (x) { in '\u0020'..'\u007e' -> input.append(x) in '\u00a0'..'\u00ff' -> input.append(x) '\b' -> backspace() '\n' -> enter() '\u0004', '\u001a' -> stop() } } private fun backspace() { val len = input.length if (len > 0) { input.setLength(len - 1) } } private fun enter() { input.append('\n') output.append(input) val temp = input input = StringBuilder() blocked.set(false) for (x in temp) { queue.put(x) } } fun stop() { queue.put('\uffff') } fun getchar(): Char { val x: Char? = queue.poll() if (x != null) return remember(x) blocked.set(true) update?.invoke() val y: Char = queue.take() return remember(y) } private fun remember(x: Char): Char { current = x return x } private var current = '\u0000' private fun unget() { queue.putFirst(current) current = '\u0000' } } ================================================ FILE: src/main/kotlin/interpreter/FlatStatements.kt ================================================ package interpreter import syntax.lexer.Token import syntax.lexer.missingIdentifier import syntax.tree.DeclarationSpecifiers import syntax.tree.Expression import syntax.tree.NamedDeclarator sealed class FlatStatement { abstract fun root(): Token open fun forEachSuccessor(action: (String) -> Unit) { } } sealed class TransferringControl : FlatStatement() class Jump(val keyword: Token, val target: String) : TransferringControl() { override fun root(): Token = keyword override fun forEachSuccessor(action: (String) -> Unit) { action(target) } } class ImplicitContinue(val keyword: Token, val target: String) : TransferringControl() { override fun root(): Token = keyword override fun forEachSuccessor(action: (String) -> Unit) { action(target) } } class JumpIf(val condition: Expression, val th3n: String, val e1se: String) : TransferringControl() { override fun root(): Token = condition.root() override fun forEachSuccessor(action: (String) -> Unit) { action(th3n) action(e1se) } } object SwitchPlaceholder : TransferringControl() { override fun root(): Token = missingIdentifier } class HashSwitch(val control: Expression, val cases: HashMap, val default: String) : TransferringControl() { override fun root(): Token = control.root() override fun forEachSuccessor(action: (String) -> Unit) { cases.values.forEach { action(it) } action(default) } } class FlatDeclaration(val specifiers: DeclarationSpecifiers, val namedDeclarators: List) : FlatStatement() { override fun root(): Token = specifiers.root() } class FlatExpressionStatement(val expression: Expression) : FlatStatement() { override fun root(): Token = expression.root() } class FlatReturn(val r3turn: Token, val result: Expression?) : TransferringControl() { override fun root(): Token = r3turn } class FlatAssert(val condition: Expression) : FlatStatement() { override fun root(): Token = condition.root() } ================================================ FILE: src/main/kotlin/interpreter/Interpreter.kt ================================================ package interpreter import common.Diagnostic import semantic.TypeChecker import semantic.types.* import syntax.lexer.Lexer import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.parser.Parser import syntax.parser.translationUnit import syntax.tree.* import java.time.LocalTime import java.time.format.DateTimeFormatter.ISO_TIME import java.time.temporal.ChronoUnit.SECONDS import kotlin.math.floor import kotlin.math.pow import kotlin.random.Random fun FunctionDefinition.returnType(): Type = (namedDeclarator.type as FunctionType).returnType class Interpreter(program: String) { val translationUnit = Parser(Lexer(program)).translationUnit() val typeChecker = TypeChecker(translationUnit) private val functions = translationUnit.functions.associateBy(FunctionDefinition::name) var onMemorySet: Function1? = null private var memory = Memory(emptySet(), emptyList()) private set(value) { field = value onMemorySet?.invoke(value) } val console = Console() var stackDepth = 0 private set private var passedAssertions = 0 private var targetType: Type = VoidPointerType init { for (function in translationUnit.functions) { BuildControlFlowGraph(function) } } var before: Function1? = null var after: Function0? = null fun run(cursor: Int, bottom: Int) { val main = translationUnit.functions.firstOrNull { it.name() == "main" } if (main != null) { if (main.returnType() !== SignedIntType || main.parameters.isNotEmpty()) { main.specifiers.root().error("int main() expected") } runMain(main) } else { val (beforeCursor, afterCursor) = translationUnit.functions .filter { it.returnType() === VoidType && it.parameters.isEmpty() } .partition { it.closingBrace.end < cursor } val entryPoint = afterCursor.firstOrNull() ?: beforeCursor.lastOrNull() ?: throw Diagnostic( bottom, "missing entry point, must provide one of:\n· void allNamesAreFine()\n· int main()" ) run(entryPoint) } } private fun runMain(main: FunctionDefinition) { initializeMemory(main) val exitCode = main.execute(emptyList()) as ArithmeticValue console.print("\nmain finished with exit code ${exitCode.value.toInt()}\n") console.update?.invoke() reportMemoryLeaks(main) } private fun run(entryPoint: FunctionDefinition) { initializeMemory(entryPoint) entryPoint.execute(emptyList()) if (passedAssertions != 0) { val now = LocalTime.now().truncatedTo(SECONDS).format(ISO_TIME) console.print("\n[$now] ${entryPoint.name()}: ALL $passedAssertions assertions PASSED\n") console.update?.invoke() } reportMemoryLeaks(entryPoint) } private fun initializeMemory(start: FunctionDefinition) { val usedStringLiterals = HashSet() val exploredFunctions = hashSetOf(start) val staticVariables: Map = translationUnit.declarations .filter { declaration -> declaration.specifiers.storageClass != TYPEDEF } .flatMap(Declaration::namedDeclarators) .filter { namedDeclarator -> namedDeclarator.offset < 0 && namedDeclarator.type.requiresStorage() } .associateBy { namedDeclarator -> namedDeclarator.name.text } val usedStaticOffsets = HashSet() fun explore(parent: Node) { parent.walkChildren({}) { node -> when (node) { is StringLiteral -> { usedStringLiterals.add(node.literal.text) } is Identifier -> { if (node.type is FunctionType) { functions[node.name.text]?.let { function -> if (exploredFunctions.add(function)) { explore(function) } } } else if (node.symbol.offset < 0) { staticVariables[node.name.text]?.let { variable -> if (usedStaticOffsets.add(node.symbol.offset)) { explore(variable) } } } } } } } explore(start) val stringLiterals = typeChecker.stringLiterals stringLiterals.retainAll(usedStringLiterals) if (usedStaticOffsets.isEmpty()) { memory = Memory(stringLiterals, emptyList()) } else { memory = Memory(stringLiterals, staticVariables.values.map { namedDeclarator -> if (namedDeclarator.offset in usedStaticOffsets) namedDeclarator else namedDeclarator.hidden() }) for (namedDeclarator in staticVariables.values) { with(namedDeclarator) { if (declarator is Declarator.Initialized && offset in usedStaticOffsets) { initialize(type, declarator.init, memory.staticVariables, offset + Int.MIN_VALUE) } else { defaultInitialize(type, memory.staticVariables, offset + Int.MIN_VALUE) } } } } } private fun reportMemoryLeaks(function: FunctionDefinition) { if (memory.heap.isNotEmpty()) { function.closingBrace.error("${memory.heap.size} missing free calls") } } private fun FunctionDefinition.execute(arguments: List): Value { ++stackDepth try { val stackFrame = Segment(stackFrameType) memory.stack.add(stackFrame) for ((param, arg) in parameters.zip(arguments)) { param.type.cast(arg).store(stackFrame, param.offset) } after?.invoke() var basicBlock = controlFlowGraph["0"]!!.getStatements() var pc = 0 while (pc != basicBlock.size) { with(basicBlock[pc++]) { when (this) { is Jump -> { basicBlock = controlFlowGraph[target]!!.getStatements() pc = 0 } is ImplicitContinue -> { basicBlock = controlFlowGraph[target]!!.getStatements() pc = 0 } is JumpIf -> { val target = if (condition.delayedCondition()) th3n else e1se basicBlock = controlFlowGraph[target]!!.getStatements() pc = 0 } is HashSwitch -> { val target = cases[(control.delayed() as ArithmeticValue).integralPromotions()] ?: default basicBlock = controlFlowGraph[target]!!.getStatements() pc = 0 } is FlatDeclaration -> { for (namedDeclarator in namedDeclarators) { with(namedDeclarator) { if (declarator is Declarator.Initialized && offset >= 0) { before?.invoke(name.start) initialize(type, declarator.init, stackFrame, offset) after?.invoke() } } } } is FlatExpressionStatement -> { expression.delayed() } is FlatReturn -> { if (result == null) { before?.invoke(r3turn.start) after?.invoke() } else { targetType = this@execute.returnType() return targetType.cast(result.delayed()).also { memory.popStackFrameUnlessEntryPoint() } } } is FlatAssert -> { if (condition is RelationalEquality && condition.right.type is ArithmeticType) { val left = condition.left.delayed() val right = condition.right.evaluate() if (relationalEquality( left as ArithmeticValue, condition.operator, right as ArithmeticValue ).isFalse() ) { val leftShow = left.show() condition.root().error( " $leftShow ${condition.operator} ${right.show()} ", -leftShow.length - 2 ) } } else if (!condition.delayedCondition()) { condition.root().error("assertion failed") } ++passedAssertions } else -> error("no execute for $this") } } } before?.invoke(closingBrace.start) if (returnType() !== VoidType) { throw Diagnostic(closingBrace.start, "missing return statement") } memory.popStackFrameUnlessEntryPoint() return VoidValue } finally { --stackDepth } } private fun initialize(qualified: Type, init: Initializer, segment: Segment, start: Int): Int { val type = qualified.unqualified() when (init) { is ExpressionInitializer -> { return if (init.expression is StringLiteral && type is ArrayType && type.elementType == SignedCharType) { val str = init.expression.literal.text for ((i, c) in str.withIndex()) { segment[start + i] = Value.signedChar(c) } for (i in str.length until type.size) { segment[start + i] = Value.NUL } start + type.size } else { targetType = type val value = type.cast(init.expression.evaluate()) value.store(segment, start) } } is InitializerList -> { when (type) { is ArrayType -> return init.list.fold(start) { offset, initializer -> initialize(type.elementType, initializer, segment, offset) } is StructType -> return type.members.zip(init.list).fold(start) { offset, memberInitializer -> initialize(memberInitializer.first.type, memberInitializer.second, segment, offset) } } } } error("no init for $init") } private fun defaultInitialize(qualified: Type, segment: Segment, start: Int): Int { val type = qualified.unqualified() when (type) { is ArithmeticType -> { segment[start] = type.defaultValue } is ArrayType -> { (0 until type.size).fold(start) { offset, _ -> defaultInitialize(type.elementType, segment, offset) } } is StructType -> { type.members.fold(start) { offset, member -> defaultInitialize(member.type, segment, offset) } } } return start + type.count() } private fun Expression.delayed(): Value { before?.invoke(root().start) val result = evaluate() after?.invoke() return result } private fun Expression.delayedCondition(): Boolean { return (delayed() as ArithmeticValue).isTrue() } private fun Expression.locate(): Object { return when (this) { is StringLiteral -> { memory.stringObjects[literal.text]!! } is Identifier -> { memory.makeObject(symbol) } is Subscript -> { val left = left.evaluate() val right = right.evaluate() if (left is PointerValue && right is ArithmeticValue) { left.referenced.checkReferable() + right.value.toInt() } else if (left is ArithmeticValue && right is PointerValue) { right.referenced.checkReferable() + left.value.toInt() } else { error("no locate for $this") } } is DirectMemberAccess -> { val struct = left.locate() val type = struct.type.unqualified() as StructType val member = type.member(right) Object(struct.segment, struct.offset + member!!.offset, member.type, 0, 1) } is IndirectMemberAccess -> { val pointer = left.evaluate() as PointerValue val struct = pointer.referenced val type = struct.type.unqualified() as StructType val member = type.member(right) Object(struct.segment, struct.offset + member!!.offset, member.type, 0, 1) } is Dereference -> { val pointer = operand.evaluate() as PointerValue pointer.referenced.checkReferable() } else -> error("no locate for $this") } } private fun Expression.evaluate(): Value { value?.let { return it } return when (this) { is Identifier -> { val symbolType = symbol.type if (symbolType is FunctionType) { FunctionDesignator(symbol.name, symbolType) } else { locate().evaluate() } } is PrintfCall -> { Value.signedInt(console.printf(format, arguments.map { it.evaluate() })) } is ScanfCall -> { Value.signedInt(console.scanf(format, arguments.map { it.evaluate() }, after)) } is Postfix -> { val obj = operand.locate() val oldValue = obj.evaluate() val newValue = if (oldValue is ArithmeticValue) { val result = if (operator.kind == PLUS_PLUS) oldValue + Value.ONE else oldValue - Value.ONE oldValue.type.cast(result) } else if (oldValue is PointerValue) { if (operator.kind == PLUS_PLUS) oldValue + 1 else oldValue - 1 } else { error("no evaluate for $this") } obj.assign(newValue) oldValue } is FunctionCall -> { val func = (function.evaluate().decayed() as FunctionPointerValue).designator val name = func.functionName val definition = functions[name.text] if (definition != null) { val evaluatedArguments = definition.parameters.zip(arguments).map { targetType = it.first.type it.second.evaluate() } return definition.execute(evaluatedArguments) } when (name.text) { "pow" -> { val base = (arguments[0].evaluate() as ArithmeticValue).value val exponent = (arguments[1].evaluate() as ArithmeticValue).value return ArithmeticValue(base.pow(exponent), DoubleType) } "time" -> { return ArithmeticValue(floor(System.currentTimeMillis() / 1000.0), UnsignedIntType) } "puts" -> { console.puts(arguments[0].evaluate() as PointerValue) return VoidValue } "putchar" -> { val arg = arguments[0].evaluate() as ArithmeticValue console.putchar(arg.value.toLong().toInt().and(0xff).toChar()) return VoidValue } "getchar" -> { return Value.signedInt(console.getchar().code.toByte().toInt()) } "malloc" -> { return allocate(function, arguments[0], memory::malloc, memory::malloc) } "free" -> { memory.free(arguments[0].evaluate() as PointerValue) return VoidValue } "realloc" -> { val pointer = arguments[0].evaluate() as PointerValue return allocate( function, arguments[1], // DO NOT REFACTOR: The lambdas call different realloc overloads! { type -> memory.realloc(pointer, type) }, { type -> memory.realloc(pointer, type) } ) } "memswap" -> { val p = arguments[0].evaluate() as PointerValue val q = arguments[1].evaluate() as PointerValue if (p.referenced.type != q.referenced.type) { error("${p.referenced.type} != ${q.referenced.type}") } val size = (arguments[2].evaluate() as ArithmeticValue).value.toInt() val actualSize = p.referenced.type.sizeof() if (size != actualSize) { error("element type ${p.referenced.type} has size $actualSize, not $size") } swap(p, q) return VoidValue } "qsort" -> { val base = arguments[0].evaluate() as PointerValue val count = (arguments[1].evaluate() as ArithmeticValue).value.toInt() val maxCount = base.referenced.bound - base.referenced.index if (count > maxCount) { error("There are only $maxCount elements in the array, not $count") } val size = (arguments[2].evaluate() as ArithmeticValue).value.toInt() val actualSize = base.referenced.type.sizeof() if (size != actualSize) { error("element type ${base.referenced.type} has size $actualSize, not $size") } val comp = arguments[3].evaluate().decayed() as FunctionPointerValue qsort(base, count, functions[comp.designator.functionName.text]!!) return VoidValue } "bsearch" -> { val key = arguments[0].evaluate() as PointerValue val base = arguments[1].evaluate() as PointerValue val count = (arguments[2].evaluate() as ArithmeticValue).value.toInt() val maxCount = base.referenced.bound - base.referenced.index if (count > maxCount) { error("There are only $maxCount elements in the array, not $count") } val size = (arguments[3].evaluate() as ArithmeticValue).value.toInt() val actualSize = base.referenced.type.sizeof() if (size != actualSize) { error("element type ${base.referenced.type} has size $actualSize, not $size") } val comp = arguments[4].evaluate().decayed() as FunctionPointerValue return bsearch(key, base, count, functions[comp.designator.functionName.text]!!) } "strlen" -> { val s = arguments[0].evaluate() as PointerValue return strlen(s, 0) } "strcmp" -> { val s = arguments[0].evaluate() as PointerValue val t = arguments[1].evaluate() as PointerValue return strcmp(s, t) } else -> error("undefined function $name") } } is Prefix -> { val obj = operand.locate() val oldValue = obj.evaluate() val newValue = if (oldValue is ArithmeticValue) { val result = if (operator.kind == PLUS_PLUS) oldValue + Value.ONE else oldValue - Value.ONE oldValue.type.cast(result) } else if (oldValue is PointerValue) { if (operator.kind == PLUS_PLUS) oldValue + 1 else oldValue - 1 } else { error("no evaluate for $this") } obj.assign(newValue) newValue } is Reference -> { if (operand.type is FunctionType) { FunctionPointerValue(operand.evaluate() as FunctionDesignator) } else { PointerValue(operand.locate()) } } is Dereference -> { if ((operand.type.decayed() as PointerType).referencedType is FunctionType) { (operand.evaluate().decayed() as FunctionPointerValue).designator } else { locate().evaluate() } } is UnaryPlus -> { unaryPlus(operand.evaluate()) } is UnaryMinus -> { unaryMinus(operand.evaluate()) } is BitwiseNot -> { bitwiseNot(operand.evaluate()) } is LogicalNot -> { logicalNot(operand.evaluate()) } is Multiplicative -> { multiplicative(left.evaluate(), operator, right.evaluate()) } is Plus -> { val left = left.evaluate() val right = right.evaluate() if (left is PointerValue && right is ArithmeticValue) { pointerPlus(left, this.left.type, right) } else if (left is ArithmeticValue && right is PointerValue) { pointerPlus(right, this.right.type, left) } else { plus(left, right) } } is Minus -> { val type = this.left.type val left = left.evaluate() val right = right.evaluate() if (left is PointerValue && right is ArithmeticValue) { pointerMinus(left, type, right) } else if (left is PointerValue && right is PointerValue) { if (type is VoidPointerType || type is ConstVoidPointerType) { Value.signedInt((left - right) * left.referenced.type.sizeof()) } else { Value.signedInt(left - right) } } else { minus(left, right) } } is Shift -> { shift(left.evaluate(), operator, right.evaluate(), type) } is RelationalEquality -> { val left = left.evaluate() val right = right.evaluate() if (left is PointerValue && right is PointerValue) { Value.truth( when (operator.kind) { LESS -> left.less(right) MORE -> right.less(left) LESS_EQUAL -> !right.less(left) MORE_EQUAL -> !left.less(right) EQUAL_EQUAL -> left.equal(right) BANG_EQUAL -> !left.equal(right) else -> error("no evaluate for $this") } ) } else { relationalEquality(left as ArithmeticValue, operator, right as ArithmeticValue) } } is Bitwise -> { bitwise(left.evaluate(), operator, right.evaluate(), type) } is Logical -> { val left = left.evaluate() as ArithmeticValue when (operator.kind) { AMPERSAND_AMPERSAND -> { if (left.isFalse()) Value.ZERO else (right.evaluate() as ArithmeticValue).normalizeBool() } BAR_BAR -> { if (left.isTrue()) Value.ONE else (right.evaluate() as ArithmeticValue).normalizeBool() } else -> error("no evaluate for $this") } } is Conditional -> { val condition = condition.evaluate() as ArithmeticValue val result = if (condition.isTrue()) th3n.delayed() else e1se.delayed() type.cast(result) } is Cast -> { type.cast(operand.evaluate()) } is Assignment -> { targetType = left.type val value = left.type.cast(right.evaluate()) val obj = left.locate() obj.preventSentinelAccess() value.store(obj.segment, obj.offset) value } is PlusAssignment -> { val target = left.locate() val left = target.evaluate() val right = right.evaluate() as ArithmeticValue val value = when (left) { is ArithmeticValue -> target.type.cast(left + right) is PointerValue -> pointerPlus(left, target.type, right) else -> error("no evaluate for $this") } target.assign(value) value } is MinusAssignment -> { val target = left.locate() val left = target.evaluate() val right = right.evaluate() as ArithmeticValue val value = when (left) { is ArithmeticValue -> target.type.cast(left - right) is PointerValue -> pointerMinus(left, target.type, right) else -> error("no evaluate for $this") } target.assign(value) value } is Comma -> { left.evaluate() right.evaluate() } else -> { if (!isLocator) error("no evaluate for $this") locate().evaluate() } } } private fun Binary.pointerPlus(pointer: PointerValue, type: Type, arithmetic: ArithmeticValue): Value { val delta = arithmetic.value.toInt() return if (type is VoidPointerType || type is ConstVoidPointerType) { val sizeof = pointer.referenced.type.sizeof() if (delta % sizeof != 0) { this.root().error("$delta is not a multiple of $sizeof") } pointer + delta / sizeof } else { pointer + delta } } private fun Binary.pointerMinus(pointer: PointerValue, type: Type, arithmetic: ArithmeticValue): Value { val delta = arithmetic.value.toInt() return if (type is VoidPointerType || type is ConstVoidPointerType) { val sizeof = pointer.referenced.type.sizeof() if (delta % sizeof != 0) { this.root().error("$delta is not a multiple of $sizeof") } pointer - delta / sizeof } else { pointer - delta } } private fun qsort(base: PointerValue, count: Int, comp: FunctionDefinition) { // Programming Pearls // 11.3 Better Quicksorts fun q(l: Int, u: Int) { if (l < u) { val pivot = base + l swap(pivot, base + Random.nextInt(l, u + 1)) var i = l var j = u + 1 while (true) { do ++i while (i <= u && (comp.execute(listOf(base + i, pivot)) as ArithmeticValue).value < 0) do --j while (/* */ (comp.execute(listOf(base + j, pivot)) as ArithmeticValue).value > 0) if (i > j) break swap(base + i, base + j) } swap(pivot, base + j) q(l, j - 1) q(j + 1, u) } } q(0, count - 1) } private fun swap(p: PointerValue, q: PointerValue) { val a = p.referenced val b = q.referenced for (i in 0 until a.type.count()) { val x = a.segment[a.offset + i] val y = b.segment[b.offset + i] a.segment[a.offset + i] = y b.segment[b.offset + i] = x } } private fun bsearch(key: PointerValue, base: PointerValue, count: Int, comp: FunctionDefinition): Value { var left = 0 var right = count while (left < right) { val middle = (left + right).ushr(1) val comparison = (comp.execute(listOf(key, base + middle)) as ArithmeticValue).value when { comparison < 0 -> right = middle comparison > 0 -> left = middle + 1 else -> return base + middle } } return base + count } private tailrec fun strlen(s: PointerValue, len: Int): ArithmeticValue { val c = s.referenced.evaluate() as ArithmeticValue return when { (c == Value.NUL) -> Value.unsignedChar(len) else -> strlen(s + 1, len + 1) } } private tailrec fun strcmp(s: PointerValue, t: PointerValue): ArithmeticValue { val c = s.referenced.evaluate() as ArithmeticValue val d = t.referenced.evaluate() as ArithmeticValue return when { (c != d) -> (c - d) (c == Value.NUL) -> Value.ZERO else -> strcmp(s + 1, t + 1) } } private fun allocate( function: Expression, size: Expression, one: (Type) -> PointerValue, many: (ArrayType) -> PointerValue ): PointerValue { if (targetType === VoidPointerType) { function.root().error("cannot infer desired type to allocate via void*") } val elementType = (targetType as PointerType).referencedType val elementSize = elementType.sizeof() val requestedBytes = (size.evaluate() as ArithmeticValue).value.toInt() val arraySize = requestedBytes / elementSize if (arraySize * elementSize != requestedBytes) { size.root() .error("$requestedBytes is not a multiple of $elementSize. Did you forget to multiply by sizeof(element type)?") } return if (arraySize == 1) one(elementType) else many(ArrayType(arraySize, elementType)) } } fun unaryPlus(x: Value): Value { val a = x as ArithmeticValue return Value.ZERO + a } fun unaryMinus(x: Value): Value { val a = x as ArithmeticValue return Value.ZERO - a } fun bitwiseNot(x: Value): Value { val a = x as ArithmeticValue return Value.MINUS_ONE - a } fun logicalNot(x: Value): Value { val a = x as ArithmeticValue return Value.truth(a.isFalse()) } fun multiplicative(x: Value, operator: Token, y: Value): Value { val a = x as ArithmeticValue val b = y as ArithmeticValue return when (operator.kind) { ASTERISK -> a * b SLASH -> a / b PERCENT -> a % b else -> error("no evaluate for $operator") } } fun plus(x: Value, y: Value): Value { val a = x as ArithmeticValue val b = y as ArithmeticValue return a + b } fun minus(x: Value, y: Value): Value { val a = x as ArithmeticValue val b = y as ArithmeticValue return a - b } fun shift(x: Value, operator: Token, y: Value, type: Type): Value { val a = (x as ArithmeticValue).integralPromotions().value.toLong().toInt() val b = (y as ArithmeticValue).integralPromotions().value.toLong().toInt() val bits = when { operator.kind == LESS_LESS -> log(a, "<< ", b, a.shl(b)) x.type === UnsignedIntType -> log(a, ">>>", b, a.ushr(b)) else -> log(a, ">> ", b, a.shr(b)) } return type.cast(Value.signedInt(bits)) } fun relationalEquality(x: ArithmeticValue, operator: Token, y: ArithmeticValue): ArithmeticValue { val commonType = x.type.usualArithmeticConversions(y.type) val a = commonType.cast(x).value val b = commonType.cast(y).value return Value.truth( when (operator.kind) { LESS -> a < b MORE -> a > b LESS_EQUAL -> a <= b MORE_EQUAL -> a >= b EQUAL_EQUAL -> a == b BANG_EQUAL -> a != b else -> error("no evaluate for $operator") } ) } fun bitwise(x: Value, operator: Token, y: Value, type: Type): Value { val a = (x as ArithmeticValue).value.toLong().toInt() val b = (y as ArithmeticValue).value.toLong().toInt() val result = when (operator.kind) { AMPERSAND -> log(a, " & ", b, a.and(b)) CARET -> log(a, " ^ ", b, a.xor(b)) BAR -> log(a, " | ", b, a.or(b)) else -> error("no evaluate for $operator") } return type.cast(Value.signedInt(result)) } private fun log(x: Int, op: String, y: Int, z: Int): Int { println(""" ${x.toBinaryString32()} $x $op${y.toBinaryString32()} $y = ${z.toBinaryString32()} $z""") return z } private fun Int.toBinaryString32(): String { return nib(28) + nib(24) + " " + nib(20) + nib(16) + " " + nib(12) + nib(8) + " " + nib(4) + nib(0) } private fun Int.nib(pos: Int): String { return NIBBLES[this.ushr(pos).and(15)] } private val NIBBLES = arrayOf( "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111", ) ================================================ FILE: src/main/kotlin/interpreter/Memory.kt ================================================ package interpreter import semantic.Symbol import semantic.types.ArrayType import semantic.types.SignedCharType import semantic.types.StructType import semantic.types.Type import syntax.lexer.fakeIdentifier import syntax.lexer.missingIdentifier import syntax.tree.NamedDeclarator import kotlin.math.min fun Iterable.synthesizeStringConstantsType(): StructType { val symbols = ArrayList() val type = StructType(fakeIdentifier("string literals"), symbols) var offset = 0 for (str in this) { val size = str.length + 1 symbols.add(Symbol(missingIdentifier, ArrayType(size, SignedCharType), offset)) offset += size } return type } fun Iterable.synthesizeStaticVariablesType(): StructType { val symbols = ArrayList() val type = StructType(fakeIdentifier("static variables"), symbols) for (namedDeclarator in this) { with(namedDeclarator) { symbols.add(Symbol(name, this.type, offset)) } } return type } class Memory(stringLiterals: Iterable, variables: Iterable) { val stringObjects = HashMap() val stringConstants = Segment(stringLiterals.synthesizeStringConstantsType()) val staticVariables = Segment(variables.synthesizeStaticVariablesType()) val stack = ArrayList() val heap = ArrayList() init { var stringOffset = 0 for (stringLiteral in stringLiterals) { stringObjects[stringLiteral] = Object(stringConstants, stringOffset, ArrayType(stringLiteral.length + 1, SignedCharType), 0, 1) for (x in stringLiteral) { stringConstants[stringOffset++] = Value.signedChar(x) } stringConstants[stringOffset++] = Value.NUL } stringConstants.readOnlyErrorMessage = "attempt to modify a string literal" } fun makeObject(symbol: Symbol): Object { return with(symbol) { if (offset < 0) { Object(staticVariables, offset + Int.MIN_VALUE, type, 0, 1) } else { Object(stack.last(), offset, type, 0, 1) } } } fun popStackFrameUnlessEntryPoint() { if (stack.size > 1) { stack.removeAt(stack.lastIndex).kill() } } fun malloc(type: Type): PointerValue { val segment = Segment(type) heap.add(segment) return PointerValue(Object(segment, 0, type, 0, 1)) } fun malloc(arrayType: ArrayType): PointerValue { val segment = Segment(arrayType) heap.add(segment) return PointerValue(Object(segment, 0, arrayType.elementType, 0, arrayType.size)) } fun free(pointer: PointerValue) { val obj = pointer.referenced val segment = obj.segment if (!segment.alive) throw AssertionError("dangling pointer") if (segment !in heap) throw AssertionError("free on non-heap segment") if (obj.offset != 0) throw AssertionError("free in the middle of segment") segment.kill() heap.remove(segment) } fun realloc(pointer: PointerValue, type: Type): PointerValue { val oldSegment = pointer.referenced.segment val newSegment = Segment(type) val smallerCount = min(oldSegment.count(), newSegment.count()) for (i in 0 until smallerCount) { newSegment[i] = oldSegment[i] } free(pointer) heap.add(newSegment) return PointerValue(Object(newSegment, 0, type, 0, 1)) } fun realloc(pointer: PointerValue, arrayType: ArrayType): PointerValue { val oldSegment = pointer.referenced.segment val newSegment = Segment(arrayType) val smallerCount = min(oldSegment.count(), newSegment.count()) for (i in 0 until smallerCount) { newSegment[i] = oldSegment[i] } free(pointer) heap.add(newSegment) return PointerValue(Object(newSegment, 0, arrayType.elementType, 0, arrayType.size)) } } ================================================ FILE: src/main/kotlin/interpreter/Segment.kt ================================================ package interpreter import semantic.types.Type class Segment(val type: Type) { private val memory: MutableList = MutableList(type.count()) { IndeterminateValue } var readOnlyErrorMessage: String? = null var alive = true private set fun kill() { assert(alive) alive = false } fun checkAlive() { if (!alive) throw AssertionError("dangling pointer") } fun count(): Int { return type.count() } operator fun get(offset: Int): Value { checkAlive() return memory[offset] } operator fun set(offset: Int, newValue: Value) { checkAlive() if (readOnlyErrorMessage != null) throw AssertionError(readOnlyErrorMessage) val oldValue = memory[offset] assert(oldValue === IndeterminateValue || oldValue.type() == newValue.type()) { "$oldValue === $IndeterminateValue || ${oldValue.type()} == ${newValue.type()}" } memory[offset] = newValue } operator fun set(obj: Object, newValue: Value) { set(obj.offset, newValue) } fun replace(offset: Int, count: Int, source: Segment, sourceOffset: Int) { checkAlive() if (readOnlyErrorMessage != null) throw AssertionError(readOnlyErrorMessage) source.checkAlive() val sourceMemory = source.memory for (i in 0 until count) { memory[offset + i] = sourceMemory[sourceOffset + i] } } companion object { private var state = System.currentTimeMillis().toInt() // generates 4,294,967,296 unique addresses before repeating fun randomAddress(): Int { state = state * 214013 + 2531011 return state } } val address = randomAddress() } ================================================ FILE: src/main/kotlin/interpreter/Value.kt ================================================ package interpreter import semantic.types.* import syntax.lexer.Token data class Object(val segment: Segment, val offset: Int, val type: Type, val index: Int, val bound: Int) { init { if (index < 0) throw AssertionError("negative index $index") if (index > bound) throw AssertionError("index $index out of bounds $bound") } fun address(): Int { val hi = segment.address val lo = segment.type.sizeof(offset) return hi.shl(16) + lo } fun isSentinel(): Boolean = (index == bound) operator fun plus(delta: Int): Object = copy(offset = offset + delta * type.count(), index = index + delta) operator fun minus(delta: Int): Object = copy(offset = offset - delta * type.count(), index = index - delta) fun preventSentinelAccess() { if (isSentinel()) throw AssertionError("index $index out of bounds $bound") } fun checkReferable(): Object { segment.checkAlive() return this } fun isReferable(): Boolean { return segment.alive } fun evaluate(): Value { return when (val type = this.type.unqualified()) { is ArrayType -> { // array-to-pointer decay PointerValue(copy(type = type.elementType, index = 0, bound = type.size)) } is StructType -> { // structs are not values, they must be preserved as objects StructPseudoValue(this) } else -> { preventSentinelAccess() val result = segment[offset] if (result == IndeterminateValue) throw AssertionError("read from uninitialized variable") result } } } fun assign(newValue: Value) { preventSentinelAccess() segment[this] = newValue } } interface Value { fun type(): Type fun show(): String fun decayed(): Value = this fun store(segment: Segment, offset: Int): Int { segment[offset] = this return offset + 1 } companion object { fun signedChar(x: Char): ArithmeticValue = ArithmeticValue(x.code.toByte().toDouble(), SignedCharType) fun unsignedChar(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), UnsignedCharType) fun signedShort(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), SignedShortType) fun unsignedShort(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), UnsignedShortType) fun signedInt(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), SignedIntType) fun unsignedInt(x: Int): ArithmeticValue = ArithmeticValue(x.toLong().and(0xffffffff).toDouble(), UnsignedIntType) fun float(x: Float): ArithmeticValue = ArithmeticValue(x.toDouble(), FloatType) fun double(x: Double): ArithmeticValue = ArithmeticValue(x, DoubleType) val ONE = signedInt(1) val NUL = signedChar('\u0000') val ZERO = signedInt(0) val MINUS_ONE = signedInt(-1) fun truth(x: Boolean): ArithmeticValue = if (x) ONE else ZERO } } object VoidValue : Value { override fun type(): Type = VoidType override fun show(): String = "void" } data class ArithmeticValue(val value: Double, val type: ArithmeticType) : Value { override fun type(): Type = type override fun show(): String = type.show(value) operator fun plus(that: ArithmeticValue): ArithmeticValue = ArithmeticValue(value + that.value, type.usualArithmeticConversions(that.type)).trim() operator fun minus(that: ArithmeticValue): ArithmeticValue = ArithmeticValue(value - that.value, type.usualArithmeticConversions(that.type)).trim() operator fun times(that: ArithmeticValue): ArithmeticValue = ArithmeticValue(value * that.value, type.usualArithmeticConversions(that.type)).trim() operator fun div(that: ArithmeticValue): ArithmeticValue = ArithmeticValue(value / that.value, type.usualArithmeticConversions(that.type)).trim() operator fun rem(that: ArithmeticValue): ArithmeticValue = ArithmeticValue(value % that.value, type.usualArithmeticConversions(that.type)).trim() private fun trim(): ArithmeticValue = ArithmeticValue(type.trim(value), type) fun integralPromotions(): ArithmeticValue = type.integralPromotions().cast(this) fun isFalse(): Boolean = (value == 0.0) fun isTrue(): Boolean = (value != 0.0) fun normalizeBool(): ArithmeticValue = if (value == 0.0 || value == 1.0) this else Value.ONE } data class PointerValue(val referenced: Object) : Value { override fun type(): Type = PointerType(referenced.type) override fun show(): String = "%08x".format(referenced.address()) operator fun plus(delta: Int): PointerValue = PointerValue(referenced.checkReferable() + delta) operator fun minus(delta: Int): PointerValue = PointerValue(referenced.checkReferable() - delta) operator fun minus(base: PointerValue): Int { if (referenced.segment != base.referenced.segment) throw AssertionError("subtract across segments") return (referenced.offset - base.referenced.offset) / referenced.type.count() } infix fun equal(that: PointerValue): Boolean { return this.referenced.segment === that.referenced.segment && this.referenced.offset == that.referenced.offset } infix fun less(that: PointerValue): Boolean { if (this.referenced.segment !== that.referenced.segment) throw AssertionError("compare across segments") return this.referenced.offset < that.referenced.offset } } data class FunctionDesignator(val functionName: Token, val functionType: FunctionType) : Value { override fun type(): Type = functionType override fun show(): String = error("show on function designator") override fun decayed(): Value = FunctionPointerValue(this) } data class FunctionPointerValue(val designator: FunctionDesignator) : Value { override fun type(): Type = PointerType(designator.functionType) override fun show(): String = "&${designator.functionName}" } object IndeterminateValue : Value { override fun type(): Type = throw AssertionError("indeterminate value has no type") override fun show(): String = "" } data class StructPseudoValue(val struct: Object) : Value { override fun type(): Type = struct.type override fun show(): String = "SPV" override fun store(segment: Segment, offset: Int): Int { val count = struct.type.count() segment.replace(offset, count, struct.segment, struct.offset) return offset + count } } ================================================ FILE: src/main/kotlin/semantic/Linter.kt ================================================ package semantic import interpreter.ArithmeticValue import interpreter.BasicBlock import interpreter.ImplicitContinue import interpreter.returnType import semantic.types.FunctionType import semantic.types.VoidType import semantic.types.isString import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.tree.* class Linter(val translationUnit: TranslationUnit) : LinterBase() { init { detectLowHangingFruit() detectUnusedVariables() detectMissingReturns() detectUnreachableCode() } private fun detectLowHangingFruit() { translationUnit.walk({}) { when (it) { is Comma -> { it.left.detectOperatorWithoutEffect() } is ExpressionStatement -> { if (it.expression is FunctionCall) { if (it.expression.type !== VoidType) { it.expression.warn("ignored function result") } } else { it.expression.detectOperatorWithoutEffect() } } is IfThenElse -> { it.condition.detectSuspiciousCondition() } is While -> { it.condition.detectSuspiciousCondition() } is Do -> { it.condition.detectSuspiciousCondition() } is For -> { it.condition?.detectSuspiciousCondition() it.update?.detectOperatorWithoutEffect() } is Assert -> { it.condition.detectSuspiciousCondition() } } } } private fun Expression.detectOperatorWithoutEffect() { val root = root() when (root.kind) { EQUAL_EQUAL -> { root.warn("== is comparison, did you mean = instead?") } IDENTIFIER -> when (type) { is FunctionType -> root.warn("missing () for function call") else -> root.warn("$root has no effect") } PLUS, HYPHEN -> when (this) { is Binary -> root.warn("$root has no effect, did you mean $root= instead?") else -> root.warn("$root has no effect") } OPENING_PAREN, SIZEOF, OPENING_BRACKET, DOT, HYPHEN_MORE, AMPERSAND, ASTERISK, TILDE, BANG, SLASH, PERCENT, LESS_LESS, MORE_MORE, LESS, MORE, LESS_EQUAL, MORE_EQUAL, BANG_EQUAL, CARET, BAR, AMPERSAND_AMPERSAND, BAR_BAR, DOUBLE_CONSTANT, FLOAT_CONSTANT, INTEGER_CONSTANT, CHARACTER_CONSTANT, STRING_LITERAL -> { root.warn("$root has no effect") } else -> { } } } private fun Expression.detectSuspiciousCondition() { when (this) { is Assignment -> { warn("= is assignment, did you mean == instead?") } is RelationalEquality -> { when { left is RelationalEquality -> { val op1 = left.operator val op2 = this.operator warn("a${op1}b${op2}c does not do what you think it does. You probably want a${op1}b && b${op2}c instead.") } left.type.isString() || right.type.isString() -> { warn("a $operator b compares string addresses. To compare characters: strcmp(a, b) $operator 0") } operator.kind == LESS || operator.kind == BANG_EQUAL -> { if (right is FunctionCall && right.function is Identifier && right.function.name.text == "strlen") { warn("consider replacing SLOW i${operator.kind}strlen(s) with FAST s[i]") } } } } is Logical -> { left.detectSuspiciousCondition() right.detectSuspiciousCondition() } } val x = value if (x is ArithmeticValue && this !is Constant) { warn("condition is always ${x.isTrue()}") } } private fun detectUnusedVariables() { val unusedVariables = HashSet() translationUnit.walk({ node -> when (node) { is FunctionDefinition -> { node.parameters.forEach { unusedVariables.add(it.name) } } is Declaration -> { if (node.specifiers.storageClass != TYPEDEF) { for (namedDeclarator in node.namedDeclarators) { unusedVariables.add(namedDeclarator.name) if (namedDeclarator.declarator is Declarator.Initialized) { namedDeclarator.declarator.init.walk({}) { when (it) { is Identifier -> { unusedVariables.remove(it.symbol.name) } } } } } } } } }) { node -> when (node) { is Identifier -> { unusedVariables.remove(node.symbol.name) } } } unusedVariables.forEach { it.warn("unused variable $it") } } private fun detectMissingReturns() { translationUnit.functions.forEach { it.detectMissingReturn() } } private fun FunctionDefinition.detectMissingReturn() { if (returnType() !== VoidType) { val exit = controlFlowGraph.values.last() if (exit.isReachable && exit.isOpen()) { root().warn("function ${name()} does not return a result on all code paths") } } } private fun detectUnreachableCode() { translationUnit.functions.forEach { it.detectUnreachableCode() } } private fun FunctionDefinition.detectUnreachableCode() { controlFlowGraph.values.asSequence() .filterNot(BasicBlock::isReachable) .flatMap(BasicBlock::getStatements) .filterNot { it.root().start < 0 } .firstOrNull() ?.apply { if (this is ImplicitContinue) { root().warn("loop never repeats") } else { root().warn("unreachable code") } } } } ================================================ FILE: src/main/kotlin/semantic/LinterBase.kt ================================================ package semantic import common.Diagnostic import syntax.lexer.Token import syntax.tree.Expression abstract class LinterBase { private val warnings = ArrayList() fun getWarnings(): List = warnings.sortedBy { it.position } protected fun Token.warn(message: String) { warnings.add(Diagnostic(start, message)) } protected fun Expression.warn(message: String) { root().warn(message) } } ================================================ FILE: src/main/kotlin/semantic/SymbolTable.kt ================================================ package semantic import semantic.types.FunctionType import semantic.types.Type import syntax.lexer.Token import syntax.tree.Identifier data class Symbol(val name: Token, val type: Type, val offset: Int) { val usages = ArrayList() override fun toString(): String = name.text } class SymbolTable { private val scopes = Array>(128) { HashMap() } private var current = 0 private val closedScopes = ArrayList>() private val allSymbols = ArrayList() fun atGlobalScope(): Boolean { return current == 0 } fun openScope() { scopes[++current] = HashMap() } fun closeScope() { assert(!atGlobalScope()) { "Attempt to close the global scope" } if (current == 1) { closedScopes.clear() } else { closedScopes.add(scopes[current]) } --current } fun reopenScope() { ++current } inline fun scoped(action: () -> T): T { openScope() val result = action() closeScope() return result } inline fun rescoped(action: () -> T): T { reopenScope() val result = action() closeScope() return result } fun lookup(name: Token): Symbol? { val text = name.text for (i in current downTo 0) { scopes[i][text]?.let { symbol -> return symbol } } return null } fun lookupInClosedScopes(name: Token): Symbol? { val text = name.text for (i in closedScopes.lastIndex downTo 0) { closedScopes[i][text]?.let { symbol -> return symbol } } return null } fun declare(name: Token, type: Type, offset: Int): Symbol { return declareIn(scopes[current], name, type, offset) } fun declareOutside(name: Token, type: Type, offset: Int): Symbol { return declareIn(scopes[current - 1], name, type, offset) } private fun declareIn(scope: HashMap, name: Token, type: Type, offset: Int): Symbol { val text = name.text val previous = scope[text] if (previous != null) { if (previous.type is FunctionType && !previous.type.defined && type is FunctionType && type.defined) { if (previous.type == type) { previous.type.defined = true previous.usages.add(Identifier(name).also { it.symbol = previous }) return previous } else { name.error("function definition signature does not agree with function declaration signature") } } else { name.error("symbol $name already declared in current scope", previous.name) } } else { val symbol = Symbol(name, type, offset) scope[text] = symbol allSymbols.add(symbol) return symbol } } fun currentFunction(): Symbol? { return scopes[0].values.maxByOrNull { symbol -> symbol.name.start } } fun names(): Sequence = sequence { for (i in current downTo 0) { for ((_, symbol) in scopes[i]) { yield(symbol.name.text) } } } fun symbolAt(position: Int): Symbol? { for (symbol in allSymbols) { if (symbol.name.start <= position && position <= symbol.name.end) return symbol for (usage in symbol.usages) { if (usage.name.start <= position && position <= usage.name.end) return symbol } } return null } } ================================================ FILE: src/main/kotlin/semantic/TypeChecker.kt ================================================ package semantic import common.Diagnostic import freditor.Levenshtein import interpreter.* import semantic.types.* import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.lexer.fakeIdentifier import syntax.lexer.missingIdentifier import syntax.tree.* import text.skipDigits class TypeChecker(translationUnit: TranslationUnit) { private val functionTokens: Map = translationUnit.functions.map { it.namedDeclarator.name }.associateBy(Token::text) private val symbolTable = SymbolTable() val stringLiterals = LinkedHashSet() private var staticOffset = Int.MIN_VALUE private var currentReturnType: Type = Later private var currentStackFrameSymbols = ArrayList() init { declare(fakeIdentifier("pow"), FunctionType(DoubleType, DoubleType, DoubleType)) declare(fakeIdentifier("time"), FunctionType(UnsignedIntType, SignedIntType)) val constCharPointer = PointerType(Const(SignedCharType)) declare(fakeIdentifier("puts"), FunctionType(VoidType, constCharPointer)) declare(fakeIdentifier("putchar"), FunctionType(VoidType, SignedIntType)) declare(fakeIdentifier("getchar"), FunctionType(SignedIntType)) declare(fakeIdentifier("malloc"), FunctionType(VoidPointerType, UnsignedIntType)) declare(fakeIdentifier("free"), FunctionType(VoidType, VoidPointerType)) declare(fakeIdentifier("realloc"), FunctionType(VoidPointerType, VoidPointerType, UnsignedIntType)) declare(fakeIdentifier("memswap"), FunctionType(VoidType, VoidPointerType, VoidPointerType, UnsignedIntType)) val predicate = FunctionType(SignedIntType, ConstVoidPointerType, ConstVoidPointerType).pointer() declare( fakeIdentifier("qsort"), FunctionType(VoidType, VoidPointerType, UnsignedIntType, UnsignedIntType, predicate) ) declare( fakeIdentifier("bsearch"), FunctionType( VoidPointerType, ConstVoidPointerType, ConstVoidPointerType, UnsignedIntType, UnsignedIntType, predicate ) ) declare(fakeIdentifier("strlen"), FunctionType(UnsignedIntType, constCharPointer)) declare(fakeIdentifier("strcmp"), FunctionType(SignedIntType, constCharPointer, constCharPointer)) translationUnit.externalDeclarations.forEach { when (it) { is FunctionDefinition -> it.typeCheck() is Declaration -> it.typeCheck() } } } private var currentDeclarationIsStatic = false private fun declare(name: Token, type: Type): Symbol { return if (symbolTable.atGlobalScope()) { declareStatic(name, type) } else { declareAutomatic(name, type) } } private fun declareStatic(name: Token, type: Type): Symbol { currentDeclarationIsStatic = true val symbol = symbolTable.declare(name, type, staticOffset) staticOffset += type.count() return symbol } private fun declareAutomatic(name: Token, type: Type): Symbol { currentDeclarationIsStatic = false val symbol = symbolTable.declare(name, type, currentStackFrameSymbols.nextOffset()) if (symbol.type.requiresStorage()) { currentStackFrameSymbols.add(symbol) } return symbol } private fun ArrayList.nextOffset(): Int { if (isEmpty()) return 0 with(last()) { return offset + type.count() } } private fun declareOutside(name: Token, type: Type) { assert(type is FunctionType) symbolTable.declareOutside(name, type, staticOffset) } fun symbolAt(position: Int): Symbol? { return symbolTable.symbolAt(position) } private fun DeclarationSpecifiers.typeCheckNoStorageClass(): Type { typeCheck() if (storageClass != VOID) root().error("no storage class allowed in this context") return type } private fun DeclarationSpecifiers.typeCheck(): Type { determineType() applyQualifiers() return type } private fun DeclarationSpecifiers.determineType() { type = typeSpecifiers[typeTokens]!! if (type == Later) { type = when (typeTokens.first()) { ENUM -> list.firstNotNullOf { it.enumType() } STRUCT -> list.firstNotNullOf { it.structType() } IDENTIFIER -> { val specifier = list.find { it.kind() == IDENTIFIER } val primitive = specifier as DeclarationSpecifier.Primitive val identifier = primitive.token val symbol = symbolTable.lookup(identifier)!! val alias = symbol.type as Typedef alias.aliased } else -> root().error("no determineType for ${typeTokens.first()}") } } } private fun DeclarationSpecifiers.applyQualifiers() { if (qualifiers.contains(CONST)) { type = type.addConst() } } private fun DeclarationSpecifier.enumType(): Type? { return when (this) { is DeclarationSpecifier.EnumDef -> { if (name.wasProvided()) declare(name, TypedefSignedIntType) var counter = 0 for (enumerator in body) { with(enumerator) { if (init != null) { val type = init.typeCheck() if (type !is ArithmeticType || !type.isIntegral()) { init.root().error("enumeration constant must be an integral number") } val value = init.value ?: init.root().error("enumeration constant must be a compile-time constant") counter = (value as ArithmeticValue).value.toInt() } declare(name, EnumerationConstant(Value.signedInt(counter))) ++counter } } SignedIntType } is DeclarationSpecifier.EnumRef -> SignedIntType else -> null } } private fun DeclarationSpecifier.structType(): Type? { return when (this) { is DeclarationSpecifier.StructDef -> { val members = ArrayList() val structType = StructType(name, members) if (name.wasProvided()) declare(name, StructTag(structType)) var offset = 0 for (structDeclaration in body) { val specifierType = structDeclaration.specifiers.typeCheckNoStorageClass() for (namedDeclarator in structDeclaration.declarators) { val type = namedDeclarator.typeCheck(specifierType) validateType(namedDeclarator.name, type) members.add(Symbol(namedDeclarator.name, type, offset)) offset += type.count() } } structType.makeComplete() } is DeclarationSpecifier.StructRef -> { val temp = symbolTable.lookup(name) ?: name.error("undefined struct $name") (temp.type as StructTag).structType } else -> null } } private fun NamedDeclarator.typeCheck(specifierType: Type): Type { type = declarator.type(specifierType) return type } private fun FunctionParameter.typeCheck(): Type { with(namedDeclarator) { typeCheck(specifiers.typeCheckNoStorageClass()) // retain const on function parameters type = type.applyQualifiersTo(type.decayed()) return type } } private fun Declarator.type(from: Type): Type { return when (this) { is Declarator.Identity -> from is Declarator.Pointer -> child.type(from.pointer().let { if (qualifiers.isEmpty()) it else it.addConst() }) is Declarator.Array -> child.type(ArrayType(determineSize(), from)) is Declarator.Function -> child.type( // ignore top-level const in function types FunctionType(from.unqualified(), parameters.map { it.typeCheck().unqualified() }) ) is Declarator.Initialized -> declarator.type(from) } } private fun Declarator.Array.determineSize(): Int { if (size == null) return 0 val type = size.typeCheck() if (type !is ArithmeticType || !type.isIntegral()) size.root().error("array size must be an integral number") val value = size.value ?: size.root().error("array size must be a compile-time constant") val size = (value as ArithmeticValue).value.toInt() if (size < 1) this.size.root().error("non-positive array size $size") return size } private fun FunctionDefinition.typeCheck() { val functionType = namedDeclarator.typeCheck(specifiers.typeCheckNoStorageClass()) as FunctionType functionType.defined = true currentReturnType = functionType.returnType if (currentReturnType is StructType) { // What is the lifetime of a returned struct? namedDeclarator.name.error("cannot return structs yet") } currentStackFrameSymbols = ArrayList() symbolTable.scoped { for (parameter in parameters) { with(parameter) { if (!name.wasProvided()) name.error("missing parameter name in function definition") validateType(name, type) val symbol = declare(name, type) offset = symbol.offset } } declareOutside(namedDeclarator.name, functionType) body.typeCheck() } if (currentStackFrameSymbols.isEmpty()) { currentStackFrameSymbols.add(Symbol(missingIdentifier, SignedCharType, 0)) } stackFrameType = StructType(namedDeclarator.name, currentStackFrameSymbols) } private fun List.typeCheck() { return forEach { it.typeCheck() } } private fun Statement.typeCheck() { when (this) { is Declaration -> { typeCheck() } is Block -> { symbolTable.scoped { statements.typeCheck() } } is ExpressionStatement -> { expression.typeCheck() } is IfThenElse -> { condition.typeCheck() th3n.typeCheck() e1se?.typeCheck() } is Switch -> { control.typeCheck().unqualified().let { if (it !is ArithmeticType || !it.isIntegral()) { switch.error("switch control expression must be of integral type") } } body.typeCheck() } is Case -> { choice.typeCheck().let { if (it !is ArithmeticType || !it.isIntegral()) { case.error("case label must be an integral constant") } choice.value ?: case.error("case label must be a compile-time constant") } body.typeCheck() } is Default -> { body.typeCheck() } is While -> { condition.typeCheck() body.typeCheck() } is Do -> { body.typeCheck() condition.typeCheck() } is For -> { symbolTable.scoped { init?.typeCheck() condition?.typeCheck() update?.typeCheck() body.typeCheck() } } is LabeledStatement -> { statement.typeCheck() } is Goto -> { } is Continue -> { } is Break -> { } is Return -> { if (result == null) { if (currentReturnType !== VoidType) r3turn.error("missing return value") } else { checkAssignmentCompatibility(currentReturnType, result.root(), result.typeCheck()) } } is Assert -> { if (condition is Assignment) condition.root().error("= is assignment, did you mean == instead?") condition.typeCheck() } else -> error("no typeCheck for $this") } } private fun Declaration.typeCheck() { val specifierType = specifiers.typeCheck() for (namedDeclarator in namedDeclarators) { val name = namedDeclarator.name val type = namedDeclarator.typeCheck(specifierType) when (specifiers.storageClass) { TYPEDEF -> declare(name, Typedef(type)) STATIC -> declareStatic(name, type) else -> declare(name, type) } namedDeclarator.offset = symbolTable.lookup(namedDeclarator.name)!!.offset val declarator = namedDeclarator.declarator if (declarator !is Declarator.Initialized) { if (type is ArrayType && type.size == 0) { name.error("missing array size or initializer") } if (type !is FunctionType) validateType(name, type) } else { val init = declarator.init if (type is ArrayType && type.size == 0) { if (init is InitializerList) { type.size = init.list.size } else if (init is ExpressionInitializer && init.expression is StringLiteral) { type.size = init.expression.literal.text.length + 1 init.expression.type = type } if (currentDeclarationIsStatic) staticOffset += type.count() } typeCheck(type, init) } } } private fun typeCheck(qualified: Type, init: Initializer) { val type = qualified.unqualified() when (init) { is ExpressionInitializer -> { if (init.expression is StringLiteral && type is ArrayType && type.elementType == SignedCharType) { if (type.size <= init.expression.literal.text.length) { init.expression.root().error("string literal too long") } } else { init.expression.typeCheck() if (currentDeclarationIsStatic && init.expression.value == null && init.expression !is StringLiteral) { init.expression.root().error("static initializers must be compile-time constants") } checkAssignmentCompatibility(type, init.expression.root(), init.expression.type) } } is InitializerList -> when (type) { is ArrayType -> { val size = type.size if (size < init.list.size) init.list[size].root().error("too many initializers for $type") if (size > init.list.size) init.list.last().root().error("not enough initializers for $type") init.list.forEach { typeCheck(type.elementType, it) } } is StructType -> { val size = type.members.size if (size < init.list.size) init.list[size].root().error("too many initializers for $type") if (size > init.list.size) init.list.last().root().error("not enough initializers for $type") for ((member, initializer) in type.members.zip(init.list)) { typeCheck(member.type, initializer) } } else -> init.openBrace.error("cannot initialize $type with braces") } } } private var sizeofNesting = 0 private inline fun sizeofContext(f: () -> Unit) { ++sizeofNesting try { f() } finally { --sizeofNesting } } private inline fun T.determineValue(f: (Value) -> Value) { if (sizeofNesting == 0) { val v = operand.value if (v != null) { value = f(v) } } } private inline fun T.determineValue(f: (Value, Value) -> Value) { if (sizeofNesting == 0) { val v = left.value if (v != null) { val w = right.value if (w != null) { value = f(v, w) } } } } private fun Token.integer(): ArithmeticValue { var radix = 10 var start = 0 if (text[0] == '0' && text.length >= 2) { when (text[1]) { 'x', 'X' -> { radix = 16 start = 2 } 'b', 'B' -> { radix = 2 start = 2 } else -> { radix = 8 start = 1 } } } try { val x = text.substring(start).toLong(radix) if (x <= 0x7fffffff) return Value.signedInt(x.toInt()) if (x <= 0xffffffff) return Value.unsignedInt(x.toInt()) } catch (_: NumberFormatException) { } error("integer literal $text is too large, allowed maximum is 4294967295") } private fun Expression.typeCheck(): Type { type = when (this) { is Constant -> { val constant = constant val temp = when (constant.kind) { DOUBLE_CONSTANT -> Value.double(constant.text.toDouble()) FLOAT_CONSTANT -> Value.float(constant.text.toFloat()) INTEGER_CONSTANT -> constant.integer() CHARACTER_CONSTANT -> Value.signedInt(constant.text[0].code.toByte().toInt()) else -> error("no value for $this") } value = temp temp.type } is StringLiteral -> { isLocator = true if (sizeofNesting == 0) { stringLiterals.add(literal.text) } ArrayType(literal.text.length + 1, SignedCharType) } is Identifier -> { val symbol = symbolTable.lookup(name) if (symbol == null) { val symbol = symbolTable.lookupInClosedScopes(name) if (symbol != null) { name.error("symbol $name no longer in scope", symbol.name) } val functionToken = functionTokens[name.text] if (functionToken != null && functionToken.start > name.start) { name.error("must declare function before use", functionToken) } val bestMatches = Levenshtein.bestMatches(name.text, symbolTable.names().asIterable()) if (bestMatches.size == 1) { val bestMatch = bestMatches.first() val prefix = bestMatch.commonPrefixWith(name.text) name.error("undeclared symbol $name, did you mean $bestMatch?", prefix.length) } else { val commaSeparated = bestMatches.joinToString(", ") name.error("undeclared symbol $name, best matches: $commaSeparated") } } this.symbol = symbol symbol.usages.add(this) if (symbol.type is EnumerationConstant) { value = symbol.type.value SignedIntType } else { isLocator = (symbol.type !is FunctionType) symbol.type } } is PrintfCall -> { checkPrintfFormatString() SignedIntType } is ScanfCall -> { arguments.forEach { val type = it.typeCheck().decayed() if (type !is PointerType) it.root().error("missing &") if (type.referencedType is ArrayType && type.referencedType.elementType === SignedCharType) { it.root().error("redundant &") } } SignedIntType } is Postfix -> { val operandType = operand.typeCheck() if (operandType.isConst()) operator.error("const", "$operator") if (!operand.isLocator) operator.error("value", "$operator") if ((operandType is ArithmeticType) || (operandType is PointerType)) { operandType } else { operator.error("$operandType", "$operator") } } is Subscript -> { isLocator = true val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is PointerType) && (rightType is ArithmeticType)) { leftType.referencedType } else if ((leftType is ArithmeticType) && (rightType is PointerType)) { rightType.referencedType } else { operator.error("$leftType", "[$rightType]") } } is FunctionCall -> { val functionPointerType = function.typeCheck().decayed() if (functionPointerType !is PointerType) function.root().error("$functionPointerType is not a function") val functionType = functionPointerType.referencedType if (functionType !is FunctionType) function.root().error("$functionType is not a function") val parameterTypes = functionType.parameters val nParameters = parameterTypes.size val nArguments = arguments.size if (nParameters != nArguments) function.root() .error("function takes $nParameters arguments, not $nArguments") for ((parameterType, argument) in parameterTypes.zip(arguments)) { checkAssignmentCompatibility(parameterType, argument.root(), argument.typeCheck()) } functionType.returnType } is DirectMemberAccess -> { isLocator = true val leftType = left.typeCheck() val structType = leftType.unqualified() if (structType is StructType) { val member = structType.member(right) ?: right.error("$right is not a member of $structType") leftType.applyQualifiersTo(member.type) } else if (leftType is PointerType && leftType.referencedType.unqualified() is StructType) { dot.error("replace . with -> for indirect member access") } else { left.root().error("$structType is not a struct") } } is IndirectMemberAccess -> { isLocator = true val leftPointerType = left.typeCheck().decayed() if (leftPointerType is StructType) arrow.error("replace -> with . for direct member access") if (leftPointerType !is PointerType) left.root().error("$leftPointerType is not a struct pointer") val leftType = leftPointerType.referencedType val structType = leftType.unqualified() if (structType is StructType) { val member = structType.member(right) ?: right.error("$right is not a member of $structType") leftType.applyQualifiersTo(member.type) } else { left.root().error("$structType is not a struct") } } is Prefix -> { val operandType = operand.typeCheck() if (operandType.isConst()) operator.error("${operator}const") if (!operand.isLocator) operator.error("${operator}value") if ((operandType is ArithmeticType) || (operandType is PointerType)) { operandType } else { operator.error("${operator}$operandType") } } is Reference -> { val operandType = operand.typeCheck() if (operandType !is FunctionType) { if (!operand.isLocator) operator.error("${operator}value") } else if (operand is Identifier) { value = FunctionDesignator(operand.name, operandType).decayed() } PointerType(operandType) } is Dereference -> { val operandType = operand.typeCheck().decayed() if (operandType !is PointerType) operator.error("${operator}$operandType") isLocator = (operandType.referencedType !is FunctionType) operandType.referencedType } is UnaryPlus -> { val operandType = operand.typeCheck().decayed() if (operandType !is ArithmeticType) operator.error("${operator}$operandType") this.determineValue(::unaryPlus) SignedIntType.max(operandType) } is UnaryMinus -> { val operandType = operand.typeCheck().decayed() if (operandType !is ArithmeticType) operator.error("${operator}$operandType") this.determineValue(::unaryMinus) SignedIntType.max(operandType) } is BitwiseNot -> { val operandType = operand.typeCheck().decayed() if (operandType !is ArithmeticType || !operandType.isIntegral()) operator.error("${operator}$operandType") this.determineValue(::bitwiseNot) SignedIntType.max(operandType) } is LogicalNot -> { val operandType = operand.typeCheck().decayed() if (operandType !is ArithmeticType) operator.error("${operator}$operandType") this.determineValue(::logicalNot) SignedIntType } is SizeofType -> { operandType = declarator.type(specifiers.typeCheckNoStorageClass()) val size = operandType.sizeof() if (size == 0) operator.error("sizeof requires object type") value = Value.unsignedInt(size) UnsignedIntType } is SizeofExpression -> { sizeofContext { operand.typeCheck() } val size = operand.type.sizeof() if (size == 0) operator.error("sizeof requires object type") value = Value.unsignedInt(size) UnsignedIntType } is Multiplicative -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { this.determineValue { a, b -> multiplicative(a, operator, b) } leftType.usualArithmeticConversions(rightType) } else { operator.error("$leftType ", "$operator $rightType") } } is Plus -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ComparablePointerType) && (rightType is ArithmeticType)) { leftType } else if ((leftType is ArithmeticType) && (rightType is ComparablePointerType)) { rightType } else if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { this.determineValue(::plus) leftType.usualArithmeticConversions(rightType) } else { operator.error("$leftType ", "$operator $rightType") } } is Minus -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ComparablePointerType) && (rightType is ArithmeticType)) { leftType } else if ((leftType is ComparablePointerType) && (rightType is ComparablePointerType)) { SignedIntType } else if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { this.determineValue(::minus) leftType.usualArithmeticConversions(rightType) } else { operator.error("$leftType ", "$operator $rightType") } } is Shift -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ArithmeticType && leftType.isIntegral()) && (rightType is ArithmeticType && rightType.isIntegral())) { val typ = leftType.integralPromotions() this.determineValue { a, b -> shift(a, operator, b, typ) } typ } else { operator.error("$leftType ", "$operator $rightType") } } is RelationalEquality -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ComparablePointerType) && (rightType is ComparablePointerType)) { SignedIntType } else if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { this.determineValue { a, b -> relationalEquality(a as ArithmeticValue, operator, b as ArithmeticValue) } SignedIntType } else { operator.error("$leftType ", "$operator $rightType") } } is Bitwise -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ArithmeticType && leftType.isIntegral()) && (rightType is ArithmeticType && rightType.isIntegral())) { val typ = leftType.usualArithmeticConversions(rightType) this.determineValue { a, b -> bitwise(a, operator, b, typ) } typ } else { operator.error("$leftType ", "$operator $rightType") } } is Logical -> { val leftType = left.typeCheck().decayed() val rightType = right.typeCheck().decayed() if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { this.determineValue { a, b -> when (operator.kind) { AMPERSAND_AMPERSAND -> { if ((a as ArithmeticValue).isFalse()) Value.ZERO else (b as ArithmeticValue).normalizeBool() } BAR_BAR -> { if ((a as ArithmeticValue).isTrue()) Value.ONE else (b as ArithmeticValue).normalizeBool() } else -> error("no evaluate for $operator") } } SignedIntType } else { operator.error("$leftType ", "$operator $rightType") } } is Conditional -> { condition.typeCheck().decayed() val a = th3n.typeCheck().decayed() val b = e1se.typeCheck().decayed() if (a is ArithmeticType && b is ArithmeticType) { a.usualArithmeticConversions(b) } else if (a is VoidType && b is VoidType) { VoidType } else if (a is PointerType && b is PointerType) { if (a.referencedType == b.referencedType) { a } else { colon.error("$a ", ": $b") } } else if (a is ComparablePointerType && b is ComparablePointerType) { // one or more void pointers VoidPointerType } else { colon.error("$a ", ": $b") } } is Cast -> { val targetType = declarator.type(specifiers.typeCheckNoStorageClass()).unqualified() val sourceType = operand.typeCheck() checkAssignmentCompatibility(targetType, operator, sourceType) this.determineValue { targetType.cast(it) } targetType } is Assignment -> { val leftType = left.typeCheck() if (leftType.isConst()) operator.error("const ", "$operator ") if (!left.isLocator) operator.error("value ", "$operator ") val rightType = right.typeCheck() checkAssignmentCompatibility(leftType, operator, rightType) leftType } is PlusAssignment -> { typeCheckPlusMinusAssignment() } is MinusAssignment -> { typeCheckPlusMinusAssignment() } is Comma -> { left.typeCheck() right.typeCheck().decayed() } else -> error("no typeCheck for $this") } return type } private fun Binary.typeCheckPlusMinusAssignment(): Type { val leftType = left.typeCheck() if (leftType.isConst()) operator.error("const ", "$operator ") if (!left.isLocator) operator.error("value ", "$operator ") val rightType = right.typeCheck().decayed() if ((leftType is ArithmeticType) && (rightType is ArithmeticType)) { return leftType } else if ((leftType is ComparablePointerType) && (rightType is ArithmeticType)) { return leftType } else { operator.error("$leftType ", "$operator $rightType") } } private fun PrintfCall.checkPrintfFormatString() { try { val args = arguments.iterator() val fmt = format.text var k = fmt.indexOf('%') while (k != -1) { if (fmt[++k] != '%') { k = fmt.skipDigits(k) // width if (fmt[k] == '.') { val dot = k k = fmt.skipDigits(k + 1) // precision if (fmt[k] !in "eEfgGs") format.stringErrorAt(dot, ". only works inside %e %E %f %g %G %s") } if (fmt[k] !in "ciudoxXeEfgGspn") format.stringErrorAt(k, "illegal conversion specifier") if (!args.hasNext()) format.stringErrorAt(k, "missing argument after format string") val arg = args.next() checkPrintfConversionSpecifier(fmt[k], arg.typeCheck().decayed(), arg.root()) } k = fmt.indexOf('%', k + 1) } if (args.hasNext()) args.next().root().error("missing conversion specifier in format string") } catch (ex: StringIndexOutOfBoundsException) { throw Diagnostic(format.end - 1, "incomplete conversion specifier") } } private fun checkPrintfConversionSpecifier(specifier: Char, type: Type, where: Token) { when (specifier) { 'c', 'i', 'u', 'd', 'o', 'x', 'X' -> if (type !is ArithmeticType || !type.isIntegral()) { where.error("%$specifier expects integral type, not $type") } 'e', 'E', 'f', 'g', 'G' -> if (type !is ArithmeticType || type.isIntegral()) { where.error("%$specifier expects floating type, not $type") } 's' -> if (type !is PointerType || type.referencedType.unqualified() !== SignedCharType) { where.error("%$specifier expects string, not $type") } 'p' -> if (type !is ComparablePointerType) { where.error("%$specifier expects pointer, not $type") } 'n' -> where.error("%$specifier not implemented yet") else -> where.error("illegal conversion specifier %$specifier") } } private fun checkAssignmentCompatibility(left: Type, operator: Token, right: Type) { if (!left.canCastFrom(right)) { operator.error("$right\n cannot be converted to \n$left") } } private fun validateType(name: Token, type: Type) { if (!type.isComplete()) name.error("incomplete type $type") when (type) { is ArrayType -> validateType(name, type.elementType) is PointerType -> { val t = type.referencedType if (t !is StructType && t !is FunctionType) validateType(name, t) } } } } ================================================ FILE: src/main/kotlin/semantic/TypeSpecifiers.kt ================================================ package semantic import semantic.types.* import syntax.lexer.TokenKind.* import syntax.lexer.TokenKindSet val enumStructUnion = TokenKindSet.of(ENUM, STRUCT, UNION) val storageClasses = TokenKindSet.of(TYPEDEF, EXTERN, STATIC, AUTO, REGISTER) val typeSpecifierIdentifier = TokenKindSet.of(IDENTIFIER) val typeSpecifiers = mapOf( TokenKindSet.of(VOID) to VoidType, TokenKindSet.of(CHAR) to SignedCharType, TokenKindSet.of(SIGNED, CHAR) to SignedCharType, TokenKindSet.of(UNSIGNED, CHAR) to UnsignedCharType, TokenKindSet.of(SHORT) to SignedShortType, TokenKindSet.of(SHORT, INT) to SignedShortType, TokenKindSet.of(SIGNED, SHORT) to SignedShortType, TokenKindSet.of(SIGNED, SHORT, INT) to SignedShortType, TokenKindSet.of(UNSIGNED, SHORT) to UnsignedShortType, TokenKindSet.of(UNSIGNED, SHORT, INT) to UnsignedShortType, TokenKindSet.of(INT) to SignedIntType, TokenKindSet.of(SIGNED) to SignedIntType, TokenKindSet.of(SIGNED, INT) to SignedIntType, TokenKindSet.of(UNSIGNED) to UnsignedIntType, TokenKindSet.of(UNSIGNED, INT) to UnsignedIntType, TokenKindSet.of(LONG) to SignedIntType, TokenKindSet.of(LONG, INT) to SignedIntType, TokenKindSet.of(SIGNED, LONG) to SignedIntType, TokenKindSet.of(SIGNED, LONG, INT) to SignedIntType, TokenKindSet.of(UNSIGNED, LONG) to UnsignedIntType, TokenKindSet.of(UNSIGNED, LONG, INT) to UnsignedIntType, TokenKindSet.of(FLOAT) to FloatType, TokenKindSet.of(DOUBLE) to DoubleType, TokenKindSet.of(ENUM) to Later, TokenKindSet.of(STRUCT) to Later, typeSpecifierIdentifier to Later, ) fun main() { var spread = 0x1_0000_0001 while (!perfect(spread)) { spread += 2 } println("return (bits * 0x%x).ushr(32).toInt()".format(spread)) } private fun perfect(spread: Long): Boolean { var set = 0 for (key in typeSpecifiers.keys) { val hash = (key.bits * spread).ushr(32).toInt() val index = hash xor (hash ushr 16) and 31 val mask = 1 shl index if (set and mask != 0) return false set = set or mask } return true } ================================================ FILE: src/main/kotlin/semantic/types/Arithmetic.kt ================================================ package semantic.types import interpreter.ArithmeticValue import interpreter.Value import text.quote abstract class ArithmeticType : Type { abstract fun show(value: Double): String abstract val defaultValue: ArithmeticValue abstract fun rank(): Int open fun isIntegral(): Boolean = true abstract fun trim(x: Double): Double override fun canCastFromDecayed(source: Type): Boolean = source is ArithmeticType override fun cast(source: Value): Value = cast(source as ArithmeticValue) fun cast(source: ArithmeticValue): ArithmeticValue = if (source.type === this) source else ArithmeticValue(trim(source.value), this) fun integralPromotions(): ArithmeticType = this.max(SignedIntType) fun usualArithmeticConversions(that: ArithmeticType): ArithmeticType = integralPromotions().max(that) fun max(that: ArithmeticType): ArithmeticType = if (this.rank() < that.rank()) that else this override fun declaration(parent: String): String = "$this$parent" } object SignedCharType : ArithmeticType() { override fun sizeof(): Int = 1 override fun show(value: Double): String = value.toInt().and(0xff).toChar().quote() override val defaultValue: ArithmeticValue = Value.NUL override fun rank(): Int = 0 override fun trim(x: Double): Double { if (x < -128.0) throw ArithmeticException("char underflow $x") if (x > +127.0) throw ArithmeticException("char overflow $x") return x.toInt().toByte().toDouble() } override fun toString(): String = "char" } object UnsignedCharType : ArithmeticType() { override fun sizeof(): Int = 1 override fun show(value: Double): String = value.toInt().toString() override val defaultValue: ArithmeticValue = Value.unsignedChar(0) override fun rank(): Int = 1 override fun trim(x: Double): Double = x.toInt().and(0xff).toDouble() override fun toString(): String = "unsigned char" } object SignedShortType : ArithmeticType() { override fun sizeof(): Int = 2 override fun show(value: Double): String = value.toInt().toString() override val defaultValue: ArithmeticValue = Value.signedShort(0) override fun rank(): Int = 2 override fun trim(x: Double): Double { if (x < -32768.0) throw ArithmeticException("short underflow $x") if (x > +32767.0) throw ArithmeticException("short overflow $x") return x.toInt().toShort().toDouble() } override fun toString(): String = "short" } object UnsignedShortType : ArithmeticType() { override fun sizeof(): Int = 2 override fun show(value: Double): String = value.toInt().toString() override val defaultValue: ArithmeticValue = Value.unsignedShort(0) override fun rank(): Int = 3 override fun trim(x: Double): Double = x.toInt().and(0xffff).toDouble() override fun toString(): String = "unsigned short" } object SignedIntType : ArithmeticType() { override fun sizeof(): Int = 4 override fun show(value: Double): String = value.toInt().toString() override val defaultValue: ArithmeticValue by lazy { Value.signedInt(0) } override fun rank(): Int = 4 override fun trim(x: Double): Double { if (x < -2147483648.0) throw ArithmeticException("int underflow $x") if (x > +2147483647.0) throw ArithmeticException("int overflow $x") return x.toInt().toDouble() } override fun toString(): String = "int" } object UnsignedIntType : ArithmeticType() { override fun sizeof(): Int = 4 override fun show(value: Double): String = value.toLong().toString() override val defaultValue: ArithmeticValue by lazy { Value.unsignedInt(0) } override fun rank(): Int = 5 override fun trim(x: Double): Double = x.toLong().and(0xffffffff).toDouble() override fun toString(): String = "unsigned int" } object FloatType : ArithmeticType() { override fun sizeof(): Int = 4 override fun show(value: Double): String = value.toFloat().toString() override val defaultValue: ArithmeticValue = Value.float(0f) override fun rank(): Int = 8 override fun isIntegral(): Boolean = false override fun trim(x: Double): Double = x.toFloat().toDouble() override fun toString(): String = "float" } object DoubleType : ArithmeticType() { override fun sizeof(): Int = 8 override fun show(value: Double): String = value.toString() override val defaultValue: ArithmeticValue = Value.double(0.0) override fun rank(): Int = 9 override fun isIntegral(): Boolean = false override fun trim(x: Double): Double = x override fun toString(): String = "double" } ================================================ FILE: src/main/kotlin/semantic/types/Array.kt ================================================ package semantic.types data class ArrayType(var size: Int, val elementType: Type) : Type { override fun sizeof(): Int = size * elementType.sizeof() override fun sizeof(offset: Int): Int { if (offset >= count()) return sizeof() val n = elementType.count() return offset / n * elementType.sizeof() + elementType.sizeof(offset % n) } override fun decayed(): Type = PointerType(elementType) override fun count(): Int = size * elementType.count() override fun isConst(): Boolean = elementType.isConst() override fun addConst(): Type = if (isConst()) this else ArrayType(size, elementType.addConst()) override fun unqualified(): Type = if (isConst()) ArrayType(size, elementType.unqualified()) else this fun dimensions(): Int = if (elementType is ArrayType) elementType.dimensions() + 1 else 1 override fun toString(): String = declaration("") override fun declaration(parent: String): String { return if (parent.isPointer()) { elementType.declaration("($parent)[$size]") } else { elementType.declaration("$parent[$size]") } } } fun Type.isString(): Boolean = this is ArrayType && elementType.unqualified() == SignedCharType ================================================ FILE: src/main/kotlin/semantic/types/Enum.kt ================================================ package semantic.types import interpreter.ArithmeticValue class EnumerationConstant(val value: ArithmeticValue) : Type { override fun requiresStorage(): Boolean = false override fun count(): Int = 0 } ================================================ FILE: src/main/kotlin/semantic/types/Function.kt ================================================ package semantic.types data class FunctionType(val returnType: Type, val parameters: List) : Type { companion object { operator fun invoke(returnType: Type, vararg parameters: Type) = FunctionType(returnType, parameters.toList()) fun declarationMarker(): FunctionType = FunctionType(VoidType) val DEFINITION_MARKER: FunctionType = declarationMarker().apply { defined = true } } var defined: Boolean = false override fun requiresStorage(): Boolean = false override fun count(): Int = 0 override fun decayed(): Type = pointer() override fun toString(): String = declaration("") override fun declaration(parent: String): String { val params = parameters.joinToString(transform = Type::toString, prefix = "(", separator = ",", postfix = ")") return if (parent.isPointer()) { returnType.declaration("($parent)$params") } else { returnType.declaration("$parent$params") } } } ================================================ FILE: src/main/kotlin/semantic/types/Pointer.kt ================================================ package semantic.types import interpreter.Value interface ComparablePointerType : Type data class PointerType(val referencedType: Type) : ComparablePointerType { override fun sizeof(): Int = 4 override fun canCastFromDecayed(source: Type): Boolean { if (source === VoidPointerType) return true if (source === ConstVoidPointerType) return referencedType.isConst() return canCastFromPointer(source) } private fun canCastFromPointer(source: Type): Boolean { if (source !is PointerType) return false val sourceReferenced = source.referencedType return referencedType == sourceReferenced || referencedType.unqualified() == sourceReferenced } override fun cast(source: Value): Value { if (!canCastFromPointer(source.type().decayed())) throw AssertionError("${source.type()}\n cannot be converted to \n$this") return source.decayed() } override fun toString(): String = declaration("") override fun declaration(parent: String): String { return referencedType.declaration("*$parent") } } object VoidPointerType : ComparablePointerType { override fun sizeof(): Int = 4 override fun canCastFromDecayed(source: Type): Boolean { if (source === this) return true return source is PointerType && !source.referencedType.isConst() } override fun toString(): String = "void*" override fun declaration(parent: String): String = "void*$parent" } object ConstVoidPointerType : ComparablePointerType { override fun sizeof(): Int = 4 override fun canCastFromDecayed(source: Type): Boolean { if (source === this || source === VoidPointerType) return true return source is PointerType } override fun toString(): String = "const void*" override fun declaration(parent: String): String = "const void*$parent" } ================================================ FILE: src/main/kotlin/semantic/types/Struct.kt ================================================ package semantic.types import semantic.Symbol import syntax.lexer.Token import syntax.lexer.missingIdentifier abstract class CompletableType : Type { private var complete = false override fun isComplete(): Boolean = complete fun makeComplete(): Type { assert(!complete) complete = true return this } } class StructType(val name: Token, val members: List) : CompletableType() { override fun sizeof(): Int = members.sumOf { it.type.sizeof() } override fun sizeof(offset: Int): Int { var size = 0 var off = offset for (member in members) { size += member.type.sizeof(off) off -= member.type.count() if (off <= 0) break } return size } override fun count(): Int = members.sumOf { it.type.count() } override fun canCastFromDecayed(source: Type): Boolean = (this === source) fun member(name: Token): Symbol? = members.find { it.name.text === name.text } override fun toString(): String = "struct $name" override fun declaration(parent: String): String = "struct $name$parent" } val StructTypeLater = StructType(missingIdentifier, emptyList()) class StructTag(val structType: StructType) : Type { override fun requiresStorage(): Boolean = false override fun count(): Int = 0 } ================================================ FILE: src/main/kotlin/semantic/types/Type.kt ================================================ package semantic.types import interpreter.Value interface Type { fun requiresStorage(): Boolean = true fun isComplete(): Boolean = sizeof() > 0 fun sizeof(): Int = 0 fun sizeof(offset: Int): Int = if (offset == 0) 0 else sizeof() fun decayed(): Type = this fun pointer(): Type = PointerType(this) fun count(): Int = 1 fun canCastFrom(source: Type): Boolean = canCastFromDecayed(source.decayed()) fun canCastFromDecayed(source: Type): Boolean = false fun cast(source: Value): Value = source fun isConst(): Boolean = false fun addConst(): Type = Const(this) fun unqualified(): Type = this fun applyQualifiersTo(target: Type): Type = target fun declaration(parent: String): String { throw AssertionError("$javaClass.declaration($parent)") } fun String.isPointer(): Boolean = this.isNotEmpty() && this.first() == '*' } data class Const(val underlying: Type) : Type by underlying { override fun pointer(): Type = PointerType(this) override fun isConst(): Boolean = true override fun addConst(): Type = this override fun unqualified(): Type = underlying override fun applyQualifiersTo(target: Type): Type = target.addConst() override fun toString(): String = declaration("") override fun declaration(parent: String): String { return if (underlying is ComparablePointerType) { underlying.declaration("const$parent") } else { "const $underlying$parent" } } } object Later : Type ================================================ FILE: src/main/kotlin/semantic/types/Typedef.kt ================================================ package semantic.types class Typedef(val aliased: Type) : Type { override fun requiresStorage(): Boolean = false override fun count(): Int = 0 } val TypedefSignedIntType = Typedef(SignedIntType) object MarkerIsTypedefName : Type object MarkerNotTypedefName : Type ================================================ FILE: src/main/kotlin/semantic/types/Void.kt ================================================ package semantic.types object VoidType : Type { override fun pointer(): Type = VoidPointerType override fun count(): Int = 0 override fun addConst(): Type = ConstVoidType override fun toString(): String = "void" override fun declaration(parent: String): String = "void$parent" } object ConstVoidType : Type { override fun pointer(): Type = ConstVoidPointerType override fun count(): Int = 0 override fun isConst(): Boolean = true override fun addConst(): Type = this override fun unqualified(): Type = VoidType override fun toString(): String = "const void" override fun declaration(parent: String): String = "const void$parent" } ================================================ FILE: src/main/kotlin/syntax/lexer/Characters.kt ================================================ package syntax.lexer import syntax.lexer.TokenKind.CHARACTER_CONSTANT import syntax.lexer.TokenKind.STRING_LITERAL fun Lexer.characterConstant(): Token { val executionChar = when (next()) { '\\' -> escapeSequence() in '\u0020'..'\u007e' -> current in '\u00a0'..'\u00ff' -> current else -> error("illegal character inside character constant") } if (next() != '\'') error("character constant must be closed by '") next() return token(CHARACTER_CONSTANT, lexeme(), executionChar.toString()) } fun Lexer.escapeSequence(): Char = when (next()) { '\'', '\"', '?', '\\' -> current 'a' -> '\u0007' 'b' -> '\u0008' 't' -> '\u0009' 'n' -> '\u000a' 'v' -> '\u000b' 'f' -> '\u000c' 'r' -> '\u000d' '0' -> '\u0000' else -> error("illegal escape character") } fun Lexer.stringLiteral(): Token { val sb = StringBuilder() while (true) { val executionChar = when (next()) { '\\' -> escapeSequence() '\"' -> { next() return token(STRING_LITERAL, lexeme(), sb.toString().intern()) } in '\u0020'..'\u007e' -> current in '\u00a0'..'\u00ff' -> current else -> error("illegal character inside string literal") } sb.append(executionChar) } } ================================================ FILE: src/main/kotlin/syntax/lexer/Identifiers.kt ================================================ package syntax.lexer import syntax.lexer.TokenKind.IDENTIFIER tailrec fun Lexer.identifierOrKeyword(): Token = when (next()) { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> identifierOrKeyword() else -> { val lexeme = lexeme() when (val value: Any? = identifiersOrKeywords[lexeme]) { is TokenKind -> verbatim(value) is String -> token(IDENTIFIER, value) else -> { identifiersOrKeywords[lexeme] = lexeme token(IDENTIFIER, lexeme) } } } } ================================================ FILE: src/main/kotlin/syntax/lexer/Lexer.kt ================================================ package syntax.lexer import common.Diagnostic const val EOF = '\u0000' class Lexer(private val input: String) { var start: Int = -1 private set var index: Int = -1 private set fun startAtIndex() { start = index } var current: Char = next() private set fun next(): Char { current = if (++index < input.length) input[index] else EOF return current } fun previous(): Char { current = input[--index] return current } fun lexeme(): String { return input.substring(start, index) } fun token(kind: TokenKind): Token { return token(kind, lexeme()) } fun token(kind: TokenKind, text: String): Token { return token(kind, text, text) } fun token(kind: TokenKind, source: String, text: String): Token { return Token(kind, start, source, text) } fun verbatim(kind: TokenKind): Token { return token(kind, kind.lexeme) } fun nextVerbatim(kind: TokenKind): Token { next() return verbatim(kind) } fun error(message: String): Nothing { throw Diagnostic(index, message) } val identifiersOrKeywords = HashMap(keywords) } ================================================ FILE: src/main/kotlin/syntax/lexer/NextToken.kt ================================================ package syntax.lexer import syntax.lexer.TokenKind.* tailrec fun Lexer.nextToken(): Token { startAtIndex() return when (current) { ' ', '\u0009', '\u000a', '\u000b', '\u000c', '\u000d' -> { next() nextToken() } 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_' -> identifierOrKeyword() '1', '2', '3', '4', '5', '6', '7', '8', '9' -> constant() '0' -> zero() '\'' -> characterConstant() '\"' -> stringLiteral() '(' -> nextVerbatim(OPENING_PAREN) ')' -> nextVerbatim(CLOSING_PAREN) ',' -> nextVerbatim(COMMA) '.' -> nextVerbatim(DOT) ':' -> nextVerbatim(COLON) ';' -> nextVerbatim(SEMICOLON) '?' -> nextVerbatim(QUESTION) '[' -> nextVerbatim(OPENING_BRACKET) ']' -> nextVerbatim(CLOSING_BRACKET) '{' -> nextVerbatim(OPENING_BRACE) '}' -> nextVerbatim(CLOSING_BRACE) '~' -> nextVerbatim(TILDE) '!' -> when (next()) { '=' -> nextVerbatim(BANG_EQUAL) else -> verbatim(BANG) } '%' -> when (next()) { '=' -> nextVerbatim(PERCENT_EQUAL) else -> verbatim(PERCENT) } '&' -> when (next()) { '=' -> nextVerbatim(AMPERSAND_EQUAL) '&' -> nextVerbatim(AMPERSAND_AMPERSAND) else -> verbatim(AMPERSAND) } '*' -> when (next()) { '=' -> nextVerbatim(ASTERISK_EQUAL) else -> verbatim(ASTERISK) } '+' -> when (next()) { '=' -> nextVerbatim(PLUS_EQUAL) '+' -> nextVerbatim(PLUS_PLUS) else -> verbatim(PLUS) } '-' -> when (next()) { '=' -> nextVerbatim(HYPHEN_EQUAL) '-' -> nextVerbatim(HYPHEN_HYPHEN) '>' -> nextVerbatim(HYPHEN_MORE) else -> verbatim(HYPHEN) } '/' -> when (next()) { '/' -> { skipSingleLineComment() nextToken() } '*' -> { skipMultiLineComment() nextToken() } '=' -> nextVerbatim(SLASH_EQUAL) else -> verbatim(SLASH) } '<' -> when (next()) { '=' -> nextVerbatim(LESS_EQUAL) '<' -> when (next()) { '=' -> nextVerbatim(LESS_LESS_EQUAL) else -> verbatim(LESS_LESS) } else -> verbatim(LESS) } '=' -> when (next()) { '=' -> nextVerbatim(EQUAL_EQUAL) else -> verbatim(EQUAL) } '>' -> when (next()) { '=' -> nextVerbatim(MORE_EQUAL) '>' -> when (next()) { '=' -> nextVerbatim(MORE_MORE_EQUAL) else -> verbatim(MORE_MORE) } else -> verbatim(MORE) } '^' -> when (next()) { '=' -> nextVerbatim(CARET_EQUAL) else -> verbatim(CARET) } '|' -> when (next()) { '=' -> nextVerbatim(BAR_EQUAL) '|' -> nextVerbatim(BAR_BAR) else -> verbatim(BAR) } EOF -> verbatim(END_OF_INPUT) else -> error("illegal input character") } } ================================================ FILE: src/main/kotlin/syntax/lexer/Numbers.kt ================================================ package syntax.lexer import syntax.lexer.TokenKind.* fun Lexer.constant(): Token { var seenDecimalPoint = false while (true) { when (next()) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> { } 'f', 'F' -> { next() return token(FLOAT_CONSTANT) } '.' -> { if (seenDecimalPoint) return token(DOUBLE_CONSTANT) seenDecimalPoint = true } else -> return token(if (seenDecimalPoint) DOUBLE_CONSTANT else INTEGER_CONSTANT) } } } fun Lexer.zero(): Token { next() if (current == 'x' || current == 'X') return hexadecimal() if (current == 'b' || current == 'B') return binary() previous() var seen8or9 = false var seenDecimalPoint = false while (true) { when (next()) { '0', '1', '2', '3', '4', '5', '6', '7' -> { } '8', '9' -> { seen8or9 = true } 'f', 'F' -> { next() return token(FLOAT_CONSTANT) } '.' -> { if (seenDecimalPoint) return token(DOUBLE_CONSTANT) seenDecimalPoint = true } else -> { if (seenDecimalPoint) return token(DOUBLE_CONSTANT) if (!seen8or9) return token(INTEGER_CONSTANT) error("octal literal indicated by leading digit 0 cannot contain digit 8 or 9") } } } } fun Lexer.hexadecimal(): Token { while (true) { when (next()) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' -> { } else -> { if (index - start > 2) return token(INTEGER_CONSTANT) error("hexadecimal literal indicated by leading ${lexeme()} must contain at least one digit") } } } } fun Lexer.binary(): Token { while (true) { when (next()) { '0', '1' -> { } else -> { if (index - start > 2) return token(INTEGER_CONSTANT) error("binary literal indicated by leading ${lexeme()} must contain at least one digit") } } } } ================================================ FILE: src/main/kotlin/syntax/lexer/SkipComments.kt ================================================ package syntax.lexer fun Lexer.skipSingleLineComment() { while (next() != '\n') { if (current == EOF) return } next() // skip '\n' } fun Lexer.skipMultiLineComment() { next() // skip '*' do { if (current == EOF) return } while ((current != '*') or (next() != '/')) next() // skip '/' } ================================================ FILE: src/main/kotlin/syntax/lexer/Token.kt ================================================ package syntax.lexer import common.Diagnostic import syntax.lexer.TokenKind.IDENTIFIER import syntax.lexer.TokenKind.STRING_LITERAL class Token(val kind: TokenKind, val start: Int, val source: String, val text: String) { val end: Int get() = start + source.length fun withTokenKind(replacement: TokenKind): Token = Token(replacement, start, source, text) fun tagged(): Token = Token(kind, start, source, "#$text".intern()) fun wasProvided(): Boolean = kind == IDENTIFIER fun error(message: String): Nothing { throw Diagnostic(start, message) } fun error(message: String, columnDelta: Int): Nothing { throw Diagnostic(start, message, columnDelta = columnDelta) } fun error(before: String, after: String): Nothing { throw Diagnostic(start, before + after, columnDelta = -before.length) } fun error(message: String, previous: Token): Nothing { throw Diagnostic(start, message, previous.start) } // 0123 456 index // "ABC\nXYZ" // 0123456789 origin fun stringErrorAt(index: Int, message: String): Nothing { assert(kind == STRING_LITERAL) var origin = 1 repeat(index) { if (source[origin] == '\\') ++origin ++origin } throw Diagnostic(start + origin, message) } override fun toString(): String = source } fun fakeIdentifier(name: String) = Token(IDENTIFIER, Int.MIN_VALUE, name, name) val missingIdentifier = fakeIdentifier("") val hiddenIdentifier = fakeIdentifier("_") ================================================ FILE: src/main/kotlin/syntax/lexer/TokenKind.kt ================================================ package syntax.lexer enum class TokenKind(val lexeme: String) { ASSERT("assert"), AUTO("auto"), BREAK("break"), CASE("case"), CHAR("char"), CONST("const"), CONTINUE("continue"), DEFAULT("default"), DO("do"), DOUBLE("double"), ELSE("else"), ENUM("enum"), EXTERN("extern"), FLOAT("float"), FOR("for"), GOTO("goto"), IF("if"), INT("int"), LONG("long"), REGISTER("register"), RETURN("return"), SHORT("short"), SIGNED("signed"), SIZEOF("sizeof"), STATIC("static"), STRUCT("struct"), SWITCH("switch"), TYPEDEF("typedef"), UNION("union"), UNSIGNED("unsigned"), VOID("void"), VOLATILE("volatile"), WHILE("while"), FLOAT_CONSTANT("FLOAT CONSTANT"), DOUBLE_CONSTANT("DOUBLE CONSTANT"), INTEGER_CONSTANT("INTEGER CONSTANT"), CHARACTER_CONSTANT("CHARACTER CONSTANT"), STRING_LITERAL("STRING LITERAL"), IDENTIFIER("IDENTIFIER"), PRINTF("PRINTF"), SCANF("SCANF"), END_OF_INPUT("END OF INPUT"), OPENING_BRACKET("["), CLOSING_BRACKET("]"), OPENING_PAREN("("), CLOSING_PAREN(")"), DOT("."), HYPHEN_MORE("->"), PLUS_PLUS("++"), HYPHEN_HYPHEN("--"), AMPERSAND("&"), ASTERISK("*"), PLUS("+"), HYPHEN("-"), TILDE("~"), BANG("!"), SLASH("/"), PERCENT("%"), LESS_LESS("<<"), MORE_MORE(">>"), LESS("<"), MORE(">"), LESS_EQUAL("<="), MORE_EQUAL(">="), EQUAL_EQUAL("=="), BANG_EQUAL("!="), CARET("^"), BAR("|"), AMPERSAND_AMPERSAND("&&"), BAR_BAR("||"), QUESTION("?"), COLON(":"), EQUAL("="), ASTERISK_EQUAL("*="), SLASH_EQUAL("/="), PERCENT_EQUAL("%="), PLUS_EQUAL("+="), HYPHEN_EQUAL("-="), LESS_LESS_EQUAL("<<="), MORE_MORE_EQUAL(">>="), AMPERSAND_EQUAL("&="), CARET_EQUAL("^="), BAR_EQUAL("|="), COMMA(","), OPENING_BRACE("{"), CLOSING_BRACE("}"), SEMICOLON(";"); override fun toString(): String = lexeme companion object { val KEYWORDS = entries.subList(ASSERT.ordinal, WHILE.ordinal + 1) val OPERATORS_SEPARATORS = entries.subList(OPENING_BRACKET.ordinal, SEMICOLON.ordinal + 1) } } val keywords: Map = TokenKind.KEYWORDS.associateBy(TokenKind::lexeme) ================================================ FILE: src/main/kotlin/syntax/lexer/TokenKindSet.kt ================================================ package syntax.lexer import java.lang.Long.lowestOneBit import java.lang.Long.numberOfTrailingZeros private val TokenKind.bitmask: Long get() { assert(ordinal < Long.SIZE_BITS) { "$this@$ordinal is not among the first ${Long.SIZE_BITS} enum constants" } return 1L shl ordinal } class TokenKindSet(val bits: Long) { companion object { val EMPTY = TokenKindSet(0L) fun of(kind: TokenKind): TokenKindSet { return TokenKindSet(kind.bitmask) } fun of(kind1: TokenKind, kind2: TokenKind): TokenKindSet { return TokenKindSet(kind1.bitmask or kind2.bitmask) } fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind): TokenKindSet { return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask) } fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind): TokenKindSet { return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask or kind4.bitmask) } fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind, kind5: TokenKind): TokenKindSet { return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask or kind4.bitmask or kind5.bitmask) } } fun contains(kind: TokenKind): Boolean { return (bits and kind.bitmask) != 0L } operator fun plus(kind: TokenKind): TokenKindSet { return TokenKindSet(bits or kind.bitmask) } fun isEmpty(): Boolean { return bits == 0L } fun first(): TokenKind { return TokenKind.entries[numberOfTrailingZeros(bits)] } override fun equals(other: Any?): Boolean { return other is TokenKindSet && this.bits == other.bits } override fun hashCode(): Int { return (bits * 0x10418282f).ushr(32).toInt() } override fun toString(): String { return generateSequence(bits) { b -> b xor lowestOneBit(b) } .takeWhile { b -> b != 0L } .map { b -> TokenKind.entries[numberOfTrailingZeros(b)] } .joinToString(", ", "[", "]") } } ================================================ FILE: src/main/kotlin/syntax/parser/Autocompletion.kt ================================================ package syntax.parser import common.Diagnostic import syntax.lexer.Lexer import syntax.lexer.TokenKind fun autocompleteIdentifier(textBeforeSelection: String): List { val lexer = Lexer(textBeforeSelection) val parser = Parser(lexer) val suffixes = parser.fittingSuffixes(textBeforeSelection) val lcp = longestCommonPrefix(suffixes) return if (lcp.isEmpty()) { suffixes } else { listOf(lcp) } } private fun Parser.fittingSuffixes(textBeforeSelection: String): List { try { translationUnit() } catch (diagnostic: Diagnostic) { if (diagnostic.position != textBeforeSelection.length) throw diagnostic if (previous.kind == TokenKind.IDENTIFIER && previous.end == textBeforeSelection.length) { return when (beforePrevious.kind) { TokenKind.DOT, TokenKind.HYPHEN_MORE -> suffixesIn(allMemberNames.asSequence()) else -> suffixesIn(symbolTable.names()) } } } return emptyList() } private fun Parser.suffixesIn(names: Sequence): List { val prefix = previous.text val prefixLength = prefix.length return names .filter { it.length > prefixLength && it.startsWith(prefix) } .map { it.substring(prefixLength) } .toList() } private fun longestCommonPrefix(strings: List): String { val shortestString = strings.minByOrNull(String::length) ?: "" shortestString.forEachIndexed { index, ch -> if (!strings.all { it[index] == ch }) { return shortestString.substring(0, index) } } return shortestString } ================================================ FILE: src/main/kotlin/syntax/parser/Declarations.kt ================================================ package syntax.parser import semantic.enumStructUnion import semantic.storageClasses import semantic.typeSpecifierIdentifier import semantic.typeSpecifiers import semantic.types.FunctionType import semantic.types.MarkerIsTypedefName import semantic.types.MarkerNotTypedefName import syntax.lexer.Token import syntax.lexer.TokenKind import syntax.lexer.TokenKind.* import syntax.lexer.TokenKindSet import syntax.tree.* fun Parser.declare(namedDeclarator: NamedDeclarator, isTypedefName: Boolean) { val pseudoType = when { isTypedefName -> MarkerIsTypedefName namedDeclarator.declarator.leaf() is Declarator.Function -> FunctionType.declarationMarker() else -> MarkerNotTypedefName } symbolTable.declare(namedDeclarator.name, pseudoType, 0) } fun Parser.isTypedefName(token: Token): Boolean { val symbol = symbolTable.lookup(token) if (symbol == null) { val taggedSymbol = symbolTable.lookup(token.tagged()) if (taggedSymbol != null) { token.error("Did you forget struct/enum/union before ${token.text}?") } } return symbol?.type === MarkerIsTypedefName } fun Parser.isDeclarationSpecifier(token: Token): Boolean = when (token.kind) { TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, CONST, VOLATILE, VOID, CHAR, SHORT, INT, LONG, SIGNED, UNSIGNED, FLOAT, DOUBLE, ENUM, STRUCT, UNION -> true IDENTIFIER -> isTypedefName(token) else -> false } fun Parser.declaration(): Statement { val specifiers = declarationSpecifiers1declareDefTagName() val isTypedef = specifiers.storageClass == TYPEDEF val declarators = commaSeparatedList0(SEMICOLON) { initDeclarator().apply { declare(this, isTypedef) } } if (current == OPENING_BRACE) { val inner = declarators.last().name val outer = symbolTable.currentFunction()!!.name inner.error("cannot define function $inner inside function $outer", outer) } return Declaration(specifiers, declarators).semicolon() } fun Parser.declarationSpecifiers1declareDefTagName(): DeclarationSpecifiers { val specifiers = declarationSpecifiers1() specifiers.defTagName()?.let { name -> symbolTable.declare(name, MarkerNotTypedefName, 0) } return specifiers } fun Parser.declarationSpecifiers1(): DeclarationSpecifiers { val specifiers = declarationSpecifiers0() if (specifiers.list.isEmpty()) { val symbol = symbolTable.lookup(token) if (symbol != null) { val taggedSymbol = symbolTable.lookup(token.tagged()) if (taggedSymbol != null) { token.error("Did you forget struct/enum/union before ${token.text}?") } } illegalStartOf("declaration") } return specifiers } fun Parser.declarationSpecifiers0(): DeclarationSpecifiers { var storageClass: TokenKind = VOID var qualifiers = TokenKindSet.EMPTY var typeTokens = TokenKindSet.EMPTY val list = ArrayList() loop@ while (true) { when (current) { TYPEDEF, EXTERN, STATIC, AUTO, REGISTER -> if (storageClass == VOID) { storageClass = current } else { val previous = list.first { storageClasses.contains(it.kind()) } token.error("multiple storage class specifiers", previous.root()) } CONST, VOLATILE -> if (qualifiers.contains(current)) { val previous = list.first { it.kind() == current } token.error("duplicate type qualifier", previous.root()) } else { qualifiers += current } IDENTIFIER -> if (typeTokens.isEmpty() && isTypedefName(token)) { typeTokens = typeSpecifierIdentifier } else { break@loop } VOID, CHAR, SHORT, INT, LONG, SIGNED, UNSIGNED, FLOAT, DOUBLE, ENUM, STRUCT, UNION -> if (!typeTokens.isEmpty() && enumStructUnion.contains(typeTokens.first())) { val previous = list.first { enumStructUnion.contains(it.kind()) } token.error( "Did you forget to terminate the previous ${previous.kind()} with a semicolon?", previous.root() ) } else if (typeTokens.contains(current)) { val previous = list.first { it.kind() == current } token.error("duplicate type specifier", previous.root()) } else { typeTokens += current if (typeTokens !in typeSpecifiers) token.error("illegal combination of type specifiers: $typeTokens") } else -> break@loop } list.add(declarationSpecifier()) } return DeclarationSpecifiers(list, storageClass, qualifiers, typeTokens) } fun Parser.declarationSpecifier(): DeclarationSpecifier = when (current) { ENUM -> enumSpecifier() STRUCT -> structSpecifier() UNION -> notImplementedYet("unions") else -> DeclarationSpecifier.Primitive(accept()) } fun Parser.enumSpecifier(): DeclarationSpecifier { return if (next() == OPENING_BRACE) { // anonymous enum DeclarationSpecifier.EnumDef(token, enumBody()) } else { val name = expect(IDENTIFIER).tagged() if (current == OPENING_BRACE) { // named enum DeclarationSpecifier.EnumDef(name, enumBody()) } else { DeclarationSpecifier.EnumRef(name) } } } fun Parser.enumBody(): List { return braced { commaSeparatedList1 { Enumerator(expect(IDENTIFIER), optional(EQUAL, ::assignmentExpression)).apply { symbolTable.declare(name, MarkerNotTypedefName, 0) } } } } fun Parser.structSpecifier(): DeclarationSpecifier { return if (next() == OPENING_BRACE) { // anonymous struct DeclarationSpecifier.StructDef(token, structBody()) } else { val name = expect(IDENTIFIER).tagged() if (current == OPENING_BRACE) { // named struct DeclarationSpecifier.StructDef(name, structBody()) } else { DeclarationSpecifier.StructRef(name) } } } fun Parser.structBody(): List { return braced { list1Until(CLOSING_BRACE) { StructDeclaration(declarationSpecifiers1(), commaSeparatedList1 { namedDeclarator().apply { allMemberNames.add(name.text) } }).semicolon() } } } fun Parser.initDeclarator(): NamedDeclarator { return initDeclarator(namedDeclarator()) } fun Parser.initDeclarator(namedDeclarator: NamedDeclarator): NamedDeclarator { return if (current == EQUAL) { next() with(namedDeclarator) { NamedDeclarator(name, Declarator.Initialized(declarator, initializer())) } } else { namedDeclarator } } fun Parser.initializer(): Initializer { return if (current == OPENING_BRACE) { InitializerList(token, braced { trailingCommaSeparatedList1(CLOSING_BRACE, ::initializer) }) } else { ExpressionInitializer(assignmentExpression()) } } fun Parser.namedDeclarator(): NamedDeclarator { if (current == ASTERISK) { next() val qualifiers = typeQualifierList() return namedDeclarator().map { Declarator.Pointer(it, qualifiers) } } var temp: NamedDeclarator = when (current) { OPENING_PAREN -> parenthesized(::namedDeclarator) IDENTIFIER -> NamedDeclarator(accept(), Declarator.Identity) else -> illegalStartOf("declarator") } while (true) { temp = when (current) { OPENING_BRACKET -> temp.map { Declarator.Array(it, declaratorArray()) } OPENING_PAREN -> temp.map { Declarator.Function(it, declaratorFunction()) } else -> return temp } } } fun Parser.typeQualifierList(): List { return collectWhile { current == CONST } } fun Parser.declaratorArray(): Expression? { expect(OPENING_BRACKET) return ::expression optionalBefore CLOSING_BRACKET } fun Parser.declaratorFunction(): List { return symbolTable.scoped { parenthesized { if (current == VOID && lookahead.kind == CLOSING_PAREN) { next() } commaSeparatedList0(CLOSING_PAREN) { val specifiers = declarationSpecifiers1declareDefTagName() val declarator = parameterDeclarator() if (declarator.name.wasProvided()) { declare(declarator, isTypedefName = false) } FunctionParameter(specifiers, declarator) } } } } fun Parser.abstractDeclarator(): Declarator { if (current == ASTERISK) { next() val qualifiers = typeQualifierList() return Declarator.Pointer(abstractDeclarator(), qualifiers) } var temp: Declarator = when (current) { OPENING_PAREN -> parenthesized(::abstractDeclarator) IDENTIFIER -> token.error("identifier in abstract declarator") else -> Declarator.Identity } while (true) { temp = when (current) { OPENING_BRACKET -> Declarator.Array(temp, declaratorArray()) OPENING_PAREN -> Declarator.Function(temp, declaratorFunction()) else -> return temp } } } fun Parser.parameterDeclarator(): NamedDeclarator { if (current == ASTERISK) { next() val qualifiers = typeQualifierList() return parameterDeclarator().map { Declarator.Pointer(it, qualifiers) } } var temp: NamedDeclarator = when (current) { OPENING_PAREN -> { if (isDeclarationSpecifier(lookahead)) { NamedDeclarator(token, Declarator.Function(Declarator.Identity, declaratorFunction())) } else { parenthesized(::parameterDeclarator) } } IDENTIFIER -> NamedDeclarator(accept(), Declarator.Identity) else -> NamedDeclarator(token, Declarator.Identity) } while (true) { temp = when (current) { OPENING_BRACKET -> temp.map { Declarator.Array(it, declaratorArray()) } OPENING_PAREN -> temp.map { Declarator.Function(it, declaratorFunction()) } else -> return temp } } } ================================================ FILE: src/main/kotlin/syntax/parser/Expressions.kt ================================================ package syntax.parser import syntax.lexer.TokenKind import syntax.lexer.TokenKind.* import syntax.tree.* const val PRECEDENCE_POSTFIX = 150 const val PRECEDENCE_PREFIX = 140 const val PRECEDENCE_COMMA = 10 fun Parser.condition(): Expression { return parenthesized(::expression) } @Suppress("NOTHING_TO_INLINE") inline fun Parser.expression(): Expression { return subexpression(outerPrecedence = 0) } @Suppress("NOTHING_TO_INLINE") inline fun Parser.assignmentExpression(): Expression { return subexpression(outerPrecedence = PRECEDENCE_COMMA) } fun Parser.functionCallArgument(): Expression { if (isDeclarationSpecifier(token)) { token.error("function call arguments require no types, \nas opposed to function definition parameters") } return subexpression(outerPrecedence = PRECEDENCE_COMMA) } fun Parser.subexpression(outerPrecedence: Int): Expression { val nullDenotation = nullDenotations[current.ordinal] ?: illegalStartOf("expression") return subexpression(with(nullDenotation) { parse(accept()) }, outerPrecedence) } tailrec fun Parser.subexpression(left: Expression, outerPrecedence: Int): Expression { val leftDenotation = leftDenotations[current.ordinal] ?: return left if (leftDenotation.precedence <= outerPrecedence) return left return subexpression(with(leftDenotation) { parse(left, accept()) }, outerPrecedence) } private val nullDenotations = arrayOfNulls(128).apply { this[IDENTIFIER] = IdentifierDenotation this[DOUBLE_CONSTANT, FLOAT_CONSTANT, INTEGER_CONSTANT, CHARACTER_CONSTANT] = ConstantDenotation this[STRING_LITERAL] = StringLiteralDenotation this[OPENING_PAREN] = PossibleCastDenotation this[PLUS_PLUS, HYPHEN_HYPHEN, AMPERSAND, ASTERISK, PLUS, HYPHEN, TILDE, BANG] = PrefixDenotation this[SIZEOF] = SizeofDenotation } private val leftDenotations = arrayOfNulls(128).apply { this[OPENING_BRACKET] = SubscriptDenotation this[OPENING_PAREN] = FunctionCallDenotation this[DOT] = DirectMemberDenotation this[HYPHEN_MORE] = IndirectMemberDenotation this[PLUS_PLUS, HYPHEN_HYPHEN] = PostfixCrementDenotation this[ASTERISK, SLASH, PERCENT] = LeftAssociativeDenotation(130, ::Multiplicative) this[PLUS] = LeftAssociativeDenotation(120, ::Plus) this[HYPHEN] = LeftAssociativeDenotation(120, ::Minus) this[LESS_LESS, MORE_MORE] = LeftAssociativeDenotation(110, ::Shift) this[LESS, MORE, LESS_EQUAL, MORE_EQUAL] = LeftAssociativeDenotation(100, ::RelationalEquality) this[EQUAL_EQUAL, BANG_EQUAL] = LeftAssociativeDenotation(90, ::RelationalEquality) this[AMPERSAND] = LeftAssociativeDenotation(80, ::Bitwise) this[CARET] = LeftAssociativeDenotation(70, ::Bitwise) this[BAR] = LeftAssociativeDenotation(60, ::Bitwise) this[AMPERSAND_AMPERSAND] = RightAssociativeDenotation(50, ::Logical) this[BAR_BAR] = RightAssociativeDenotation(40, ::Logical) this[QUESTION] = ConditionalDenotation(30) this[EQUAL] = RightAssociativeDenotation(20, ::Assignment) this[PLUS_EQUAL] = RightAssociativeDenotation(20, ::PlusAssignment) this[HYPHEN_EQUAL] = RightAssociativeDenotation(20, ::MinusAssignment) this[COMMA] = RightAssociativeDenotation(PRECEDENCE_COMMA, ::Comma) } private operator fun Array.set(index: TokenKind, value: V) { this[index.ordinal] = value } private operator fun Array.set(vararg indexes: TokenKind, value: V) { for (index in indexes) { this[index.ordinal] = value } } ================================================ FILE: src/main/kotlin/syntax/parser/ExternalDefinitions.kt ================================================ package syntax.parser import semantic.types.FunctionType import syntax.lexer.TokenKind.* import syntax.tree.* fun Parser.translationUnit(): TranslationUnit { return TranslationUnit(list1Until(END_OF_INPUT, ::externalDeclaration)) } fun Parser.externalDeclaration(): Node { val specifiers = declarationSpecifiers1declareDefTagName() if (current == SEMICOLON && specifiers.isDeclaratorOptional()) { return Declaration(specifiers, emptyList()).semicolon() } val firstNamedDeclarator = namedDeclarator() if (firstNamedDeclarator.declarator.leaf() is Declarator.Function) { if (current == SEMICOLON && lookahead.kind == OPENING_BRACE) { token.error("function definitions require no semicolon") } if (current == OPENING_BRACE) { symbolTable.declare(firstNamedDeclarator.name, FunctionType.DEFINITION_MARKER, 0) return symbolTable.rescoped { braced { FunctionDefinition(specifiers, firstNamedDeclarator, list0Until(CLOSING_BRACE, ::statement), token) } } } } val isTypedefName = specifiers.storageClass == TYPEDEF declare(firstNamedDeclarator, isTypedefName) val declarators = commaSeparatedList1(initDeclarator(firstNamedDeclarator)) { initDeclarator().apply { declare(this, isTypedefName) } } return Declaration(specifiers, declarators).semicolon() } ================================================ FILE: src/main/kotlin/syntax/parser/LeftDenotations.kt ================================================ package syntax.parser import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.tree.* abstract class LeftDenotation(val precedence: Int) { abstract fun Parser.parse(left: Expression, operator: Token): Expression } object SubscriptDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { override fun Parser.parse(left: Expression, operator: Token): Expression { return Subscript(left, operator, expression() before CLOSING_BRACKET) } } object FunctionCallDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { override fun Parser.parse(left: Expression, operator: Token): Expression { return FunctionCall(left, commaSeparatedList0(CLOSING_PAREN, ::functionCallArgument) before CLOSING_PAREN) } } object DirectMemberDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { override fun Parser.parse(left: Expression, operator: Token): Expression { return DirectMemberAccess(left, operator, expect(IDENTIFIER)) } } object IndirectMemberDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { override fun Parser.parse(left: Expression, operator: Token): Expression { return IndirectMemberAccess(left, operator, expect(IDENTIFIER)) } } object PostfixCrementDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { override fun Parser.parse(left: Expression, operator: Token): Expression { return Postfix(left, operator) } } class LeftAssociativeDenotation(precedence: Int, val factory: (Expression, Token, Expression) -> Expression) : LeftDenotation(precedence) { override fun Parser.parse(left: Expression, operator: Token): Expression { return factory(left, operator, subexpression(precedence)) } } class RightAssociativeDenotation(precedence: Int, val factory: (Expression, Token, Expression) -> Expression) : LeftDenotation(precedence) { override fun Parser.parse(left: Expression, operator: Token): Expression { return factory(left, operator, subexpression(precedence - 1)) } } class ConditionalDenotation(precedence: Int) : LeftDenotation(precedence) { override fun Parser.parse(left: Expression, operator: Token): Expression { return Conditional(left, operator, expression(), expect(COLON), subexpression(precedence - 1)) } } ================================================ FILE: src/main/kotlin/syntax/parser/NullDenotations.kt ================================================ package syntax.parser import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.tree.* abstract class NullDenotation { abstract fun Parser.parse(token: Token): Expression } object IdentifierDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { return when (token.text) { "printf" -> parenthesized { printfCall(token.withTokenKind(PRINTF)) } "scanf" -> parenthesized { scanfCall(token.withTokenKind(SCANF)) } else -> Identifier(token) } } private fun Parser.printfCall(printf: Token): Expression { val format = expect(STRING_LITERAL) val arguments = if (current == COMMA) { next() commaSeparatedList1(::assignmentExpression) } else { emptyList() } return PrintfCall(printf, format, arguments) } private fun Parser.scanfCall(scanf: Token): Expression { val format = expect(STRING_LITERAL) val arguments = if (current == COMMA) { next() commaSeparatedList1(::assignmentExpression) } else { emptyList() } return ScanfCall(scanf, format, arguments) } } object ConstantDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { return Constant(token) } } object StringLiteralDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { return StringLiteral(token) } } object PossibleCastDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { val specifiers = declarationSpecifiers0() return if (specifiers.list.isEmpty()) { expression() before CLOSING_PAREN } else { val declarator = abstractDeclarator() before CLOSING_PAREN Cast(token, specifiers, declarator, subexpression(PRECEDENCE_PREFIX)) } } } object PrefixDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { val operand = subexpression(PRECEDENCE_PREFIX) return when (token.kind) { PLUS_PLUS, HYPHEN_HYPHEN -> Prefix(token, operand) AMPERSAND -> Reference(token, operand) ASTERISK -> Dereference(token, operand) PLUS -> UnaryPlus(token, operand) HYPHEN -> UnaryMinus(token, operand) TILDE -> BitwiseNot(token, operand) BANG -> LogicalNot(token, operand) else -> error("no parse for $token") } } } object SizeofDenotation : NullDenotation() { override fun Parser.parse(token: Token): Expression { return if (current == OPENING_PAREN && isDeclarationSpecifier(lookahead)) { parenthesized { SizeofType(token, declarationSpecifiers0(), abstractDeclarator()) } } else { SizeofExpression(token, subexpression(PRECEDENCE_PREFIX)) } } } ================================================ FILE: src/main/kotlin/syntax/parser/Parser.kt ================================================ package syntax.parser import common.Diagnostic import semantic.SymbolTable import syntax.lexer.Lexer import syntax.lexer.Token import syntax.lexer.TokenKind import syntax.lexer.TokenKind.* import syntax.lexer.nextToken class Parser(private val lexer: Lexer) { var beforePrevious: Token = Token(END_OF_INPUT, 0, "", "") private set var previous: Token = Token(END_OF_INPUT, 0, "", "") private set var token: Token = lexer.nextToken() private set var current: TokenKind = token.kind private set var lookahead: Token = lexer.nextToken() private set fun next(): TokenKind { beforePrevious = previous previous = token token = lookahead current = token.kind lookahead = lexer.nextToken() return current } fun accept(): Token { val result = token next() return result } fun expect(expected: TokenKind): Token { if (current != expected) throw Diagnostic(previous.end, "expected $expected") return accept() } fun T.semicolon(): T { expect(SEMICOLON) return this } infix fun T.before(expected: TokenKind): T { expect(expected) return this } fun illegalStartOf(rule: String): Nothing { token.error("illegal start of $rule") } fun notImplementedYet(feature: String): Nothing { token.error("$feature not implemented yet") } inline fun commaSeparatedList1(first: T, parse: () -> T): List { val list = mutableListOf(first) while (current == COMMA) { next() list.add(parse()) } return list } inline fun commaSeparatedList1(parse: () -> T): List { return commaSeparatedList1(parse(), parse) } inline fun commaSeparatedList0(terminator: TokenKind, parse: () -> T): List { return if (current == terminator) { emptyList() } else { commaSeparatedList1(parse) } } inline fun trailingCommaSeparatedList1(terminator: TokenKind, parse: () -> T): List { val list = mutableListOf(parse()) while (current == COMMA && next() != terminator) { list.add(parse()) } return list } inline fun list1While(proceed: () -> Boolean, parse: () -> T): List { val list = mutableListOf(parse()) while (proceed()) { list.add(parse()) } return list } inline fun list0While(proceed: () -> Boolean, parse: () -> T): List { return if (!proceed()) { emptyList() } else { list1While(proceed, parse) } } inline fun list1Until(terminator: TokenKind, parse: () -> T): List { return list1While({ current != terminator }, parse) } inline fun list0Until(terminator: TokenKind, parse: () -> T): List { return list0While({ current != terminator }, parse) } inline fun collectWhile(proceed: () -> Boolean): List { return list0While(proceed, ::accept) } inline fun parenthesized(parse: () -> T): T { expect(OPENING_PAREN) val result = parse() expect(CLOSING_PAREN) return result } inline fun braced(parse: () -> T): T { expect(OPENING_BRACE) val result = parse() expect(CLOSING_BRACE) return result } infix fun (() -> T).optionalBefore(terminator: TokenKind): T? { return if (current == terminator) { next() null } else { this() before terminator } } inline fun optional(indicator: TokenKind, parse: () -> T): T? { return if (current != indicator) { null } else { next() parse() } } val symbolTable = SymbolTable() val allMemberNames = HashSet() } ================================================ FILE: src/main/kotlin/syntax/parser/Statements.kt ================================================ package syntax.parser import common.Diagnostic import syntax.lexer.Token import syntax.lexer.TokenKind.* import syntax.tree.* fun Parser.statement(): Statement = when (current) { IF -> IfThenElse(accept(), condition(), statement(), optional(ELSE, ::statement)) SWITCH -> Switch(accept(), condition(), statement()) CASE -> Case(accept(), expression() before COLON, statement()) DEFAULT -> Default(accept() before COLON, statement()) WHILE -> While(accept(), condition(), statement()) DO -> Do(accept(), statement() before WHILE, condition()).semicolon() FOR -> symbolTable.scoped { val f0r = accept() before OPENING_PAREN For( f0r, forInit(f0r), ::expression optionalBefore SEMICOLON, ::expression optionalBefore CLOSING_PAREN, statement() ) } GOTO -> Goto(accept(), expect(IDENTIFIER)).semicolon() CONTINUE -> Continue(accept()).semicolon() BREAK -> Break(accept()).semicolon() RETURN -> Return(accept(), ::expression optionalBefore SEMICOLON) ASSERT -> Assert(accept(), expression()).semicolon() TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, CONST, VOLATILE, VOID, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, SIGNED, UNSIGNED, STRUCT, UNION, ENUM -> declaration() OPENING_BRACE -> symbolTable.scoped { Block(token, braced { list0Until(CLOSING_BRACE, ::statement) }) } IDENTIFIER -> when { lookahead.kind == COLON -> LabeledStatement(accept() before COLON, statement()) isTypedefName(token) -> declaration() else -> ExpressionStatement(expression()).semicolon() } SEMICOLON -> token.error("unexpected semicolon") else -> ExpressionStatement(expression()).semicolon() } private fun Parser.forInit(f0r: Token): Statement? = when (current) { SEMICOLON -> null.semicolon() TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, CONST, VOLATILE, VOID, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, SIGNED, UNSIGNED, STRUCT, UNION, ENUM -> forInitDeclaration(f0r) IDENTIFIER -> when { isTypedefName(token) -> forInitDeclaration(f0r) else -> forInitExpressionStatement(f0r) } else -> forInitExpressionStatement(f0r) } private const val FOR_LOOP_SYNTAX = "for (init; condition; update)\n ^ ^\nSemicolons, NOT commas!" private fun Parser.forInitDeclaration(f0r: Token): Statement { try { return declaration() } catch (diagnostic: Diagnostic) { if (diagnostic.message.endsWith(" was already declared elsewhere")) { f0r.error(FOR_LOOP_SYNTAX) } else { throw diagnostic } } } private fun Parser.forInitExpressionStatement(f0r: Token): Statement { val result = ExpressionStatement(expression()) if (current == CLOSING_PAREN) { f0r.error(FOR_LOOP_SYNTAX) } return result.semicolon() } ================================================ FILE: src/main/kotlin/syntax/tree/DeclarationSpecifier.kt ================================================ package syntax.tree import semantic.types.Later import semantic.types.Type import syntax.lexer.Token import syntax.lexer.TokenKind import syntax.lexer.TokenKind.ENUM import syntax.lexer.TokenKind.STRUCT import syntax.lexer.TokenKindSet sealed class DeclarationSpecifier : Node() { abstract fun kind(): TokenKind class Primitive(val token: Token) : DeclarationSpecifier() { override fun kind(): TokenKind = token.kind override fun root(): Token = token } class StructDef(val name: Token, val body: List) : DeclarationSpecifier() { override fun kind(): TokenKind = STRUCT override fun root(): Token = name override fun forEachChild(action: (Node) -> Unit) { body.forEach { action(it) } } override fun toString(): String = if (name.wasProvided()) "struct $name" else "struct" } class StructRef(val name: Token) : DeclarationSpecifier() { override fun kind(): TokenKind = STRUCT override fun root(): Token = name override fun toString(): String = "struct $name" } class EnumDef(val name: Token, val body: List) : DeclarationSpecifier() { override fun kind(): TokenKind = ENUM override fun root(): Token = name override fun forEachChild(action: (Node) -> Unit) { body.forEach { action(it) } } override fun toString(): String = if (name.wasProvided()) "enum $name" else "enum" } class EnumRef(val name: Token) : DeclarationSpecifier() { override fun kind(): TokenKind = ENUM override fun root(): Token = name override fun toString(): String = "enum $name" } } class StructDeclaration(val specifiers: DeclarationSpecifiers, val declarators: List) : Node() { override fun root(): Token = specifiers.root() override fun forEachChild(action: (Node) -> Unit) { declarators.forEach { action(it) } } override fun toString(): String = specifiers.toString() } class DeclarationSpecifiers( val list: List, val storageClass: TokenKind, val qualifiers: TokenKindSet, val typeTokens: TokenKindSet ) : Node() { var type: Type = Later override fun root(): Token = list[0].root() override fun forEachChild(action: (Node) -> Unit) { list.forEach { action(it) } } override fun toString(): String = list.joinToString(" ") fun defTagName(): Token? { when (typeTokens.first()) { ENUM -> list.forEach { if (it is DeclarationSpecifier.EnumDef && it.name.wasProvided()) return it.name } STRUCT -> list.forEach { if (it is DeclarationSpecifier.StructDef && it.name.wasProvided()) return it.name } else -> {} } return null } fun isDeclaratorOptional(): Boolean { return (storageClass != TokenKind.TYPEDEF) && when (typeTokens.first()) { ENUM -> true STRUCT -> list.any { it is DeclarationSpecifier.StructDef && it.name.wasProvided() } else -> false } } } class Enumerator(val name: Token, val init: Expression?) : Node() { override fun root(): Token = name } ================================================ FILE: src/main/kotlin/syntax/tree/Declarator.kt ================================================ package syntax.tree import semantic.types.Later import semantic.types.Type import syntax.lexer.Token import syntax.lexer.hiddenIdentifier class NamedDeclarator(val name: Token, val declarator: Declarator) : Node() { override fun forEachChild(action: (Node) -> Unit) { if (declarator is Declarator.Initialized) { action(declarator.init) } } override fun root(): Token = name var type: Type = Later var offset: Int = 1234567890 fun hidden(): NamedDeclarator { val result = NamedDeclarator(hiddenIdentifier, declarator) result.type = type result.offset = offset return result } override fun toString(): String = "$name : $type" inline fun map(f: (Declarator) -> Declarator): NamedDeclarator = NamedDeclarator(name, f(declarator)) } class FunctionParameter(val specifiers: DeclarationSpecifiers, val namedDeclarator: NamedDeclarator) sealed class Declarator { fun leaf(): Declarator = leaf(Identity) protected abstract fun leaf(parent: Declarator): Declarator object Identity : Declarator() { override fun leaf(parent: Declarator): Declarator = parent } class Pointer(val child: Declarator, val qualifiers: List) : Declarator() { override fun leaf(parent: Declarator): Declarator = child.leaf(this) } class Array(val child: Declarator, val size: Expression?) : Declarator() { override fun leaf(parent: Declarator): Declarator = child.leaf(this) } class Function(val child: Declarator, val parameters: List) : Declarator() { override fun leaf(parent: Declarator): Declarator = child.leaf(this) } class Initialized(val declarator: Declarator, val init: Initializer) : Declarator() { override fun leaf(parent: Declarator): Declarator = declarator.leaf(Identity) } } abstract class Initializer : Node() class ExpressionInitializer(val expression: Expression) : Initializer() { override fun forEachChild(action: (Node) -> Unit) { action(expression) } override fun root(): Token = expression.root() } class InitializerList(val openBrace: Token, val list: List) : Initializer() { override fun forEachChild(action: (Node) -> Unit) { list.forEach(action) } override fun root(): Token = openBrace } ================================================ FILE: src/main/kotlin/syntax/tree/Expression.kt ================================================ package syntax.tree import interpreter.Value import semantic.Symbol import semantic.types.Later import semantic.types.Type import syntax.lexer.Token abstract class Expression : Node() { var type: Type = Later var value: Value? = null var isLocator: Boolean = false override fun toString(): String { value?.let { value -> return "${super.toString()} : $type ${value.show()}" } return "${super.toString()} : $type" } } abstract class Unary(val operator: Token, val operand: Expression) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(operand) } override fun root(): Token = operator } abstract class Binary(val left: Expression, val operator: Token, val right: Expression) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(left) action(right) } override fun root(): Token = operator } class Constant(val constant: Token) : Expression() { override fun root(): Token = constant } class StringLiteral(val literal: Token) : Expression() { override fun root(): Token = literal } class Identifier(val name: Token) : Expression() { lateinit var symbol: Symbol override fun root(): Token = name } class PrintfCall(val printf: Token, val format: Token, val arguments: List) : Expression() { override fun forEachChild(action: (Node) -> Unit) { arguments.forEach(action) } override fun root(): Token = printf override fun toString(): String = "printf ${format.source} : $type" } class ScanfCall(val scanf: Token, val format: Token, val arguments: List) : Expression() { override fun forEachChild(action: (Node) -> Unit) { arguments.forEach(action) } override fun root(): Token = scanf override fun toString(): String = "scanf ${format.source} : $type" } class Postfix(x: Expression, f: Token) : Unary(f, x) class Subscript(x: Expression, f: Token, y: Expression) : Binary(x, f, y) { override fun toString(): String = "[] : $type" } class FunctionCall(val function: Expression, val arguments: List) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(function) arguments.forEach(action) } override fun root(): Token = function.root() override fun toString(): String = "() : $type" } class DirectMemberAccess(val left: Expression, val dot: Token, val right: Token) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(left) } override fun root(): Token = dot } class IndirectMemberAccess(val left: Expression, val arrow: Token, val right: Token) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(left) } override fun root(): Token = arrow } class Prefix(f: Token, x: Expression) : Unary(f, x) class Reference(f: Token, x: Expression) : Unary(f, x) class Dereference(f: Token, x: Expression) : Unary(f, x) class UnaryPlus(f: Token, x: Expression) : Unary(f, x) class UnaryMinus(f: Token, x: Expression) : Unary(f, x) class BitwiseNot(f: Token, x: Expression) : Unary(f, x) class LogicalNot(f: Token, x: Expression) : Unary(f, x) class Cast(operator: Token, val specifiers: DeclarationSpecifiers, val declarator: Declarator, operand: Expression) : Unary(operator, operand) class SizeofType(val operator: Token, val specifiers: DeclarationSpecifiers, val declarator: Declarator) : Expression() { var operandType: Type = Later override fun root(): Token = operator } class SizeofExpression(f: Token, x: Expression) : Unary(f, x) class Multiplicative(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Plus(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Minus(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Shift(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class RelationalEquality(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Bitwise(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Logical(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Conditional( val condition: Expression, val question: Token, val th3n: Expression, val colon: Token, val e1se: Expression ) : Expression() { override fun forEachChild(action: (Node) -> Unit) { action(condition) action(th3n) action(e1se) } override fun root(): Token = question } class Assignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class PlusAssignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class MinusAssignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) class Comma(x: Expression, f: Token, y: Expression) : Binary(x, f, y) ================================================ FILE: src/main/kotlin/syntax/tree/External.kt ================================================ package syntax.tree import interpreter.BasicBlock import semantic.types.StructType import semantic.types.StructTypeLater import syntax.lexer.Token class TranslationUnit(val externalDeclarations: List) : Node() { val functions = externalDeclarations.filterIsInstance() val declarations: List init { declarations = ArrayList() walkChildren({}) { if (it is Declaration) { declarations.add(it) } } } override fun forEachChild(action: (Node) -> Unit) { externalDeclarations.forEach(action) } override fun root(): Token = externalDeclarations.first().root() override fun toString(): String = "translation unit" } class FunctionDefinition( val specifiers: DeclarationSpecifiers, val namedDeclarator: NamedDeclarator, val body: List, val closingBrace: Token ) : Node() { fun name(): String = namedDeclarator.name.text val parameters: List = (namedDeclarator.declarator.leaf() as Declarator.Function).parameters.map(FunctionParameter::namedDeclarator) var stackFrameType: StructType = StructTypeLater lateinit var controlFlowGraph: LinkedHashMap override fun forEachChild(action: (Node) -> Unit) { // action(declarator) body.forEach(action) } override fun root(): Token = namedDeclarator.name override fun toString(): String = namedDeclarator.toString() } ================================================ FILE: src/main/kotlin/syntax/tree/Node.kt ================================================ package syntax.tree import syntax.lexer.Token abstract class Node { fun walk(enter: (Node) -> Unit, leave: (Node) -> Unit) { enter(this) walkChildren(enter, leave) leave(this) } fun walkChildren(enter: (Node) -> Unit, leave: (Node) -> Unit) { forEachChild { it.walk(enter, leave) } } open fun forEachChild(action: (Node) -> Unit) { } abstract fun root(): Token override fun toString(): String = root().toString() } ================================================ FILE: src/main/kotlin/syntax/tree/Statement.kt ================================================ package syntax.tree import syntax.lexer.Token abstract class Statement : Node() class Declaration(val specifiers: DeclarationSpecifiers, val namedDeclarators: List) : Statement() { override fun forEachChild(action: (Node) -> Unit) { namedDeclarators.forEach(action) } override fun root(): Token = specifiers.root() override fun toString(): String = specifiers.toString() } class Block(val openBrace: Token, val statements: List) : Statement() { override fun forEachChild(action: (Node) -> Unit) { statements.forEach(action) } override fun root(): Token = openBrace } class ExpressionStatement(val expression: Expression) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(expression) } override fun root(): Token = expression.root() override fun toString(): String = "${super.toString()} ;" } class IfThenElse(val iF: Token, val condition: Expression, val th3n: Statement, val e1se: Statement?) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(condition) action(th3n) e1se?.let { action(it) } } override fun root(): Token = iF } class Switch(val switch: Token, val control: Expression, val body: Statement) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(control) action(body) } override fun root(): Token = switch } class Case(val case: Token, val choice: Expression, val body: Statement) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(choice) action(body) } override fun root(): Token = case } class Default(val default: Token, val body: Statement) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(body) } override fun root(): Token = default } class While(val whi1e: Token, val condition: Expression, val body: Statement) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(condition) action(body) } override fun root(): Token = whi1e } class Do(val d0: Token, val body: Statement, val condition: Expression) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(body) action(condition) } override fun root(): Token = d0 } class For( val f0r: Token, val init: Statement?, val condition: Expression?, val update: Expression?, val body: Statement ) : Statement() { override fun forEachChild(action: (Node) -> Unit) { init?.run(action) condition?.run(action) update?.run(action) action(body) } override fun root(): Token = f0r } class Continue(val continu3: Token) : Statement() { override fun root(): Token = continu3 } class Break(val br3ak: Token) : Statement() { override fun root(): Token = br3ak } class Return(val r3turn: Token, val result: Expression?) : Statement() { override fun forEachChild(action: (Node) -> Unit) { result?.run(action) } override fun root(): Token = r3turn } class Assert(val ass3rt: Token, val condition: Expression) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(condition) } override fun root(): Token = ass3rt } class LabeledStatement(val label: Token, val statement: Statement) : Statement() { override fun forEachChild(action: (Node) -> Unit) { action(statement) } override fun root(): Token = label } class Goto(val goto: Token, val label: Token) : Statement() { override fun root(): Token = goto } ================================================ FILE: src/main/kotlin/text/Char.kt ================================================ package text fun Char.quote(): String = when (this) { '\u0000' -> "'\\0'" '\u0007' -> "'\\a'" '\u0008' -> "'\\b'" '\u0009' -> "'\\t'" '\u000a' -> "'\\n'" '\u000b' -> "'\\v'" '\u000c' -> "'\\f'" '\u000d' -> "'\\r'" ' ' -> "' '" '!' -> "'!'" '"' -> "'\"'" '#' -> "'#'" '$' -> "'$'" '%' -> "'%'" '&' -> "'&'" '\'' -> "'\\\''" '(' -> "'('" ')' -> "')'" '*' -> "'*'" '+' -> "'+'" ',' -> "','" '-' -> "'-'" '.' -> "'.'" '/' -> "'/'" '0' -> "'0'" '1' -> "'1'" '2' -> "'2'" '3' -> "'3'" '4' -> "'4'" '5' -> "'5'" '6' -> "'6'" '7' -> "'7'" '8' -> "'8'" '9' -> "'9'" ':' -> "':'" ';' -> "';'" '<' -> "'<'" '=' -> "'='" '>' -> "'>'" '?' -> "'?'" '@' -> "'@'" 'A' -> "'A'" 'B' -> "'B'" 'C' -> "'C'" 'D' -> "'D'" 'E' -> "'E'" 'F' -> "'F'" 'G' -> "'G'" 'H' -> "'H'" 'I' -> "'I'" 'J' -> "'J'" 'K' -> "'K'" 'L' -> "'L'" 'M' -> "'M'" 'N' -> "'N'" 'O' -> "'O'" 'P' -> "'P'" 'Q' -> "'Q'" 'R' -> "'R'" 'S' -> "'S'" 'T' -> "'T'" 'U' -> "'U'" 'V' -> "'V'" 'W' -> "'W'" 'X' -> "'X'" 'Y' -> "'Y'" 'Z' -> "'Z'" '[' -> "'['" '\\' -> "'\\\\'" ']' -> "']'" '^' -> "'^'" '_' -> "'_'" '`' -> "'`'" 'a' -> "'a'" 'b' -> "'b'" 'c' -> "'c'" 'd' -> "'d'" 'e' -> "'e'" 'f' -> "'f'" 'g' -> "'g'" 'h' -> "'h'" 'i' -> "'i'" 'j' -> "'j'" 'k' -> "'k'" 'l' -> "'l'" 'm' -> "'m'" 'n' -> "'n'" 'o' -> "'o'" 'p' -> "'p'" 'q' -> "'q'" 'r' -> "'r'" 's' -> "'s'" 't' -> "'t'" 'u' -> "'u'" 'v' -> "'v'" 'w' -> "'w'" 'x' -> "'x'" 'y' -> "'y'" 'z' -> "'z'" '{' -> "'{'" '|' -> "'|'" '}' -> "'}'" '~' -> "'~'" ' ' -> "' '" '¡' -> "'¡'" '¢' -> "'¢'" '£' -> "'£'" '¤' -> "'¤'" '¥' -> "'¥'" '¦' -> "'¦'" '§' -> "'§'" '¨' -> "'¨'" '©' -> "'©'" 'ª' -> "'ª'" '«' -> "'«'" '¬' -> "'¬'" '­' -> "'­'" '®' -> "'®'" '¯' -> "'¯'" '°' -> "'°'" '±' -> "'±'" '²' -> "'²'" '³' -> "'³'" '´' -> "'´'" 'µ' -> "'µ'" '¶' -> "'¶'" '·' -> "'·'" '¸' -> "'¸'" '¹' -> "'¹'" 'º' -> "'º'" '»' -> "'»'" '¼' -> "'¼'" '½' -> "'½'" '¾' -> "'¾'" '¿' -> "'¿'" 'À' -> "'À'" 'Á' -> "'Á'" 'Â' -> "'Â'" 'Ã' -> "'Ã'" 'Ä' -> "'Ä'" 'Å' -> "'Å'" 'Æ' -> "'Æ'" 'Ç' -> "'Ç'" 'È' -> "'È'" 'É' -> "'É'" 'Ê' -> "'Ê'" 'Ë' -> "'Ë'" 'Ì' -> "'Ì'" 'Í' -> "'Í'" 'Î' -> "'Î'" 'Ï' -> "'Ï'" 'Ð' -> "'Ð'" 'Ñ' -> "'Ñ'" 'Ò' -> "'Ò'" 'Ó' -> "'Ó'" 'Ô' -> "'Ô'" 'Õ' -> "'Õ'" 'Ö' -> "'Ö'" '×' -> "'×'" 'Ø' -> "'Ø'" 'Ù' -> "'Ù'" 'Ú' -> "'Ú'" 'Û' -> "'Û'" 'Ü' -> "'Ü'" 'Ý' -> "'Ý'" 'Þ' -> "'Þ'" 'ß' -> "'ß'" 'à' -> "'à'" 'á' -> "'á'" 'â' -> "'â'" 'ã' -> "'ã'" 'ä' -> "'ä'" 'å' -> "'å'" 'æ' -> "'æ'" 'ç' -> "'ç'" 'è' -> "'è'" 'é' -> "'é'" 'ê' -> "'ê'" 'ë' -> "'ë'" 'ì' -> "'ì'" 'í' -> "'í'" 'î' -> "'î'" 'ï' -> "'ï'" 'ð' -> "'ð'" 'ñ' -> "'ñ'" 'ò' -> "'ò'" 'ó' -> "'ó'" 'ô' -> "'ô'" 'õ' -> "'õ'" 'ö' -> "'ö'" '÷' -> "'÷'" 'ø' -> "'ø'" 'ù' -> "'ù'" 'ú' -> "'ú'" 'û' -> "'û'" 'ü' -> "'ü'" 'ý' -> "'ý'" 'þ' -> "'þ'" 'ÿ' -> "'ÿ'" else -> "'\\${Integer.toOctalString(code)}'" } ================================================ FILE: src/main/kotlin/text/String.kt ================================================ package text fun String.skipDigits(start: Int): Int { val n = length for (i in start until n) { if (this[i] !in '0'..'9') return i } return n } ================================================ FILE: src/main/kotlin/ui/Flexer.kt ================================================ package ui import common.puts import freditor.FlexerState import freditor.FlexerState.EMPTY import freditor.FlexerState.THIS import freditor.FlexerStateBuilder object Flexer : freditor.Flexer() { private val SLASH_SLASH = FlexerState('\n', null).setDefault(THIS) private val SLASH_ASTERISK___ASTERISK_SLASH = EMPTY.tail() private val SLASH_ASTERISK___ASTERISK = FlexerState('*', THIS, '/', SLASH_ASTERISK___ASTERISK_SLASH) private val SLASH_ASTERISK = FlexerState('*', SLASH_ASTERISK___ASTERISK).setDefault(THIS) private val CHAR_CONSTANT_END = EMPTY.tail() private val CHAR_CONSTANT_ESCAPE = FlexerState('\n', null) private val CHAR_CONSTANT_TAIL = FlexerState('\n', null, '\'', CHAR_CONSTANT_END, '\\', CHAR_CONSTANT_ESCAPE).setDefault(THIS) private val CHAR_CONSTANT_HEAD = CHAR_CONSTANT_TAIL.head() private val STRING_LITERAL_END = EMPTY.tail() private val STRING_LITERAL_ESCAPE = FlexerState('\n', null) private val STRING_LITERAL_TAIL = FlexerState('\n', null, '\"', STRING_LITERAL_END, '\\', STRING_LITERAL_ESCAPE).setDefault(THIS) private val STRING_LITERAL_HEAD = STRING_LITERAL_TAIL.head() init { SLASH_ASTERISK___ASTERISK.setDefault(SLASH_ASTERISK) CHAR_CONSTANT_ESCAPE.setDefault(CHAR_CONSTANT_TAIL) STRING_LITERAL_ESCAPE.setDefault(STRING_LITERAL_TAIL) } private val NUMBER_TAIL = FlexerState("..09AFXXafxx", THIS) private val NUMBER_HEAD = NUMBER_TAIL.head() private val IDENTIFIER_TAIL = FlexerState("09AZ__az", THIS) private val IDENTIFIER_HEAD = IDENTIFIER_TAIL.head() private val START = FlexerStateBuilder() .set('(', OPENING_PAREN) .set(')', CLOSING_PAREN) .set('[', OPENING_BRACKET) .set(']', CLOSING_BRACKET) .set('{', OPENING_BRACE) .set('}', CLOSING_BRACE) .set('\n', NEWLINE) .set(' ', SPACE_HEAD) .set('/', FlexerState('*', SLASH_ASTERISK, '/', SLASH_SLASH).head()) .set('\'', CHAR_CONSTANT_HEAD) .set('\"', STRING_LITERAL_HEAD) .set("09", NUMBER_HEAD) .set("AZ__az", IDENTIFIER_HEAD) .build() .verbatim( IDENTIFIER_TAIL, "assert", "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while" ) .verbatim( EMPTY, "!", "!=", "%", "%=", "&", "&&", "&=", "*", "*=", "+", "++", "+=", ",", "-", "--", "-=", "->", ".", "/", "/=", ":", ";", "<", "<<", "<<=", "<=", "=", "==", ">", ">=", ">>", ">>=", "?", "^", "^=", "|", "|=", "||", "~" ) .setDefault(ERROR) override fun start(): FlexerState = START override fun pickColorForLexeme(previousState: FlexerState, endState: FlexerState): Int { return lexemeColors[endState] ?: 0x000000 } private val lexemeColors = hashMapOf(ERROR to 0x808080) .puts( CHAR_CONSTANT_HEAD, CHAR_CONSTANT_TAIL, CHAR_CONSTANT_ESCAPE, STRING_LITERAL_HEAD, STRING_LITERAL_TAIL, STRING_LITERAL_ESCAPE, 0x808080 ) .puts(SLASH_SLASH, SLASH_ASTERISK, SLASH_ASTERISK___ASTERISK, SLASH_ASTERISK___ASTERISK_SLASH, 0x008000) .puts(CHAR_CONSTANT_END, STRING_LITERAL_END, 0xdc009c) .puts(NUMBER_HEAD, NUMBER_TAIL, 0x6400c8) .puts( START.read( "assert", "auto", "break", "case", "const", "continue", "default", "do", "else", "enum", "extern", "for", "goto", "if", "register", "return", "sizeof", "static", "struct", "switch", "typedef", "union", "volatile", "while" ), 0x0000ff ) .puts(START.read("char", "double", "float", "int", "long", "short", "signed", "unsigned", "void"), 0x008080) .puts(START.read("(", ")", "[", "]", "{", "}"), 0xff0000) .puts( START.read( "!", "!=", "%", "%=", "&", "&&", "&=", "*", "*=", "+", "++", "+=", "-", "--", "-=", "->", ".", "/", "/=", ":", "<", "<<", "<<=", "<=", "=", "==", ">", ">=", ">>", ">>=", "?", "^", "^=", "|", "|=", "||", "~" ), 0x804040 ) } ================================================ FILE: src/main/kotlin/ui/MainFrame.kt ================================================ package ui import common.Diagnostic import freditor.* import interpreter.Interpreter import interpreter.Memory import semantic.Linter import semantic.TypeChecker import syntax.lexer.Lexer import syntax.lexer.keywords import syntax.parser.Parser import syntax.parser.autocompleteIdentifier import syntax.parser.translationUnit import syntax.tree.* import java.awt.BorderLayout import java.awt.Dimension import java.awt.EventQueue import java.awt.Toolkit import java.awt.event.KeyAdapter import java.awt.event.KeyEvent import java.util.concurrent.ArrayBlockingQueue import java.util.function.Consumer import javax.swing.* import javax.swing.event.TreeModelListener import javax.swing.tree.TreeModel import javax.swing.tree.TreePath private val NAME = Regex("""[A-Z_a-z][0-9A-Z_a-z]*""") object StopTheProgram : Exception() { private fun readResolve(): Any = StopTheProgram } fun T.sansSerif(): T { this.font = Fronts.sansSerif return this } class MainFrame : JFrame() { private val queue = ArrayBlockingQueue(1) private var interpreter = Interpreter("int main(){return 0;}") private val memoryUI = MemoryUI(Memory(emptySet(), emptyList())) private val scrolledMemory = JScrollPane(memoryUI) private val syntaxTree = JTree().sansSerif() private val scrolledSyntaxTree = JScrollPane(syntaxTree) private val visualizer = JTabbedPane().sansSerif() private val tabbedEditors = TabbedEditors("skorbut", Flexer, JavaIndenter.instance) { freditor -> val editor = FreditorUI(freditor, 0, 25) editor.addKeyListener(object : KeyAdapter() { override fun keyPressed(event: KeyEvent) { when (event.keyCode) { KeyEvent.VK_SPACE -> if (event.isControlDown) { autocompleteIdentifier() } KeyEvent.VK_R -> if (FreditorUI.isControlRespectivelyCommandDown(event) && event.isAltDown) { renameSymbol() } KeyEvent.VK_F1 -> showType() KeyEvent.VK_F3 -> jumpToDeclarationAndFindUsages() KeyEvent.VK_F5 -> into.doClick() KeyEvent.VK_F6 -> over.doClick() KeyEvent.VK_F7 -> r3turn.doClick() } } }) editor.onRightClick = Consumer { editor.clearDiagnostics() showType() } editor } private val editor get() = tabbedEditors.selectedEditor private val slider = JSlider(0, 11, 0).sansSerif() private val timer = Timer(1000) { queue.offer("into") } private val start = JButton("start").sansSerif() private val into = JButton("step into (F5)").sansSerif() private val over = JButton("step over (F6)").sansSerif() private val r3turn = JButton("step return (F7)").sansSerif() private val stop = JButton("stop").sansSerif() private val buttons = JPanel() private val scrolledDiagnostics = JScrollPane() private val consoleUI = JTextArea() private val output = JTabbedPane().sansSerif() private val controls = JPanel() private var targetStackDepth = Int.MAX_VALUE private var lastReceivedPosition = 0 init { title = "skorbut version ${Release.compilationDate(MainFrame::class.java)} @ ${editor.file.parent}" scrolledMemory.preferredSize = Dimension(500, 500) scrolledSyntaxTree.preferredSize = Dimension(500, 500) visualizer.addTab("memory", scrolledMemory) visualizer.addTab("syntax tree", scrolledSyntaxTree) visualizer.addChangeListener { if (visualizer.selectedComponent === scrolledMemory) { memoryUI.update() } else { hideDirtySyntaxTree() } } if (editor.length() == 0) { editor.load(helloWorld) } tabbedEditors.tabs.addChangeListener { updateDiagnostics(emptyList()) hideDirtySyntaxTree() } val horizontalSplit = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, visualizer, tabbedEditors.tabs) horizontalSplit.preferredSize = Dimension(1000, 500) slider.majorTickSpacing = 1 slider.paintLabels = true buttons.add(start) into.isEnabled = false buttons.add(into) over.isEnabled = false buttons.add(over) r3turn.isEnabled = false buttons.add(r3turn) stop.isEnabled = false buttons.add(stop) val diagnosticsPanel = JPanel() diagnosticsPanel.layout = BorderLayout() diagnosticsPanel.add(scrolledDiagnostics) consoleUI.font = Fronts.monospaced consoleUI.isEditable = false output.addTab("diagnostics", scrolledDiagnostics) output.addTab("console", JScrollPane(consoleUI)) output.preferredSize = Dimension(0, Toolkit.getDefaultToolkit().screenSize.height / 5) controls.layout = BoxLayout(controls, BoxLayout.Y_AXIS) controls.add(slider) controls.add(buttons) controls.add(output) val verticalSplit = JSplitPane(JSplitPane.VERTICAL_SPLIT, horizontalSplit, controls) verticalSplit.resizeWeight = 1.0 add(verticalSplit) listenToSyntaxTree() listenToSlider() listenToButtons() listenToConsole() defaultCloseOperation = EXIT_ON_CLOSE tabbedEditors.saveOnExit(this) pack() isVisible = true editor.requestFocusInWindow() } private fun hideDirtySyntaxTree() { if (visualizer.selectedComponent === scrolledSyntaxTree) { if (!isRunning() && !tryCompile()) { visualizer.selectedComponent = scrolledMemory } } } private fun listenToSyntaxTree() { syntaxTree.addTreeSelectionListener { val node = it.newLeadSelectionPath?.lastPathComponent if (node is Node) { editor.setCursorTo(node.root().start) } editor.requestFocusInWindow() } } private fun listenToConsole() { consoleUI.addKeyListener(object : KeyAdapter() { override fun keyTyped(event: KeyEvent) { if (interpreter.console.isBlocked()) { interpreter.console.keyTyped(event.keyChar) updateConsole() } else { JOptionPane.showMessageDialog( this@MainFrame, "You probably forgot one STEP", "Nobody is waiting for input yet", JOptionPane.ERROR_MESSAGE ) } } }) } private fun updateConsole() { consoleUI.text = interpreter.console.getText() output.selectedIndex = 1 requestFocusInBlockedConsoleOrEditor() } private fun requestFocusInBlockedConsoleOrEditor() { if (interpreter.console.isBlocked()) { consoleUI.requestFocusInWindow() } else { editor.requestFocusInWindow() } } private fun listenToSlider() { slider.addChangeListener { requestFocusInBlockedConsoleOrEditor() configureTimer() } } private fun configureTimer() { val d = delay() if (d < 0) { timer.stop() } else { timer.initialDelay = d timer.delay = d if (isRunning()) { timer.restart() } } } private fun delay(): Int { val wait = slider.value return if (wait == 0) -1 else 1.shl(slider.maximum - wait) } private fun isRunning(): Boolean = !start.isEnabled private fun listenToButtons() { start.addActionListener { editor.indent() editor.saveWithBackup() editor.clearDiagnostics() editor.requestFocusInWindow() queue.clear() if (tryCompile()) { run() } } into.addActionListener { requestFocusInBlockedConsoleOrEditor() queue.offer("into") } over.addActionListener { requestFocusInBlockedConsoleOrEditor() queue.offer("over") } r3turn.addActionListener { requestFocusInBlockedConsoleOrEditor() queue.offer("return") } stop.addActionListener { editor.requestFocusInWindow() timer.stop() queue.clear() queue.put("stop") interpreter.console.stop() } } private fun autocompleteIdentifier() { try { val suffixes = autocompleteIdentifier(editor.textBeforeSelection) if (suffixes.size == 1) { editor.insert(suffixes[0]) } else { println(suffixes.sorted().joinToString(", ")) } } catch (diagnostic: Diagnostic) { showDiagnostic(diagnostic) updateDiagnostics(arrayListOf(diagnostic)) } } private fun renameSymbol() { if (isRunning() || !tryCompile()) return val symbol = interpreter.typeChecker.symbolAt(editor.cursor()) ?: return val oldName = symbol.name.text val input = JOptionPane.showInputDialog( this, oldName, "rename symbol", JOptionPane.QUESTION_MESSAGE, null, null, oldName ) ?: return val newName = input.toString().trim() if (!NAME.matches(newName) || newName in keywords) return val positions = IntArray(1 + symbol.usages.size) positions[0] = symbol.name.start symbol.usages.forEachIndexed { index, usage -> positions[1 + index] = usage.name.start } editor.rename(oldName, newName, positions) tryCompile() } private fun detectRoot(node: Node) { val root = node.root() if (editor.cursor() in root.start until root.end) { root.error(" $node ", -1) } } private fun showType() { try { val translationUnit = Parser(Lexer(editor.text)).translationUnit() TypeChecker(translationUnit) translationUnit.walk({}) { node -> if (node is Expression) { detectRoot(node) } else if (node is FunctionDefinition) { detectRoot(node) node.parameters.forEach(::detectRoot) } else if (node is NamedDeclarator) { detectRoot(node) if (node.declarator is Declarator.Initialized) { node.declarator.init.walk({}, ::detectRoot) } } } } catch (diagnostic: Diagnostic) { showDiagnostic(diagnostic) } } private fun jumpToDeclarationAndFindUsages() { if (isRunning() || !tryCompile()) return val symbol = interpreter.typeChecker.symbolAt(editor.cursor()) ?: return val symbolStart = symbol.name.start editor.setCursorTo(symbolStart) val name = symbol.name.text val diagnostics = symbol.usages.map { usage -> val usageStart = usage.name.start val line = 1 + editor.lineOfPosition(usageStart) Diagnostic(usageStart, "usage of $name on line $line", symbolStart) } updateDiagnostics(diagnostics) } private fun tryCompile(): Boolean { try { compile() return true } catch (diagnostic: Diagnostic) { showDiagnostic(diagnostic) updateDiagnostics(arrayListOf(diagnostic)) } catch (other: Throwable) { showDiagnostic(other.message ?: "null") other.printStackTrace() } return false } private fun compile() { interpreter = Interpreter(editor.text) updateSyntaxTreeModel() val linter = Linter(interpreter.translationUnit) updateDiagnostics(linter.getWarnings()) } private fun run() { interpreter.onMemorySet = { memory -> EventQueue.invokeLater { memoryUI.memory = memory } } interpreter.before = ::pauseAt interpreter.after = { EventQueue.invokeAndWait { if (visualizer.selectedComponent === scrolledMemory) { memoryUI.update() } if (interpreter.console.isDirty) { updateConsole() } } } consoleUI.text = "" interpreter.console.update = { EventQueue.invokeAndWait(::updateConsole) } tabbedEditors.tabs.isEnabled = false start.isEnabled = false into.isEnabled = true over.isEnabled = true r3turn.isEnabled = true stop.isEnabled = true configureTimer() tryExecute() } private fun updateSyntaxTreeModel() { syntaxTree.model = object : TreeModel { override fun getRoot(): Any { return interpreter.translationUnit } override fun getChildCount(parent: Any): Int { var count = 0 (parent as Node).forEachChild { ++count } return count } override fun getChild(parent: Any, index: Int): Any? { var child: Node? = null var i = 0 (parent as Node).forEachChild { if (i == index) { child = it } ++i } return child } override fun getIndexOfChild(parent: Any, child: Any): Int { var index = -1 var i = 0 (parent as Node).forEachChild { if (it === child) { index = i } ++i } return index } override fun isLeaf(node: Any): Boolean { return getChildCount(node) == 0 } override fun addTreeModelListener(l: TreeModelListener?) { } override fun removeTreeModelListener(l: TreeModelListener?) { } override fun valueForPathChanged(path: TreePath?, newValue: Any?) { } } } private fun showDiagnostic(diagnostic: Diagnostic) { editor.setCursorTo(diagnostic.position) editor.requestFocusInWindow() editor.showDiagnostic(diagnostic.message, diagnostic.position, diagnostic.columnDelta) } private fun showDiagnostic(position: Int, message: String) { editor.setCursorTo(position) editor.requestFocusInWindow() editor.showDiagnostic(message) } private fun showDiagnostic(message: String) { editor.requestFocusInWindow() editor.showDiagnostic(message) } private fun updateDiagnostics(diagnostics: List) { val list = JList(diagnostics.toTypedArray()).sansSerif() list.addListSelectionListener { event -> if (!event.valueIsAdjusting) { val index = list.selectedIndex if (index != -1) { val diagnostic = diagnostics[index] updateCaretPosition(diagnostic) editor.requestFocusInWindow() list.clearSelection() } } } scrolledDiagnostics.setViewportView(list) if (diagnostics.isEmpty()) { output.setTitleAt(0, "diagnostics") } else { output.setTitleAt(0, "diagnostics (${diagnostics.size})") output.selectedIndex = 0 } } private fun updateCaretPosition(diagnostic: Diagnostic) { if (editor.cursor() != diagnostic.position) { editor.setCursorTo(diagnostic.position) } else if (diagnostic.secondPosition != -1) { editor.setCursorTo(diagnostic.secondPosition) } } private fun pauseAt(position: Int) { lastReceivedPosition = position val entry = if (interpreter.stackDepth <= targetStackDepth) { // Step into mode EventQueue.invokeLater { editor.setCursorTo(position) } // Block until the next button press queue.take() } else { // Step over/return mode // Don't block, but consume potential button presses, especially stop queue.poll() } when (entry) { "into" -> { targetStackDepth = Int.MAX_VALUE } "over" -> { targetStackDepth = interpreter.stackDepth } "return" -> { targetStackDepth = interpreter.stackDepth - 1 } "stop" -> { timer.stop() throw StopTheProgram } } } private fun tryExecute() { Thread { try { targetStackDepth = Int.MAX_VALUE lastReceivedPosition = 0 interpreter.run(editor.cursor(), editor.length()) } catch (_: StopTheProgram) { } catch (diagnostic: Diagnostic) { EventQueue.invokeLater { showDiagnostic(diagnostic) } } catch (other: Throwable) { EventQueue.invokeLater { showDiagnostic(lastReceivedPosition, other.message ?: "null") other.printStackTrace() } } finally { EventQueue.invokeLater { tabbedEditors.tabs.isEnabled = true start.isEnabled = true into.isEnabled = false over.isEnabled = false r3turn.isEnabled = false stop.isEnabled = false timer.stop() } } }.start() } } const val helloWorld = """void demo() { char a[] = "hi"; char * p = a; ++p; ++p; ++p; } """ ================================================ FILE: src/main/kotlin/ui/MemoryUI.kt ================================================ package ui import common.Counter import freditor.Fronts import interpreter.Memory import interpreter.PointerValue import interpreter.Segment import semantic.types.ArrayType import semantic.types.StructType import semantic.types.Type import java.awt.* import java.awt.geom.Line2D import java.awt.geom.QuadCurve2D import javax.swing.* import javax.swing.border.CompoundBorder import javax.swing.border.EmptyBorder import javax.swing.border.LineBorder import javax.swing.border.TitledBorder import kotlin.math.pow import kotlin.math.sqrt class MemoryUI(var memory: Memory) : JPanel() { init { layout = BoxLayout(this, BoxLayout.Y_AXIS) } private val emptyBorder = EmptyBorder(8, 8, 8, 8) private val stringsBorder = LineBorder(Color.LIGHT_GRAY, 2, true) private val staticsBorder = LineBorder(Color.GRAY, 2, true) private val stackBorder = LineBorder(Color.BLUE, 2, true) private val heapBorder = LineBorder(Color(128, 0, 128), 2, true) private var lineBorder = stringsBorder private val rigidWidth = Dimension(300, 0) private fun Segment.objectComponent(title: String, qualified: Type): JComponent { val type = qualified.unqualified() val address = valueIndex val component = when (type) { is ArrayType -> arrayComponent(type) is StructType -> structComponent(type) else -> scalarComponent() } component.alignmentX = Component.LEFT_ALIGNMENT component.border = CompoundBorder( emptyBorder, TitledBorder(lineBorder, title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, Fronts.sansSerif) ) objects[this]!![address][type] = component return component } private fun Segment.arrayComponent(type: ArrayType): JComponent { val cells = JPanel() val axis = if (type.dimensions().and(1) == 1) BoxLayout.X_AXIS else BoxLayout.Y_AXIS cells.layout = BoxLayout(cells, axis) for (i in 0 until type.size) { cells.add(objectComponent(i.toString(), type.elementType)) } return cells } private fun Segment.structComponent(type: StructType): JComponent { val cells = JPanel() val axis = BoxLayout.Y_AXIS cells.layout = BoxLayout(cells, axis) for (symbol in type.members) { val name = symbol.name.text if (!name.startsWith('_')) { cells.add(objectComponent(name, symbol.type)) } else { valueIndex += symbol.type.count() } } return cells } private fun Segment.scalarComponent(): JComponent { val value = this[valueIndex++] val scalar = JLabel(" %3s ".format(value.show())) scalar.font = Fronts.monospaced if (value is PointerValue && value.referenced.isReferable()) { pointers[scalar] = value } return scalar } private val objects = HashMap>>() private val pointers = LinkedHashMap() private var valueIndex = 0 fun update() { removeAll() objects.clear() pointers.clear() updateSingleSegment(memory.stringConstants, stringsBorder) updateSingleSegment(memory.staticVariables, staticsBorder) updateMultipleSegments(memory.heap, heapBorder) add(Box.createVerticalGlue()) updateMultipleSegments(memory.stack, stackBorder) revalidate() repaint() } private fun updateSingleSegment(segment: Segment, border: LineBorder) { val structType = segment.type as StructType if (structType.members.isNotEmpty()) { lineBorder = border update(segment) } } private fun update(segment: Segment) { objects[segment] = MutableList(segment.type.count()) { HashMap() } valueIndex = 0 with(segment) { if (type is StructType) { val title = "%04x %s".format(address.and(0xffff), type.name.text) val component = objectComponent(title, type) val rigidArea = Box.createRigidArea(rigidWidth) as JComponent rigidArea.alignmentX = JComponent.LEFT_ALIGNMENT component.add(rigidArea) add(component) } else { val title = "%04x".format(address.and(0xffff)) add(objectComponent(title, type)) } } } private fun updateMultipleSegments(segments: List, border: LineBorder) { lineBorder = border segments.asReversed().forEach(::update) } override fun paint(graphics: Graphics) { super.paint(graphics) val g2d = graphics as Graphics2D g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE) g2d.stroke = stroke g2d.color = color val pointerCounter = Counter() for ((source, pointer) in pointers.entries) { val place = pointerCounter.count(pointer) val obj = pointer.referenced val offset = if (obj.isSentinel()) obj.minus(1).offset else obj.offset objects[obj.segment]!![offset][obj.type.unqualified()]?.let { target -> val sourcePos = SwingUtilities.convertPoint(source, 0, 0, this) val targetPos = SwingUtilities.convertPoint(target, 0, 0, this) val x1 = sourcePos.x + source.width / 2 val y1 = sourcePos.y + source.height / 2 val x2 = targetPos.x + if (obj.isSentinel()) target.width else lerp(source.width, 0.5.pow(place * 0.5), 16) var y2 = targetPos.y + 8 if (y2 < y1) { y2 = targetPos.y + target.height - 8 } drawPointer(g2d, x1.toDouble(), y1.toDouble(), x2.toDouble(), y2.toDouble()) } } } private fun lerp(zero: Int, x: Double, one: Int): Int = (zero.toDouble() * (1 - x) + one.toDouble() * x).toInt() private fun drawPointer(g2d: Graphics2D, x1: Double, y1: Double, x2: Double, y2: Double) { var deltaX = x2 - x1 var deltaY = y2 - y1 if (deltaY > 0) { deltaX = -deltaX deltaY = -deltaY } val controlX = (x1 + x2 - deltaY) * 0.5 val controlY = (y1 + y2 + deltaX) * 0.5 g2d.draw(QuadCurve2D.Double(x1, y1, controlX, controlY, x2, y2)) deltaX = controlX - x2 deltaY = controlY - y2 val scaleFactor = 8.0 / sqrt((deltaX * deltaX + deltaY * deltaY) * 2) deltaX *= scaleFactor deltaY *= scaleFactor g2d.draw(Line2D.Double(x2, y2, x2 + (deltaX - deltaY), y2 + (deltaY + deltaX))) g2d.draw(Line2D.Double(x2, y2, x2 + (deltaX + deltaY), y2 + (deltaY - deltaX))) } private val stroke = BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND) private val color = Color(1.0f, 0.5f, 0f, 0.75f) } ================================================ FILE: src/test/kotlin/interpreter/InterpreterTest.kt ================================================ package interpreter import org.junit.jupiter.api.Test class InterpreterTest { private fun run(program: String) { Interpreter(program).run(program.length, program.length) } @Test fun sum() { run( """ int sum(int a, int b) { return a + b; } int main() { assert sum(1, 2) == 3; return 0; } """ ) } @Test fun fak() { run( """ int fak(int n) { int result; if (n == 0) { result = 1; } else { result = fak(n - 1); result = n * result; } return result; } int main() { assert fak(5) == 120; return 0; } """ ) } @Test fun passingStrategies() { run( """ int pass(int i, int * p) { i = 3; *p = 4; return i; } int main() { int a = 1; int b = 2; int c = pass(a, &b); assert a == 1; assert b == 4; assert c == 3; return 0; } """ ) } @Test fun doubleResultType() { run( """ double the_answer() { return 42.5; } int main() { assert the_answer() == 42.5; return 0; } """ ) } @Test fun voidReturnType() { run( """ void swap(int * p, int * q) { int c = *p; *p = *q; *q = c; } int main() { int i = 1; int k = 2; swap(&i, &k); assert i == 2; assert k == 1; return 0; } """ ) } @Test fun multiDeclaration() { run( """ int main() { int *a, b, **c; a = &b; c = &a; return 0; } """ ) } @Test fun arrayAccess() { run( """ int main() { int a[2][3]; a[0][0] = 2; a[0][1] = 3; a[0][2] = 5; a[1][0] = 7; a[1][1] = 11; a[1][2] = 13; assert a[0][0] == 2; assert a[0][1] == 3; assert a[0][2] == 5; assert a[1][0] == 7; assert a[1][1] == 11; assert a[1][2] == 13; return 0; } """ ) } @Test fun decay1D() { run( """ int main() { int a[2][3]; a[1][2] = 42; int * p = a[1]; assert p[2] == 42; return 0; } """ ) } @Test fun decay2D() { run( """ int main() { int a[2][3]; a[1][2] = 42; int (*p)[3] = a; assert p[1][2] == 42; return 0; } """ ) } @Test fun pointerArithmetic() { run( """ int main() { int a[9]; int * p = a + 2; int * q = &a[9] - 4; assert q - p == 3; return 0; } """ ) } @Test fun charConstants() { run( """ int main() { char a = 'a'; assert 'z' - a == 25; return 0; } """ ) } @Test fun latin1() { run( """ int main() { char x = 'ß'; int i = x; assert x == -33; assert i == -33; char a[] = "äöüÄÖÜß"; assert a[0] == -28; assert a[1] == -10; assert a[2] == -4; assert a[3] == -60; assert a[4] == -42; assert a[5] == -36; assert a[6] == -33; char * s = "äöüÄÖÜß"; assert s[0] == -28; assert s[1] == -10; assert s[2] == -4; assert s[3] == -60; assert s[4] == -42; assert s[5] == -36; assert s[6] == -33; return 0; } """ ) } @Test fun sumOfFirstTenNumbers() { run( """ int main() { int sum = 0; for (int i = 1; i <= 10; ++i) { sum = sum + i; } assert sum == 55; return 0; } """ ) } @Test fun forIntInt() { run( """ int main() { int sum = 0; for (int a = 1, b = 9; a <= b; ++a, --b) { sum += a * b; } assert sum == 1*9 + 2*8 + 3*7 + 4*6 + 5*5; return 0; } """ ) } @Test fun forIntChar() { run( """ int main() { char a[7]; for (struct {int i; char c;} x = {0, 'A'}; x.i < sizeof a; ++x.i, ++x.c) { a[x.i] = x.c; } assert a[0] == 'A'; assert a[1] == 'B'; assert a[2] == 'C'; assert a[3] == 'D'; assert a[4] == 'E'; assert a[5] == 'F'; assert a[6] == 'G'; return 0; } """ ) } @Test fun forStatic() { run( """ void fill(char * p, char * q) { for (static char c = 'A'; p < q; ++p) { *p = c++; } } int main() { char a[7]; fill(a + 0, a + 1); fill(a + 1, a + 3); fill(a + 3, a + 7); assert a[0] == 'A'; assert a[1] == 'B'; assert a[2] == 'C'; assert a[3] == 'D'; assert a[4] == 'E'; assert a[5] == 'F'; assert a[6] == 'G'; return 0; } """ ) } @Test fun forTypedef() { run( """ int main() { char a[5]; int i = 0; for (typedef const char * str; i < sizeof a; ++i) { str p = "world" + i; a[i] = *p; } assert a[0] == 'w'; assert a[1] == 'o'; assert a[2] == 'r'; assert a[3] == 'l'; assert a[4] == 'd'; return 0; } """ ) } @Test fun forScopes() { run( """ int main() { int i = 1; assert i == 1; for (int i = 2; ; ) { assert i == 2; int i = 3; assert i == 3; break; } assert i == 1; return 0; } """ ) } @Test fun gcd() { run( """int gcd(int x, int y) { while (y) { int z = x % y; x = y; y = z; } return x; } int main() { assert gcd(100, 24) == 4; assert gcd(90, 120) == 30; assert gcd(13, 99) == 1; return 0; } """ ) } @Test fun twoDimensionalPrimes() { run( """ int main() { int primes[2][4] = {{2, 3, 5, 7}, {11, 13, 17, 19}}; assert primes[0][0] == 2; assert primes[0][1] == 3; assert primes[0][2] == 5; assert primes[0][3] == 7; assert primes[1][0] == 11; assert primes[1][1] == 13; assert primes[1][2] == 17; assert primes[1][3] == 19; return 0; } """ ) } @Test fun incompleteArrayParameter() { run( """ int sum(int a[], int n) { int s = 0; int i; for (i = 0; i < n; ++i) { s = s + a[i]; } return s; } int main() { int primes[] = {2, 3, 5, 7, 11, 13, 17, 19}; assert sum(primes, 8) == 77; return 0; } """ ) } @Test fun bogusSizeArrayParameter() { run( """ int sum(int a[5], int n) { int sum = 0; int i; for (i = 0; i < n; ++i) { sum = sum + a[i]; } return sum; } int main() { int primes[] = {2, 3, 5, 7, 11, 13, 17, 19}; assert sum(primes, 8) == 77; return 0; } """ ) } @Test fun pointerIncrement() { run( """ int main() { char * a = "123"; char * b = a++; char * c = ++a; assert *a == '3'; assert *b == '1'; assert *c == '3'; return 0; } """ ) } @Test fun pointerDecrement() { run( """ int main() { char * a = "123" + 2; char * b = a--; char * c = --a; assert *a == '1'; assert *b == '3'; assert *c == '1'; return 0; } """ ) } @Test fun relationalAndEqualityOnPointers() { run( """ int main() { char * a = "hello"; char * b = a; assert a == b; assert a <= b; assert b >= a; ++b; assert a != b; assert a < b; assert a <= b; assert b > a; assert b >= a; return 0; } """ ) } @Test fun sizeof() { run( """ int main() { char c; assert sizeof c == 1; int i; assert sizeof i == 4; unsigned u; assert sizeof u == 4; float f; assert sizeof f == 4; double d; assert sizeof d == 8; int a[6]; assert sizeof a == 24; return 0; } """ ) } @Test fun sizeofType() { run( """ int main() { assert sizeof(char) == 1; assert sizeof(int) == 4; assert sizeof(unsigned) == 4; assert sizeof(float) == 4; assert sizeof(double) == 8; assert sizeof(int[6]) == 24; return 0; } """ ) } @Test fun selfReferentialDeclaration() { run( """ int main() { char x; { int x = sizeof x; assert x == sizeof x; } return 0; } """ ) } @Test fun sizeOfParen() { run( """ int main() { assert sizeof("hello" + 2)[3] == 1; return 0; } """ ) } @Test fun parenthesizedExpression() { run( """ int main() { assert 1 + 2 * 3 + 4 == 11; assert (1 + 2) * (3 + 4) == 21; return 0; } """ ) } @Test fun comma() { run( """ int main() { int a; int b; int c; assert (a = 2, b = 3, c = 5) == 5; assert a == 2; assert b == 3; assert c == 5; return 0; } """ ) } @Test fun logicalNot() { run( """ int main() { assert !0 == 1; assert !1 == 0; assert !2 == 0; return 0; } """ ) } @Test fun logicalAnd() { run( """ int main() { assert (0 && 0) == 0; assert (0 && 1) == 0; assert (1 && 0) == 0; assert (1 && 1) == 1; return 0; } """ ) } @Test fun logicalOr() { run( """ int main() { assert (0 || 0) == 0; assert (0 || 1) == 1; assert (1 || 0) == 1; assert (1 || 1) == 1; return 0; } """ ) } @Test fun voidPointers() { run( """ int main() { int x; int * a = &x; void * b = a; int * c = b; assert a == b; assert b == c; assert a == c; return 0; } """ ) } @Test fun plusMinusAssignment() { run( """ int main() { int sum = 0; int i; for (i = 9; i >= 0; i -= 2) { sum += i; } assert sum == 9 + 7 + 5 + 3 + 1; return 0; } """ ) } @Test fun plusAssignmentConst() { run( """ int main() { int a = 1; const int b = 2; a += b; // failed to typecheck assert(a == 3); return 0; } """ ) } @Test fun bitwiseAnd() { run( """ int main() { assert (1103341801 & 630371112) == 25337896; return 0; } """ ) } @Test fun bitwiseXor() { run( """ int main() { assert (1103341801 ^ 630371112) == 1683037121; return 0; } """ ) } @Test fun bitwiseOr() { run( """ int main() { assert (1103341801 | 630371112) == 1708375017; return 0; } """ ) } @Test fun doWhile() { run( """ int main() { int i = 8; do { ++i; } while (i & (i - 1)); assert i == 16; return 0; } """ ) } @Test fun functionReturningPointerToArray() { run( """ char (*f())[6] { return &"hello"; } int main() { char (*p)[6] = f(); assert p[0][0] == 'h'; return 0; } """ ) } @Test fun rand() { run( """ unsigned random; void srand(unsigned seed) { random = seed; } int rand() { random = random * 214013 + 2531011; return random * 2 / 131072; } int main() { srand(0); assert rand() == 38; assert rand() == 7719; assert rand() == 21238; assert rand() == 2437; assert rand() == 8855; assert rand() == 11797; assert rand() == 8365; assert rand() == 32285; assert rand() == 10450; assert rand() == 30612; return 0; } """ ) } @Test fun functionPrototype() { run( """ int square(int x); int main() { assert square(9) == 81; return 0; } int square(int x) { return x * x; } """ ) } @Test fun passStructByReference() { run( """ struct Point { int x, y; int z; }; void add(struct Point * a, struct Point * b, struct Point * s) { s->x = a->x + b->x; s->y = a->y + b->y; s->z = a->z + b->z; } int main() { struct Point p = {2, 3, 5}; struct Point q = {7, 11, 13}; struct Point r; add(&p, &q, &r); assert r.x == 9; assert r.y == 14; assert r.z == 18; return 0; } """ ) } @Test fun passStructByValue() { run( """ struct Point { int x; int y, z; }; void add(struct Point * dst, struct Point src) { dst->x += src.x; dst->y += src.y; dst->z += src.z; // modifying src should have no effect ++src.x; ++src.y; ++src.z; } int main() { struct Point a = {2, 3, 5}; struct Point b = {7, 11, 13}; add(&a, b); assert a.x == 9; assert a.y == 14; assert a.z == 18; assert b.x == 7; assert b.y == 11; assert b.z == 13; return 0; } """ ) } @Test fun assignStruct() { run( """ struct Point { int x, y, z; }; int main() { struct Point a = {2, 3, 5}; const struct Point b = a; struct Point c; c = b; assert a.x == 2; assert a.y == 3; assert a.z == 5; assert b.x == 2; assert b.y == 3; assert b.z == 5; assert c.x == 2; assert c.y == 3; assert c.z == 5; return 0; } """ ) } @Test fun initializeStructInArray() { run( """ struct Point { int x, y, z; }; int main() { const struct Point b = {7, 11, 13}; struct Point d = {29, 31, 37}; const struct Point a[] = {{2, 3, 5}, b, {17, 19, 23}, d}; assert a[0].x == 2; assert a[0].y == 3; assert a[0].z == 5; assert a[1].x == 7; assert a[1].y == 11; assert a[1].z == 13; assert a[2].x == 17; assert a[2].y == 19; assert a[2].z == 23; assert a[3].x == 29; assert a[3].y == 31; assert a[3].z == 37; return 0; } """ ) } @Test fun passFunctionPointer() { run( """ int twice(int (*f)(int x), int x) { return f(f(x)); } int square(int x) { return x * x; } int main() { assert twice(square, 3) == 81; return 0; } """ ) } @Test fun indirectMemberAccessViaArray() { run( """ int main() { struct Point { int x, y; } a[1]; a->x = 1; a->y = 2; assert a[0].x == 1; assert a[0].y == 2; return 0; } """ ) } @Test fun unaryPlus() { run( """ int abs(int x) { if (x < 0) return -x; else return +x; } int main() { assert abs(+42) == 42; assert abs(-42) == 42; return 0; } """ ) } @Test fun conditionalArithmetic() { run( """ int abs(int x) { return (x < 0) ? -x : +x; } int main() { assert abs(+42) == 42; assert abs(-42) == 42; return 0; } """ ) } @Test fun conditionalMixedArithmetic() { run( """ int main() { double lossy = 1 ? 1234567890 : 3.14f; assert lossy == 1234567936; return 0; } """ ) } @Test fun conditionalVoid() { run( """ char x; void True() { x = 't'; } void False() { x = 'f'; } int main() { 0 ? True() : False(); assert x == 'f'; 1 ? True() : False(); assert x == 't'; return 0; } """ ) } @Test fun conditionalPointers() { run( """ int * minimum(int * p, int * q) { return (*q < *p) ? q : p; } int main() { int a[] = {2, 3}; *minimum(a, a + 1) = 5; *minimum(a, a + 1) = 7; assert a[0] == 5; assert a[1] == 7; return 0; } """ ) } @Test fun conditionalVoidPointers() { run( """ int main() { int * a = malloc(sizeof(int)); void * p = a; int * b = malloc(sizeof(int)); void * q = b; free(1 ? p : q); free(0 ? p : q); return 0; } """ ) } @Test fun conditionalMixedVoidPointers1() { run( """ int main() { int * a = malloc(sizeof(int)); void * p = a; int * q = malloc(sizeof(int)); free(1 ? p : q); free(0 ? p : q); return 0; } """ ) } @Test fun conditionalMixedVoidPointers2() { run( """ int main() { int * p = malloc(sizeof(int)); int * b = malloc(sizeof(int)); void * q = b; free(1 ? p : q); free(0 ? p : q); return 0; } """ ) } @Test fun conditionalChained() { run( """ int signum(int x) { return x < 0 ? -1 : x > 0 ? +1 : +-0; } int main() { assert signum(-42) == -1; assert signum(000) == 00; assert signum(+42) == +1; return 0; } """ ) } @Test fun forOptional1() { run( """ int main() { int sum = 0; int i; for (i = 1; i <= 10; ) { sum += i; ++i; } assert sum == 55; return 0; } """ ) } @Test fun forOptional2() { run( """ int main() { int sum = 0; int i; for (i = 1; ; ++i) { if (!(i <= 10)) { assert sum == 55; return 0; } sum += i; } } """ ) } @Test fun forOptional3() { run( """ int main() { int sum = 0; int i; for (i = 1; ; ) { if (!(i <= 10)) { assert sum == 55; return 0; } sum += i; ++i; } } """ ) } @Test fun forOptional4() { run( """ int main() { int sum = 0; int i = 1; for (; i <= 10; ++i) { sum += i; } assert sum == 55; return 0; } """ ) } @Test fun forOptional5() { run( """ int main() { int sum = 0; int i = 1; for (; i <= 10; ) { sum += i; ++i; } assert sum == 55; return 0; } """ ) } @Test fun forOptional6() { run( """ int main() { int sum = 0; int i = 1; for (; ; ++i) { if (!(i <= 10)) { assert sum == 55; return 0; } sum += i; } } """ ) } @Test fun forOptional7() { run( """ int main() { int sum = 0; int i = 1; for (; ; ) { if (!(i <= 10)) { assert sum == 55; return 0; } sum += i; ++i; } } """ ) } @Test fun mallocSingleElement() { run( """ int main() { int * p = malloc(4); *p = 42; free(p); return 0; } """ ) } @Test fun mallocArray() { run( """ int main() { int * p = malloc(12); p[0] = 2; p[1] = 3; p[2] = 5; free(p); return 0; } """ ) } @Test fun passMallocResultToFunction() { run( """ int * foo(int * p) { *p = 42; return p; } int main() { free(foo(malloc(4))); return 0; } """ ) } @Test fun typedefScopes() { run( """ typedef int X; X main() { X a = 42; { X Y = 3; X X = 2; X * Y; } X * Y; Y = &a; char typedef X; X * Z; Z = "hello"; return 0; } """ ) } @Test fun staticLocalCounter() { run( """ int a; int id() { int b; static int counter = 0; int c; return counter++; } int d; int main() { int e; assert id() == 0; assert id() == 1; assert id() == 2; return 0; } """ ) } @Test fun staticLocalStringArray() { run( """ char * weekday(int n) { static char * table[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; return table[n]; } int main() { assert weekday(0) == "Sunday"; assert weekday(6) == "Saturday"; return 0; } """ ) } @Test fun layoutForStaticArrayWithDeducedSize() { run( """ int a[] = {1, 2, 3}; int x = 4; int main() { assert a[0] == 1; return 0; } """ ) } @Test fun signedCharDeclarations() { run( """ int main() { signed char a = -1; assert a < 0; assert sizeof(a) == 1; char signed b = -1; assert b < 0; assert sizeof(b) == 1; return 0; } """ ) } @Test fun unsignedCharDeclarations() { run( """ int main() { unsigned char a = -1; assert a > 0; assert sizeof(a) == 1; char unsigned b = -1; assert b > 0; assert sizeof(b) == 1; return 0; } """ ) } @Test fun signedShortDeclarations() { run( """ int main() { short a = -1; assert a < 0; assert sizeof(a) == 2; short int b = -1; assert b < 0; assert sizeof(b) == 2; int short c = -1; assert c < 0; assert sizeof(c) == 2; signed short d = -1; assert d < 0; assert sizeof(d) == 2; short signed e = -1; assert e < 0; assert sizeof(e) == 2; signed short int f = -1; assert f < 0; assert sizeof(f) == 2; signed int short g = -1; assert g < 0; assert sizeof(g) == 2; short signed int h = -1; assert h < 0; assert sizeof(h) == 2; short int signed i = -1; assert i < 0; assert sizeof(i) == 2; int signed short j = -1; assert j < 0; assert sizeof(j) == 2; int short signed k = -1; assert k < 0; assert sizeof(k) == 2; return 0; } """ ) } @Test fun unsignedShortDeclarations() { run( """ int main() { unsigned short d = -1; assert d > 0; assert sizeof(d) == 2; short unsigned e = -1; assert e > 0; assert sizeof(e) == 2; unsigned short int f = -1; assert f > 0; assert sizeof(f) == 2; unsigned int short g = -1; assert g > 0; assert sizeof(g) == 2; short unsigned int h = -1; assert h > 0; assert sizeof(h) == 2; short int unsigned i = -1; assert i > 0; assert sizeof(i) == 2; int unsigned short j = -1; assert j > 0; assert sizeof(j) == 2; int short unsigned k = -1; assert k > 0; assert sizeof(k) == 2; return 0; } """ ) } @Test fun signedIntDeclarations() { run( """ int main() { int a = -1; assert a < 0; assert sizeof(a) == 4; signed b = -1; assert b < 0; assert sizeof(b) == 4; signed int d = -1; assert d < 0; assert sizeof(d) == 4; int signed e = -1; assert e < 0; assert sizeof(e) == 4; return 0; } """ ) } @Test fun unsignedIntDeclarations() { run( """ int main() { unsigned b = -1; assert b > 0; assert sizeof(b) == 4; unsigned int d = -1; assert d > 0; assert sizeof(d) == 4; int unsigned e = -1; assert e > 0; assert sizeof(e) == 4; return 0; } """ ) } @Test fun typedefAnonymousStruct() { run( """ typedef struct { int x, y; int z; } Point; void add(Point * a, Point * b, Point * s) { s->x = a->x + b->x; s->y = a->y + b->y; s->z = a->z + b->z; } int main() { Point p = {2, 3, 5}; Point q = {7, 11, 13}; Point r; add(&p, &q, &r); assert r.x == 9; assert r.y == 14; assert r.z == 18; return 0; } """ ) } @Test fun multipleAnonymousStructs() { run( """ typedef struct { int x, y; } Point2D; typedef struct { int x, y; int z; } Point3D; int main() { return 0; } """ ) } @Test fun missingParameterNames() { run( """ int square(int); int twice(int(int), int); int main() { assert twice(square, 3) == 81; return 0; } int twice(int f(int), int x) { return f(f(x)); } int square(int x) { return x * x; } """ ) } @Test fun trailingCommaInArrayInInitializerList() { run( """ int main() { int a[] = { 2, 3, 5, 7, }; assert sizeof a == 16; return 0; } """ ) } @Test fun anonymousFunctionParameter() { run( """ int foo(int (x)); typedef int x; int bar(int (x)); int main() { sizeof foo(42); sizeof bar(foo); return 0; } """ ) } @Test fun parameterShadowsGlobalTypedef() { run( """ typedef int x; void foo(int x) { int y = 1; // The following line is now parsed as a multiplication, // because x is the int parameter, not the global typedef. x * y; // Previously, this was a duplicate definition of y (as a pointer to int). } int main() { return 0; } """ ) } @Test fun parameterScopeIsClosed() { run( """ typedef int x; x a; void foo(int x); x b; void foo(int x) { } x c; int main() { return 0; } """ ) } @Test fun signedLongDeclarations() { run( """ int main() { long a = -1; assert a < 0; assert sizeof(a) == 4; long int b = -1; assert b < 0; assert sizeof(b) == 4; int long c = -1; assert c < 0; assert sizeof(c) == 4; signed long d = -1; assert d < 0; assert sizeof(d) == 4; long signed e = -1; assert e < 0; assert sizeof(e) == 4; signed long int f = -1; assert f < 0; assert sizeof(f) == 4; signed int long g = -1; assert g < 0; assert sizeof(g) == 4; long signed int h = -1; assert h < 0; assert sizeof(h) == 4; long int signed i = -1; assert i < 0; assert sizeof(i) == 4; int signed long j = -1; assert j < 0; assert sizeof(j) == 4; int long signed k = -1; assert k < 0; assert sizeof(k) == 4; return 0; } """ ) } @Test fun unsignedLongDeclarations() { run( """ int main() { unsigned long d = -1; assert d > 0; assert sizeof(d) == 4; long unsigned e = -1; assert e > 0; assert sizeof(e) == 4; unsigned long int f = -1; assert f > 0; assert sizeof(f) == 4; unsigned int long g = -1; assert g > 0; assert sizeof(g) == 4; long unsigned int h = -1; assert h > 0; assert sizeof(h) == 4; long int unsigned i = -1; assert i > 0; assert sizeof(i) == 4; int unsigned long j = -1; assert j > 0; assert sizeof(j) == 4; int long unsigned k = -1; assert k > 0; assert sizeof(k) == 4; return 0; } """ ) } @Test fun arraySizeConstantExpression() { run( """ int main() { short a[3 * 7]; assert sizeof a == 42; short b[sizeof a / 2]; assert sizeof b == 42; return 0; } """ ) } @Test fun octalSingleDigit() { run( """ int main() { assert 00 == 0; assert 01 == 1; assert 02 == 2; assert 03 == 3; assert 04 == 4; assert 05 == 5; assert 06 == 6; assert 07 == 7; return 0; } """ ) } @Test fun octalAllDigits() { run( """ int main() { assert 01234567 == 342391; return 0; } """ ) } @Test fun nonOctalFloat() { run( """ int main() { assert 010f == 10; assert 08f == 8; assert 09f == 9; return 0; } """ ) } @Test fun nonOctalDouble() { run( """ int main() { assert 010. == 10; assert 08. == 8; assert 09. == 9; return 0; } """ ) } @Test fun hexSingleDigit() { run( """ int main() { assert 0x0 == 0; assert 0x1 == 1; assert 0x2 == 2; assert 0x3 == 3; assert 0x4 == 4; assert 0x5 == 5; assert 0x6 == 6; assert 0x7 == 7; assert 0x8 == 8; assert 0x9 == 9; assert 0xA == 10; assert 0xB == 11; assert 0xC == 12; assert 0xD == 13; assert 0xE == 14; assert 0xF == 15; assert 0xa == 10; assert 0xb == 11; assert 0xc == 12; assert 0xd == 13; assert 0xe == 14; assert 0xf == 15; return 0; } """ ) } @Test fun hexAllDigits() { run( """ int main() { assert 0x01234567 == 19088743; assert 0x89abcdef == 2309737967; assert 0x89ABCDEF == 2309737967; return 0; } """ ) } @Test fun binaryLiterals() { run( """ int main() { assert 0b0 == 0; assert 0b1 == 1; assert 0b10 == 2; assert 0b11 == 3; assert 0b100 == 4; assert 0b101 == 5; assert 0b110 == 6; assert 0b111 == 7; assert 0b1000 == 8; assert 0b1001 == 9; assert 0b1010 == 10; assert 0b1011 == 11; assert 0b1100 == 12; assert 0b1101 == 13; assert 0b1110 == 14; assert 0b1111 == 15; assert 0b01001001100101100000001011010010 == 1234567890; assert 0b10001011110100000011100000110101 == 2345678901; return 0; } """ ) } @Test fun localFunctionPrototype() { run( """ int main() { int square(int); assert square(9) == 81; return 0; } int square(int x) { return x * x; } """ ) } @Test fun enumUninitialized() { run( """ enum dir { north, east, south, west } sun = east; int main() { enum dir x = 0; assert x == north; ++x; assert x == sun; ++x; assert x == south; ++x; assert x == west; return 0; } """ ) } @Test fun enumInitialized() { run( """ enum { Y = 6 }; int main() { enum { X = 7 }; char a[Y][X]; assert sizeof a == 42; return 0; } """ ) } @Test fun enumMixed() { run( """ enum numbers { a = 4, b = 8, c = 15, d, e = 23, f = 42 }; int main() { assert a == 4; assert b == 8; assert c == 15; assert d == 16; assert e == 23; assert f == 42; return 0; } """ ) } @Test fun staticInitializationArithmetic() { run( """ char a; unsigned char b; short c; unsigned short d; int e; unsigned int f; float g; double h; int main() { assert a == 0; assert b == 0; assert c == 0; assert d == 0; assert e == 0; assert f == 0; assert g == 0; assert h == 0; return 0; } """ ) } @Test fun staticInitializationArray() { run( """ int a[5]; int main() { assert a[0] == 0; assert a[1] == 0; assert a[2] == 0; assert a[3] == 0; assert a[4] == 0; return 0; } """ ) } @Test fun staticInitializationStruct() { run( """ struct Point { int x, y, z; } p; int main() { assert p.x == 0; assert p.y == 0; assert p.z == 0; return 0; } """ ) } @Test fun staticInitializationNested() { run( """ struct Person { char name[20]; int age; } a[2]; int main() { assert a[0].name[0] == 0; assert a[0].name[19] == 0; assert a[0].age == 0; assert a[1].name[0] == 0; assert a[1].name[19] == 0; assert a[1].age == 0; return 0; } """ ) } @Test fun staticInitializationLocal() { run( """ int a; int main() { int b = 42; static int c; assert a == 0; assert c == 0; return 0; } """ ) } @Test fun functionPointersAreConstantExpressions() { run( """ int square(int x) { return x * x; } int (*fp)(int) = □ int main() { assert fp(3) == 9; return 0; } """ ) } @Test fun signedUnsignedComparisons() { run( """ int main() { assert -1 == 0xffffffff; unsigned z = 0; assert z < -1; assert z <= -1; assert -1 > z; assert -1 >= z; return 0; } """ ) } @Test fun realloc() { run( """ int main() { int * p = malloc(12); p[0] = 2; p[1] = 3; p[2] = 5; p = realloc(p, 16); p[3] = 7; assert p[0] == 2; assert p[1] == 3; assert p[2] == 5; assert p[3] == 7; free(p); return 0; } """ ) } @Test fun simpleContinue() { run( """ int main() { int x = 0; int i = 0; for (i = 0; i <= 12; ++i) { if (i % 2 == 0) continue; if (i % 3 == 0) continue; x += i; } assert(x == 1+5+7+11); return 0; } """ ) } @Test fun innerContinue() { run( """ int main() { int n = 0; int i = 0; for (i = 1; i <= 10; ++i) { int j = 0; for (j = 1; j <= 10; ++j) { if (i == j) continue; ++n; } } assert(n == 100 - 10); return 0; } """ ) } @Test fun outerContinue() { run( """ int main() { int n = 0; int i = 0; for (i = 1; i <= 10; ++i) { if (i % 2 == 0) continue; while (0) { --i; } ++n; } assert(n == 5); return 0; } """ ) } @Test fun simpleBreak() { run( """ int main() { int n = 0; int i = 0; for (i = 1; i <= 10; ++i) { ++n; if (i == 5) break; ++n; } assert(n == 5 + 4); return 0; } """ ) } @Test fun simpleGoto() { run( """ int main() { int x = 0; int y = 0; int z = 0; ++x; goto increment_z; ++y; increment_z: ++z; assert x == 1; assert y == 0; assert z == 1; return 0; } """ ) } @Test fun simulateDoWhileWithGoto() { run( """ int main() { int sum = 0; int i = 1; loop: sum += i; ++i; if (i <= 10) goto loop; assert sum == 55; return 0; } """ ) } @Test fun fallthrough() { run( """ int log10(int x) { int log = 0; switch (x) { case 1000000000: ++log; case 100000000: ++log; case 10000000: ++log; case 1000000: ++log; case 100000: ++log; case 10000: ++log; case 1000: ++log; case 100: ++log; case 10: ++log; case 1: return log; default: return -1; } } int main() { assert 0 == log10(1); assert 1 == log10(10); assert 2 == log10(100); assert 3 == log10(1000); assert 4 == log10(10000); assert 5 == log10(100000); assert 6 == log10(1000000); assert 7 == log10(10000000); assert 8 == log10(100000000); assert 9 == log10(1000000000); return 0; } """ ) } @Test fun defaultAboveOthers() { run( """ int isPrime(int x) { switch (x) { default: return 0; case 2: case 3: case 5: case 7: return 1; } } int main() { assert!isPrime(0); assert!isPrime(1); assert isPrime(2); assert isPrime(3); assert!isPrime(4); assert isPrime(5); assert!isPrime(6); assert isPrime(7); assert!isPrime(8); assert!isPrime(9); return 0; } """ ) } @Test fun missingBreak() { run( """ const char * color(int x) { const char * result = ""; switch (x) { case 0: result = "red"; break; case 1: result = "green"; // missing break case 2: result = "blue"; break; } return result; } int main() { assert color(0) == "red"; assert color(1) == "blue"; // due to missing break assert color(2) == "blue"; assert color(3) == ""; return 0; } """ ) } @Test fun duffsDevice() { run( """ void duffsDevice(char * dst, const char * src, unsigned n) { unsigned rest = n % 8; n = n / 8; switch (rest) { case 0: do { *dst++ = *src++; case 7: *dst++ = *src++; case 6: *dst++ = *src++; case 5: *dst++ = *src++; case 4: *dst++ = *src++; case 3: *dst++ = *src++; case 2: *dst++ = *src++; case 1: *dst++ = *src++; } while (n--); } } int main() { char a[] = "................"; duffsDevice(a, "0123456789", 10); assert a[0] == '0'; assert a[1] == '1'; assert a[2] == '2'; assert a[3] == '3'; assert a[4] == '4'; assert a[5] == '5'; assert a[6] == '6'; assert a[7] == '7'; assert a[8] == '8'; assert a[9] == '9'; assert a[10] == '.'; return 0; } """ ) } @Test fun bitwiseNot() { run( """ int main() { int i = 0; i = ~i; assert i == -1; unsigned u = 0; u = ~u; assert u == 4294967295; assert sizeof(~'a') == sizeof(int); assert ~'a' == -98; return 0; } """ ) } @Test fun signedShift() { run( """ int main() { int x = 5; x = x << 3; assert x == 40; x = x >> 2; assert x == 10; assert -1 >> 1 == -1; return 0; } """ ) } @Test fun unsignedShift() { run( """ int main() { assert 0x80000000 >> 1 == 0x40000000; assert 0x80000000 << 1 == 0; return 0; } """ ) } @Test fun voidParameterList() { run( """ int main(void) { assert sizeof main() == sizeof(int); return 0; } """ ) } @Test fun bsearchPresentElements() { run( """ int less(const void * x, const void * y) { const int * p = x; const int * q = y; int result = (*p < *q) ? -1 : (*p > *q); return result; } int main(void) { int a[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; int i; for (i = 0; i < 10; ++i) { assert bsearch(a + i, a, 10, 4, less) == a + i; } return 0; } """ ) } @Test fun bsearchAbsentElements() { run( """ int less(const void * x, const void * y) { const int * p = x; const int * q = y; int result = (*p < *q) ? -1 : (*p > *q); return result; } int main(void) { int a[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; int b[] = {0, 1, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30}; int i; for (i = 0; i < 21; ++i) { assert bsearch(b + i, a, 10, 4, less) == a + 10; } return 0; } """ ) } @Test fun localTypedefBackReference() { run( """ int main() { typedef double a, b, c[sizeof(a)], d[sizeof(b)]; assert sizeof(a) == 8; assert sizeof(b) == 8; assert sizeof(c) == 64; assert sizeof(d) == 64; return 0; } """ ) } @Test fun globalTypedefBackReference() { run( """ typedef double a, b, c[sizeof(a)], d[sizeof(b)]; int main() { assert sizeof(a) == 8; assert sizeof(b) == 8; assert sizeof(c) == 64; assert sizeof(d) == 64; return 0; } """ ) } @Test fun postfixAfterSizeof() { run( """ int main() { char x = 'a'; assert sizeof(x)++ == 1; assert sizeof x ++ == 1; return 0; } """ ) } @Test fun conditionalBindsStrongerThanComma() { run( """ int main() { assert (1 ? 2 : 3, 4) == 4; return 0; } """ ) } @Test fun initEnumeratorWithSizeof() { run( """ enum { N = sizeof(int) }; int main() { assert N == 4; return 0; } """ ) } @Test fun castConstVoidPointers() { run( """ int fruit_compare(const void * const v, const void * w) { return *(const char * const *)v - *(const char * const * const)w; } int main() { typedef const char * string; string a = "apple", b = "banana", c = "cherry", d = "date", e = "eggfruit", f = "fig", g = "grape"; string fruits[] = {g, c, b, f, d, e, a}; qsort(fruits, sizeof fruits/sizeof*fruits, sizeof*fruits, fruit_compare); assert fruits[0] == a; assert fruits[1] == b; assert fruits[2] == c; assert fruits[3] == d; assert fruits[4] == e; assert fruits[5] == f; assert fruits[6] == g; return 0; } """ ) } @Test fun castIntToDouble() { run( """ int main() { assert (double)1/2 == 0.5; int i = 1; assert (const double)i++/2 == 0.5; assert i == 2; return 0; } """ ) } @Test fun castArraySize() { run( """ int main() { char pi[(int)3.1416]; char e[(int)2.71828]; assert sizeof pi == 3; assert sizeof e == 2; return 0; } """ ) } @Test fun castCaseLabels() { run( """ const char * pronounce(int x) { switch (x) { case 1: return "one"; case (int)2.71828: return "two"; case (int)3.1416: return "three"; default: return "???"; } } int main() { assert pronounce(1) == "one"; assert pronounce(2) == "two"; assert pronounce(3) == "three"; assert pronounce(4) == "???"; return 0; } """ ) } @Test fun switchCharCaseInt() { run( """ int main() { const char x = 'A'; switch (x) { case 65: break; default: assert 0; } return 0; } """ ) } @Test fun switchUnsignedCaseInt() { run( """ int main() { switch (0xffffffff) { case -1: break; default: assert 0; } return 0; } """ ) } @Test fun returnEarlyFromVoidFunction() { run( """ int lastSign; void setSign(int x) { if (x < 0) { lastSign = -1; return; } if (x > 0) { lastSign = +1; return; } lastSign = 0; } int sign(int x) { setSign(x); return lastSign; } int main() { assert sign(-42) == -1; assert sign(0) == 0; assert sign(+42) == +1; return 0; } """ ) } @Test fun sameOffsetInDifferentSegments() { run( """ int main() { int * a = malloc(sizeof(int)); int * b = malloc(sizeof(int)); assert a != b; assert !(a == b); free(a); free(b); return 0; } """ ) } @Test fun strlen() { run( """ int main() { assert strlen("") == 0; assert strlen("C") == 1; assert strlen("in") == 2; assert strlen("the") == 3; assert strlen("name") == 4; assert strlen("brand") == 5; return 0; } """ ) } @Test fun strcmp() { run( """ int main() { assert strcmp("apple", "apple") == 0; assert strcmp("apple", "pear") < 0; assert strcmp("pear", "apple") > 0; assert strcmp("apple", "ape") > 0; assert strcmp("ape", "apple") < 0; assert strcmp("apple", "apply") < 0; assert strcmp("apply", "apple") > 0; assert strcmp("apple", "applepie") < 0; assert strcmp("applepie", "apple") > 0; assert strcmp("", "") == 0; assert strcmp("", "a") < 0; assert strcmp("a", "") > 0; return 0; } """ ) } } ================================================ FILE: src/test/kotlin/semantic/types/TypeToStringTest.kt ================================================ package semantic.types import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class TypeToStringTest { @Test fun array() { assertEquals("int[10]", ArrayType(10, SignedIntType).toString()) } @Test fun arrayOfArray() { assertEquals("int[1][2]", ArrayType(1, ArrayType(2, SignedIntType)).toString()) } @Test fun pointer() { assertEquals("int*", PointerType(SignedIntType).toString()) } @Test fun arrayOfPointers() { assertEquals("int*[10]", ArrayType(10, PointerType(SignedIntType)).toString()) } @Test fun pointerToArray() { assertEquals("int(*)[10]", PointerType(ArrayType(10, SignedIntType)).toString()) } @Test fun pointerToPointerToArray() { assertEquals("int(**)[10]", PointerType(PointerType(ArrayType(10, SignedIntType))).toString()) } @Test fun qsort() { val predicate = FunctionType(SignedIntType, ConstVoidPointerType, ConstVoidPointerType).pointer() val qsort = FunctionType(VoidType, VoidPointerType, UnsignedIntType, UnsignedIntType, predicate) assertEquals("void(void*,unsigned int,unsigned int,int(*)(const void*,const void*))", qsort.toString()) } @Test fun bsearch() { val predicate = FunctionType(SignedIntType, ConstVoidPointerType, ConstVoidPointerType).pointer() val bsearch = FunctionType( VoidPointerType, ConstVoidPointerType, ConstVoidPointerType, UnsignedIntType, UnsignedIntType, predicate ) assertEquals( "void*(const void*,const void*,unsigned int,unsigned int,int(*)(const void*,const void*))", bsearch.toString() ) } @Test fun function() { assertEquals("int()", FunctionType(SignedIntType).toString()) } @Test fun functionReturningPointer() { assertEquals("int*()", FunctionType(PointerType(SignedIntType)).toString()) } @Test fun functionReturningPointerToPointer() { assertEquals("int**()", FunctionType(PointerType(PointerType(SignedIntType))).toString()) } @Test fun functionReturningPointerToArray() { assertEquals("int(*())[10]", FunctionType(PointerType(ArrayType(10, SignedIntType))).toString()) } @Test fun functionReturningPointerToFunction() { val callback = PointerType(FunctionType(SignedIntType, SignedIntType, SignedIntType)) assertEquals("int(*(int(*)(int,int)))(int,int)", FunctionType(callback, callback).toString()) } @Test fun constant() { assertEquals("const int", Const(SignedIntType).toString()) } @Test fun arrayOfConst() { assertEquals("const int[10]", ArrayType(10, Const(SignedIntType)).toString()) } @Test fun pointerToConst() { assertEquals("const int*", PointerType(Const(SignedIntType)).toString()) } @Test fun constPointer() { assertEquals("int*const", Const(PointerType(SignedIntType)).toString()) } @Test fun constPointerToConst() { assertEquals("const int*const", Const(PointerType(Const(SignedIntType))).toString()) } @Test fun constPointerToConstPointerToConst() { assertEquals("const int*const*const", Const(PointerType(Const(PointerType(Const(SignedIntType))))).toString()) } @Test fun constPointerToArray() { assertEquals("int(*const)[10]", Const(PointerType(ArrayType(10, SignedIntType))).toString()) } @Test fun constPointerToArrayOfConst() { assertEquals("const int(*const)[10]", Const(PointerType(ArrayType(10, Const(SignedIntType)))).toString()) } @Test fun constPointerToFunction() { assertEquals( "int(*const)(int)", Const(PointerType(FunctionType(SignedIntType, SignedIntType))).toString() ) } @Test fun arrayOfConstPointersToVoid() { assertEquals("void*const[10]", ArrayType(10, Const(VoidPointerType)).toString()) } @Test fun arrayOfConstPointersToConstVoid() { assertEquals("const void*const[10]", ArrayType(10, Const(ConstVoidPointerType)).toString()) } } ================================================ FILE: src/test/kotlin/syntax/lexer/LexerTest.kt ================================================ package syntax.lexer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.Test import syntax.lexer.TokenKind.IDENTIFIER import syntax.lexer.TokenKind.STRING_LITERAL import kotlin.random.Random class LexerTest { private var lexer = Lexer("") @Test fun identifiers() { lexer = Lexer("a z a0 z9 a_z foo _bar the_quick_brown_fox_jumps_over_the_lazy_dog THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG") expectIdentifier("a") expectIdentifier("z") expectIdentifier("a0") expectIdentifier("z9") expectIdentifier("a_z") expectIdentifier("foo") expectIdentifier("_bar") expectIdentifier("the_quick_brown_fox_jumps_over_the_lazy_dog") expectIdentifier("THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG") } @Test fun stringLiterals() { lexer = Lexer( """ "hello" "hi there" "say \"hi\"" "\"please\" is the magic word" "use \\n for a new line" """ ) expectStringLiteral("hello") expectStringLiteral("hi there") expectStringLiteral("""say "hi"""") expectStringLiteral(""""please" is the magic word""") expectStringLiteral("""use \n for a new line""") } @Test fun singleLineComments() { lexer = Lexer( """// comment #1 a // comment #2 // comment #3 b c // comment #4 d// comment #5 e//""" ) expectIdentifier("a") expectIdentifier("b") expectIdentifier("c") expectIdentifier("d") expectIdentifier("e") } @Test fun multiLineComments() { lexer = Lexer( """/* comment #1 */ a /* comment #2 */ b /*/ comment #3*/ c /**/ d/***/ e /* / ** / *** /*/ f /*""" ) expectIdentifier("a") expectIdentifier("b") expectIdentifier("c") expectIdentifier("d") expectIdentifier("e") expectIdentifier("f") } @Test fun keywords() { for (kind in TokenKind.KEYWORDS) { lexemeRoundTrip(kind) } } @Test fun operatorsSeparators() { for (kind in TokenKind.OPERATORS_SEPARATORS) { lexemeRoundTrip(kind) } } private fun lexemeRoundTrip(kindIn: TokenKind) { val lexemeIn = kindIn.lexeme lexer = Lexer(lexemeIn) val kindOut = lexer.nextToken().kind val lexemeOut = kindOut.lexeme assertSame(lexemeIn, lexemeOut) } @Test fun nearKeywords() { for (kind in TokenKind.KEYWORDS) { val keyword = kind.lexeme val init = keyword.substring(0, keyword.length - 1) val last = keyword.last() identifierRoundTrip(init) identifierRoundTrip(init + randomLetterOtherThan(last)) identifierRoundTrip(keyword + randomLetter()) } } private fun identifierRoundTrip(identifier: String) { lexer = Lexer(identifier) expectIdentifier(identifier) } private fun randomLetter(): Char = (Random.nextInt(26) + 97).toChar() private fun randomLetterOtherThan(forbidden: Char): Char { val ch = (Random.nextInt(26 - 1) + 97).toChar() // ch is a random character between a and y. // In case ch is the forbidden character, 'z' is available. // All characters will be chosen with the same probability. return if (ch == forbidden) 'z' else ch } private fun expectIdentifier(identifier: String) { val token = lexer.nextToken() assertEquals(IDENTIFIER, token.kind) assertEquals(identifier, token.text) } private fun expectStringLiteral(stringLiteral: String) { val token = lexer.nextToken() assertEquals(STRING_LITERAL, token.kind) assertEquals(stringLiteral, token.text) } } ================================================ FILE: src/test/kotlin/syntax/parser/AutocompletionTest.kt ================================================ package syntax.parser import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class AutocompletionTest { @Test fun fullSuffix() { val actual = autocompleteIdentifier("int foo, bar, baz; int main() { f") assertEquals(listOf("oo"), actual) } @Test fun partialSuffix() { val actual = autocompleteIdentifier("int foo, bar, baz; int main() { b") assertEquals(listOf("a"), actual) } @Test fun ambiguous() { val actual = autocompleteIdentifier("int foo, bar, baz; int main() { ba") assertEquals(listOf("r", "z"), actual) } @Test fun alreadyComplete() { val actual = autocompleteIdentifier("int foo, bar, baz; int main() { foo") assertEquals(emptyList(), actual) } @Test fun parameter() { val actual = autocompleteIdentifier("double f(double number, double numbest) { num") assertEquals(listOf("be"), actual) } @Test fun local() { val actual = autocompleteIdentifier("double f() { double number, numbest; num") assertEquals(listOf("be"), actual) } @Test fun parameterAndLocal() { val actual = autocompleteIdentifier("double f(double number) { double numbest; num") assertEquals(listOf("be"), actual) } @Test fun globalAndLocal() { val actual = autocompleteIdentifier("double number; double f() { double numbest; num") assertEquals(listOf("be"), actual) } @Test fun nestedLocals() { val actual = autocompleteIdentifier("double f() { double number; { double numbest; num") assertEquals(listOf("be"), actual) } @Test fun outOfScope() { val actual = autocompleteIdentifier("double f() { double number; { double numbest; } num") assertEquals(listOf("ber"), actual) } @Test fun recursion() { val actual = autocompleteIdentifier("void foo() { f") assertEquals(listOf("oo"), actual) } @Test fun backwardCall() { val actual = autocompleteIdentifier("void foo() {} void bar() { f") assertEquals(listOf("oo"), actual) } @Test fun forwardCall() { val actual = autocompleteIdentifier("void foo(); void bar() { f") assertEquals(listOf("oo"), actual) } @Test fun afterMutualRecursion() { val actual = autocompleteIdentifier("void baz(); void bar() { baz(); } void baz() { bar(); } void foo() { b") assertEquals(listOf("a"), actual) } @Test fun enumerationConstant() { val actual = autocompleteIdentifier("enum { WORD_SIZE = sizeof W") assertEquals(listOf("ORD_SIZE"), actual) } @Test fun directStructAccess() { val actual = autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f(struct Person p) { p.n") assertEquals(listOf("ame"), actual) } @Test fun indirectStructAccess() { val actual = autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f(struct Person * p) { p->n") assertEquals(listOf("ame"), actual) } @Test fun ignoreStruct() { val actual = autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f() { n") assertEquals(listOf("oob"), actual) } } ================================================ FILE: src/test/resources/junit-platform.properties ================================================ junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent