Showing preview only (313K chars total). Download the full file or copy to clipboard to get everything.
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
================================================

## 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<br>
**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<br>
**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.<br>
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.<br>
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.<br>
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 <stdio.h>`?
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<br>usages | F3 |
| F5 | step into | F5 |
| F6 | step over | F6 |
| F7 | step return | F7 |
| Tab<br>Enter | auto-indent | Tab<br>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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fredoverflow</groupId>
<artifactId>skorbut</artifactId>
<version>0.1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.version>2.2.21</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<main.class>MainKt</main.class>
<java.runtime>${java.home}/lib/rt.jar</java.runtime>
</properties>
<profiles>
<profile>
<id>java-modules</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<java.runtime>${java.home}/jmods(!**.jar;!module-info.class)</java.runtime>
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>fredoverflow</groupId>
<artifactId>freditor</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.14.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<includeDependencyInjar>true</includeDependencyInjar>
<outFilter>META-INF/MANIFEST.MF,!META-INF/**,!**.kotlin_*</outFilter>
<outjar>${project.artifactId}.jar</outjar>
<options>
<!-- for some reason, the usual approach via configuration/libs/lib does not work -->
<option>-libraryjars ${java.runtime}</option>
<!-- preserve entry point, otherwise output jar would be empty -->
<option>-keep public class ${main.class} { public static void main(java.lang.String[]); }</option>
<!-- remove compiler-generated null checks for unneeded Java->Kotlin interoperability -->
<option>-assumenosideeffects class kotlin.jvm.internal.Intrinsics { static void checkParameterIsNotNull(java.lang.Object, java.lang.String); }</option>
<!-- hide annoying but harmless reflection warnings -->
<option>-dontnote kotlin.**</option>
</options>
</configuration>
</plugin>
</plugins>
</build>
</project>
================================================
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<Any, Int>()
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 <K, V, M : MutableMap<K, V>> M.puts(keys: Array<out K>, value: V): M {
for (key in keys) {
put(key, value)
}
return this
}
fun <K, V, M : MutableMap<K, V>> M.puts(k1: K, k2: K, value: V): M {
put(k1, value)
put(k2, value)
return this
}
fun <K, V, M : MutableMap<K, V>> 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 <K, V, M : MutableMap<K, V>> 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<FlatStatement>()
fun getStatements(): List<FlatStatement> = 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<ArithmeticValue, String>()
class State(
val continueTarget: String, val breakTarget: String,
val switchControlType: ArithmeticType?,
val cases: HashMap<ArithmeticValue, String>, 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<String, BasicBlock>()
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<Statement>.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<Char>()
private val blocked = AtomicBoolean(false)
fun isBlocked(): Boolean = blocked.get()
var update: Function0<Unit>? = 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<Value>): 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<Value>, after: Function0<Unit>?): 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<ArithmeticValue, String>, 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<NamedDeclarator>) :
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<Memory, Unit>? = 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<Int, Unit>? = null
var after: Function0<Unit>? = 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<String>()
val exploredFunctions = hashSetOf(start)
val staticVariables: Map<String, NamedDeclarator> = 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<Int>()
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>): 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<String>.synthesizeStringConstantsType(): StructType {
val symbols = ArrayList<Symbol>()
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<NamedDeclarator>.synthesizeStaticVariablesType(): StructType {
val symbols = ArrayList<Symbol>()
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<String>, variables: Iterable<NamedDeclarator>) {
val stringObjects = HashMap<String, Object>()
val stringConstants = Segment(stringLiterals.synthesizeStringConstantsType())
val staticVariables = Segment(variables.synthesizeStaticVariablesType())
val stack = ArrayList<Segment>()
val heap = ArrayList<Segment>()
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<Value> = 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<Token>()
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<Diagnostic>()
fun getWarnings(): List<Diagnostic> = 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<Identifier>()
override fun toString(): String = name.text
}
class SymbolTable {
private val scopes = Array<HashMap<String, Symbol>>(128) { HashMap() }
private var current = 0
private val closedScopes = ArrayList<Map<String, Symbol>>()
private val allSymbols = ArrayList<Symbol>()
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 <T> scoped(action: () -> T): T {
openScope()
val result = action()
closeScope()
return result
}
inline fun <T> 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<String, Symbol>, 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<String> = 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<String, Token> =
translationUnit.functions.map { it.namedDeclarator.name }.associateBy(Token::text)
private val symbolTable = SymbolTable()
val stringLiterals = LinkedHashSet<String>()
private var staticOffset = Int.MIN_VALUE
private var currentReturnType: Type = Later
private var currentStackFrameSymbols = ArrayList<Symbol>()
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<Symbol>.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<Symbol>()
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<Statement>.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 : Unary> T.determineValue(f: (Value) -> Value) {
if (sizeofNesting == 0) {
val v = operand.value
if (v != null) {
value = f(v)
}
}
}
private inline fun <T : Binary> 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>) : 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<Symbol>) : 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<String, Any>(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<String, TokenKind> = 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<String> {
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<String> {
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<String>): List<String> {
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>): 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<DeclarationSpecifier>()
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<Enumerator> {
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<StructDeclaration> {
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<Token> {
return collectWhile { current == CONST }
}
fun Parser.declaratorArray(): Expression? {
expect(OPENING_BRACKET)
return ::expression optionalBefore CLOSING_BRACKET
}
fun Parser.declaratorFunction(): List<FunctionParameter> {
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<NullDenotation>(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<LeftDenotation>(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 <V> Array<V>.set(index: TokenKind, value: V) {
this[index.ordinal] = value
}
private operator fun <V> Array<V>.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> T.semicolon(): T {
expect(SEMICOLON)
return this
}
infix fun <T> 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 <T> commaSeparatedList1(first: T, parse: () -> T): List<T> {
val list = mutableListOf(first)
while (current == COMMA) {
next()
list.add(parse())
}
return list
}
inline fun <T> commaSeparatedList1(parse: () -> T): List<T> {
return commaSeparatedList1(parse(), parse)
}
inline fun <T> commaSeparatedList0(terminator: TokenKind, parse: () -> T): List<T> {
return if (current == terminator) {
emptyList()
} else {
commaSeparatedList1(parse)
}
}
inline fun <T> trailingCommaSeparatedList1(terminator: TokenKind, parse: () -> T): List<T> {
val list = mutableListOf(parse())
while (current == COMMA && next() != terminator) {
list.add(parse())
}
return list
}
inline fun <T> list1While(proceed: () -> Boolean, parse: () -> T): List<T> {
val list = mutableListOf(parse())
while (proceed()) {
list.add(parse())
}
return list
}
inline fun <T> list0While(proceed: () -> Boolean, parse: () -> T): List<T> {
return if (!proceed()) {
emptyList()
} else {
list1While(proceed, parse)
}
}
inline fun <T> list1Until(terminator: TokenKind, parse: () -> T): List<T> {
return list1While({ current != terminator }, parse)
}
inline fun <T> list0Until(terminator: TokenKind, parse: () -> T): List<T> {
return list0While({ current != terminator }, parse)
}
inline fun collectWhile(proceed: () -> Boolean): List<Token> {
return list0While(proceed, ::accept)
}
inline fun <T> parenthesized(parse: () -> T): T {
expect(OPENING_PAREN)
val result = parse()
expect(CLOSING_PAREN)
return result
}
inline fun <T> braced(parse: () -> T): T {
expect(OPENING_BRACE)
val result = parse()
expect(CLOSING_BRACE)
return result
}
infix fun <T> (() -> T).optionalBefore(terminator: TokenKind): T? {
return if (current == terminator) {
next()
null
} else {
this() before terminator
}
}
inline fun <T> optional(indicator: TokenKind, parse: () -> T): T? {
return if (current != indicator) {
null
} else {
next()
parse()
}
}
val symbolTable = SymbolTable()
val allMemberNames = HashSet<String>()
}
================================================
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
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
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (318K chars).
[
{
"path": ".gitignore",
"chars": 24,
"preview": "/target/\n/.idea/\n/*.iml\n"
},
{
"path": "README.md",
"chars": 6885,
"preview": "\n\n## What is Skorbut?\n\nSkorbut is a simple teaching environment for a subset of C with a memory visu"
},
{
"path": "pom.xml",
"chars": 5011,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "sloc",
"chars": 486,
"preview": "# sloc: count significant lines of code\n# INsignificant lines contain only spaces and/or braces\n\n# $1 directory\n# $2 ext"
},
{
"path": "src/main/kotlin/Main.kt",
"chars": 201,
"preview": "import freditor.SwingConfig\nimport ui.MainFrame\nimport java.awt.EventQueue\n\nfun main() {\n SwingConfig.metalWithDefaul"
},
{
"path": "src/main/kotlin/common/Counter.kt",
"chars": 215,
"preview": "package common\n\nclass Counter {\n private val counter = HashMap<Any, Int>()\n\n fun count(x: Any): Int {\n val "
},
{
"path": "src/main/kotlin/common/Diagnostic.kt",
"chars": 367,
"preview": "package common\n\ndata class Diagnostic(\n val position: Int, override val message: String,\n val secondPosition: Int "
},
{
"path": "src/main/kotlin/common/Maps.kt",
"chars": 697,
"preview": "package common\n\nfun <K, V, M : MutableMap<K, V>> M.puts(keys: Array<out K>, value: V): M {\n for (key in keys) {\n "
},
{
"path": "src/main/kotlin/interpreter/BasicBlock.kt",
"chars": 1014,
"preview": "package interpreter\n\nclass BasicBlock {\n private val statements = ArrayList<FlatStatement>()\n fun getStatements():"
},
{
"path": "src/main/kotlin/interpreter/BuildControlFlowGraph.kt",
"chars": 8971,
"preview": "package interpreter\n\nimport semantic.types.ArithmeticType\nimport syntax.lexer.missingIdentifier\nimport syntax.tree.*\n\nva"
},
{
"path": "src/main/kotlin/interpreter/Console.kt",
"chars": 10083,
"preview": "package interpreter\n\nimport semantic.types.DoubleType\nimport semantic.types.FloatType\nimport semantic.types.SignedCharTy"
},
{
"path": "src/main/kotlin/interpreter/FlatStatements.kt",
"chars": 2069,
"preview": "package interpreter\n\nimport syntax.lexer.Token\nimport syntax.lexer.missingIdentifier\nimport syntax.tree.DeclarationSpeci"
},
{
"path": "src/main/kotlin/interpreter/Interpreter.kt",
"chars": 35809,
"preview": "package interpreter\n\nimport common.Diagnostic\nimport semantic.TypeChecker\nimport semantic.types.*\nimport syntax.lexer.Le"
},
{
"path": "src/main/kotlin/interpreter/Memory.kt",
"chars": 4087,
"preview": "package interpreter\n\nimport semantic.Symbol\nimport semantic.types.ArrayType\nimport semantic.types.SignedCharType\nimport "
},
{
"path": "src/main/kotlin/interpreter/Segment.kt",
"chars": 1788,
"preview": "package interpreter\n\nimport semantic.types.Type\n\nclass Segment(val type: Type) {\n private val memory: MutableList<Val"
},
{
"path": "src/main/kotlin/interpreter/Value.kt",
"chars": 6656,
"preview": "package interpreter\n\nimport semantic.types.*\nimport syntax.lexer.Token\n\ndata class Object(val segment: Segment, val offs"
},
{
"path": "src/main/kotlin/semantic/Linter.kt",
"chars": 6858,
"preview": "package semantic\n\nimport interpreter.ArithmeticValue\nimport interpreter.BasicBlock\nimport interpreter.ImplicitContinue\ni"
},
{
"path": "src/main/kotlin/semantic/LinterBase.kt",
"chars": 451,
"preview": "package semantic\n\nimport common.Diagnostic\nimport syntax.lexer.Token\nimport syntax.tree.Expression\n\nabstract class Linte"
},
{
"path": "src/main/kotlin/semantic/SymbolTable.kt",
"chars": 3680,
"preview": "package semantic\n\nimport semantic.types.FunctionType\nimport semantic.types.Type\nimport syntax.lexer.Token\nimport syntax."
},
{
"path": "src/main/kotlin/semantic/TypeChecker.kt",
"chars": 38382,
"preview": "package semantic\n\nimport common.Diagnostic\nimport freditor.Levenshtein\nimport interpreter.*\nimport semantic.types.*\nimpo"
},
{
"path": "src/main/kotlin/semantic/TypeSpecifiers.kt",
"chars": 2131,
"preview": "package semantic\n\nimport semantic.types.*\nimport syntax.lexer.TokenKind.*\nimport syntax.lexer.TokenKindSet\n\nval enumStru"
},
{
"path": "src/main/kotlin/semantic/types/Arithmetic.kt",
"chars": 4654,
"preview": "package semantic.types\n\nimport interpreter.ArithmeticValue\nimport interpreter.Value\nimport text.quote\n\nabstract class Ar"
},
{
"path": "src/main/kotlin/semantic/types/Array.kt",
"chars": 1251,
"preview": "package semantic.types\n\ndata class ArrayType(var size: Int, val elementType: Type) : Type {\n override fun sizeof(): I"
},
{
"path": "src/main/kotlin/semantic/types/Enum.kt",
"chars": 212,
"preview": "package semantic.types\n\nimport interpreter.ArithmeticValue\n\nclass EnumerationConstant(val value: ArithmeticValue) : Type"
},
{
"path": "src/main/kotlin/semantic/types/Function.kt",
"chars": 1000,
"preview": "package semantic.types\n\ndata class FunctionType(val returnType: Type, val parameters: List<Type>) : Type {\n companion"
},
{
"path": "src/main/kotlin/semantic/types/Pointer.kt",
"chars": 1901,
"preview": "package semantic.types\n\nimport interpreter.Value\n\ninterface ComparablePointerType : Type\n\ndata class PointerType(val ref"
},
{
"path": "src/main/kotlin/semantic/types/Struct.kt",
"chars": 1348,
"preview": "package semantic.types\n\nimport semantic.Symbol\nimport syntax.lexer.Token\nimport syntax.lexer.missingIdentifier\n\nabstract"
},
{
"path": "src/main/kotlin/semantic/types/Type.kt",
"chars": 1552,
"preview": "package semantic.types\n\nimport interpreter.Value\n\ninterface Type {\n fun requiresStorage(): Boolean = true\n\n fun is"
},
{
"path": "src/main/kotlin/semantic/types/Typedef.kt",
"chars": 277,
"preview": "package semantic.types\n\nclass Typedef(val aliased: Type) : Type {\n override fun requiresStorage(): Boolean = false\n\n "
},
{
"path": "src/main/kotlin/semantic/types/Void.kt",
"chars": 691,
"preview": "package semantic.types\n\nobject VoidType : Type {\n override fun pointer(): Type = VoidPointerType\n\n override fun co"
},
{
"path": "src/main/kotlin/syntax/lexer/Characters.kt",
"chars": 1363,
"preview": "package syntax.lexer\n\nimport syntax.lexer.TokenKind.CHARACTER_CONSTANT\nimport syntax.lexer.TokenKind.STRING_LITERAL\n\nfun"
},
{
"path": "src/main/kotlin/syntax/lexer/Identifiers.kt",
"chars": 837,
"preview": "package syntax.lexer\n\nimport syntax.lexer.TokenKind.IDENTIFIER\n\ntailrec fun Lexer.identifierOrKeyword(): Token = when (n"
},
{
"path": "src/main/kotlin/syntax/lexer/Lexer.kt",
"chars": 1254,
"preview": "package syntax.lexer\n\nimport common.Diagnostic\n\nconst val EOF = '\\u0000'\n\nclass Lexer(private val input: String) {\n v"
},
{
"path": "src/main/kotlin/syntax/lexer/NextToken.kt",
"chars": 3526,
"preview": "package syntax.lexer\n\nimport syntax.lexer.TokenKind.*\n\ntailrec fun Lexer.nextToken(): Token {\n startAtIndex()\n ret"
},
{
"path": "src/main/kotlin/syntax/lexer/Numbers.kt",
"chars": 2381,
"preview": "package syntax.lexer\n\nimport syntax.lexer.TokenKind.*\n\nfun Lexer.constant(): Token {\n var seenDecimalPoint = false\n "
},
{
"path": "src/main/kotlin/syntax/lexer/SkipComments.kt",
"chars": 332,
"preview": "package syntax.lexer\n\nfun Lexer.skipSingleLineComment() {\n while (next() != '\\n') {\n if (current == EOF) retur"
},
{
"path": "src/main/kotlin/syntax/lexer/Token.kt",
"chars": 1557,
"preview": "package syntax.lexer\n\nimport common.Diagnostic\nimport syntax.lexer.TokenKind.IDENTIFIER\nimport syntax.lexer.TokenKind.ST"
},
{
"path": "src/main/kotlin/syntax/lexer/TokenKind.kt",
"chars": 2334,
"preview": "package syntax.lexer\n\nenum class TokenKind(val lexeme: String) {\n ASSERT(\"assert\"),\n AUTO(\"auto\"),\n BREAK(\"brea"
},
{
"path": "src/main/kotlin/syntax/lexer/TokenKindSet.kt",
"chars": 2142,
"preview": "package syntax.lexer\n\nimport java.lang.Long.lowestOneBit\nimport java.lang.Long.numberOfTrailingZeros\n\nprivate val TokenK"
},
{
"path": "src/main/kotlin/syntax/parser/Autocompletion.kt",
"chars": 1660,
"preview": "package syntax.parser\n\nimport common.Diagnostic\nimport syntax.lexer.Lexer\nimport syntax.lexer.TokenKind\n\nfun autocomplet"
},
{
"path": "src/main/kotlin/syntax/parser/Declarations.kt",
"chars": 10650,
"preview": "package syntax.parser\n\nimport semantic.enumStructUnion\nimport semantic.storageClasses\nimport semantic.typeSpecifierIdent"
},
{
"path": "src/main/kotlin/syntax/parser/Expressions.kt",
"chars": 3534,
"preview": "package syntax.parser\n\nimport syntax.lexer.TokenKind\nimport syntax.lexer.TokenKind.*\nimport syntax.tree.*\n\nconst val PRE"
},
{
"path": "src/main/kotlin/syntax/parser/ExternalDefinitions.kt",
"chars": 1445,
"preview": "package syntax.parser\n\nimport semantic.types.FunctionType\nimport syntax.lexer.TokenKind.*\nimport syntax.tree.*\n\nfun Pars"
},
{
"path": "src/main/kotlin/syntax/parser/LeftDenotations.kt",
"chars": 2263,
"preview": "package syntax.parser\n\nimport syntax.lexer.Token\nimport syntax.lexer.TokenKind.*\nimport syntax.tree.*\n\nabstract class Le"
},
{
"path": "src/main/kotlin/syntax/parser/NullDenotations.kt",
"chars": 2981,
"preview": "package syntax.parser\n\nimport syntax.lexer.Token\nimport syntax.lexer.TokenKind.*\nimport syntax.tree.*\n\nabstract class Nu"
},
{
"path": "src/main/kotlin/syntax/parser/Parser.kt",
"chars": 4029,
"preview": "package syntax.parser\n\nimport common.Diagnostic\nimport semantic.SymbolTable\nimport syntax.lexer.Lexer\nimport syntax.lexe"
},
{
"path": "src/main/kotlin/syntax/parser/Statements.kt",
"chars": 2946,
"preview": "package syntax.parser\n\nimport common.Diagnostic\nimport syntax.lexer.Token\nimport syntax.lexer.TokenKind.*\nimport syntax."
},
{
"path": "src/main/kotlin/syntax/tree/DeclarationSpecifier.kt",
"chars": 3241,
"preview": "package syntax.tree\n\nimport semantic.types.Later\nimport semantic.types.Type\nimport syntax.lexer.Token\nimport syntax.lexe"
},
{
"path": "src/main/kotlin/syntax/tree/Declarator.kt",
"chars": 2368,
"preview": "package syntax.tree\n\nimport semantic.types.Later\nimport semantic.types.Type\nimport syntax.lexer.Token\nimport syntax.lexe"
},
{
"path": "src/main/kotlin/syntax/tree/Expression.kt",
"chars": 4844,
"preview": "package syntax.tree\n\nimport interpreter.Value\nimport semantic.Symbol\nimport semantic.types.Later\nimport semantic.types.T"
},
{
"path": "src/main/kotlin/syntax/tree/External.kt",
"chars": 1523,
"preview": "package syntax.tree\n\nimport interpreter.BasicBlock\nimport semantic.types.StructType\nimport semantic.types.StructTypeLate"
},
{
"path": "src/main/kotlin/syntax/tree/Node.kt",
"chars": 505,
"preview": "package syntax.tree\n\nimport syntax.lexer.Token\n\nabstract class Node {\n fun walk(enter: (Node) -> Unit, leave: (Node) "
},
{
"path": "src/main/kotlin/syntax/tree/Statement.kt",
"chars": 3672,
"preview": "package syntax.tree\n\nimport syntax.lexer.Token\n\nabstract class Statement : Node()\n\nclass Declaration(val specifiers: Dec"
},
{
"path": "src/main/kotlin/text/Char.kt",
"chars": 3556,
"preview": "package text\n\nfun Char.quote(): String = when (this) {\n '\\u0000' -> \"'\\\\0'\"\n '\\u0007' -> \"'\\\\a'\"\n '\\u0008' -> \""
},
{
"path": "src/main/kotlin/text/String.kt",
"chars": 169,
"preview": "package text\n\nfun String.skipDigits(start: Int): Int {\n val n = length\n for (i in start until n) {\n if (thi"
},
{
"path": "src/main/kotlin/ui/Flexer.kt",
"chars": 4455,
"preview": "package ui\n\nimport common.puts\nimport freditor.FlexerState\nimport freditor.FlexerState.EMPTY\nimport freditor.FlexerState"
},
{
"path": "src/main/kotlin/ui/MainFrame.kt",
"chars": 18547,
"preview": "package ui\n\nimport common.Diagnostic\nimport freditor.*\nimport interpreter.Interpreter\nimport interpreter.Memory\nimport s"
},
{
"path": "src/main/kotlin/ui/MemoryUI.kt",
"chars": 7163,
"preview": "package ui\n\nimport common.Counter\nimport freditor.Fronts\nimport interpreter.Memory\nimport interpreter.PointerValue\nimpor"
},
{
"path": "src/test/kotlin/interpreter/InterpreterTest.kt",
"chars": 43840,
"preview": "package interpreter\n\nimport org.junit.jupiter.api.Test\n\nclass InterpreterTest {\n private fun run(program: String) {\n "
},
{
"path": "src/test/kotlin/semantic/types/TypeToStringTest.kt",
"chars": 4200,
"preview": "package semantic.types\n\nimport org.junit.jupiter.api.Assertions.assertEquals\nimport org.junit.jupiter.api.Test\n\nclass Ty"
},
{
"path": "src/test/kotlin/syntax/lexer/LexerTest.kt",
"chars": 4077,
"preview": "package syntax.lexer\n\nimport org.junit.jupiter.api.Assertions.assertEquals\nimport org.junit.jupiter.api.Assertions.asser"
},
{
"path": "src/test/kotlin/syntax/parser/AutocompletionTest.kt",
"chars": 3404,
"preview": "package syntax.parser\n\nimport org.junit.jupiter.api.Assertions.assertEquals\nimport org.junit.jupiter.api.Test\n\nclass Aut"
},
{
"path": "src/test/resources/junit-platform.properties",
"chars": 107,
"preview": "junit.jupiter.execution.parallel.enabled = true\njunit.jupiter.execution.parallel.mode.default = concurrent\n"
}
]
About this extraction
This page contains the full source code of the fredoverflow/skorbut-release GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (292.6 KB), approximately 72.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.