├── .gitignore ├── skorbut.png ├── src ├── test │ ├── resources │ │ └── junit-platform.properties │ └── kotlin │ │ ├── syntax │ │ ├── parser │ │ │ └── AutocompletionTest.kt │ │ └── lexer │ │ │ └── LexerTest.kt │ │ └── semantic │ │ └── types │ │ └── TypeToStringTest.kt └── main │ └── kotlin │ ├── text │ ├── String.kt │ └── Char.kt │ ├── Main.kt │ ├── semantic │ ├── types │ │ ├── Enum.kt │ │ ├── Typedef.kt │ │ ├── Void.kt │ │ ├── Function.kt │ │ ├── Array.kt │ │ ├── Struct.kt │ │ ├── Type.kt │ │ ├── Pointer.kt │ │ └── Arithmetic.kt │ ├── LinterBase.kt │ ├── TypeSpecifiers.kt │ ├── SymbolTable.kt │ └── Linter.kt │ ├── common │ ├── Counter.kt │ ├── Diagnostic.kt │ └── Maps.kt │ ├── syntax │ ├── lexer │ │ ├── SkipComments.kt │ │ ├── Identifiers.kt │ │ ├── Lexer.kt │ │ ├── Characters.kt │ │ ├── Token.kt │ │ ├── TokenKindSet.kt │ │ ├── Numbers.kt │ │ ├── TokenKind.kt │ │ └── NextToken.kt │ ├── tree │ │ ├── Node.kt │ │ ├── External.kt │ │ ├── Declarator.kt │ │ ├── DeclarationSpecifier.kt │ │ ├── Statement.kt │ │ └── Expression.kt │ └── parser │ │ ├── ExternalDefinitions.kt │ │ ├── Autocompletion.kt │ │ ├── LeftDenotations.kt │ │ ├── NullDenotations.kt │ │ ├── Statements.kt │ │ ├── Expressions.kt │ │ ├── Parser.kt │ │ └── Declarations.kt │ ├── interpreter │ ├── BasicBlock.kt │ ├── Segment.kt │ ├── FlatStatements.kt │ ├── Memory.kt │ ├── Value.kt │ ├── BuildControlFlowGraph.kt │ └── Console.kt │ └── ui │ ├── Flexer.kt │ ├── MemoryUI.kt │ └── MainFrame.kt ├── sloc ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.idea/ 3 | /*.iml 4 | -------------------------------------------------------------------------------- /skorbut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fredoverflow/skorbut/HEAD/skorbut.png -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.execution.parallel.enabled = true 2 | junit.jupiter.execution.parallel.mode.default = concurrent 3 | -------------------------------------------------------------------------------- /src/main/kotlin/text/String.kt: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | fun String.skipDigits(start: Int): Int { 4 | val n = length 5 | for (i in start until n) { 6 | if (this[i] !in '0'..'9') return i 7 | } 8 | return n 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import freditor.SwingConfig 2 | import ui.MainFrame 3 | import java.awt.EventQueue 4 | 5 | fun main() { 6 | SwingConfig.metalWithDefaultFont(SwingConfig.SANS_SERIF_PLAIN_16) 7 | EventQueue.invokeLater(::MainFrame) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Enum.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import interpreter.ArithmeticValue 4 | 5 | class EnumerationConstant(val value: ArithmeticValue) : Type { 6 | override fun requiresStorage(): Boolean = false 7 | 8 | override fun count(): Int = 0 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/common/Counter.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | class Counter { 4 | private val counter = HashMap() 5 | 6 | fun count(x: Any): Int { 7 | val soFar = counter.getOrElse(x) { 0 } 8 | counter[x] = soFar + 1 9 | return soFar 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Typedef.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | class Typedef(val aliased: Type) : Type { 4 | override fun requiresStorage(): Boolean = false 5 | 6 | override fun count(): Int = 0 7 | } 8 | 9 | val TypedefSignedIntType = Typedef(SignedIntType) 10 | 11 | object MarkerIsTypedefName : Type 12 | 13 | object MarkerNotTypedefName : Type 14 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/SkipComments.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | fun Lexer.skipSingleLineComment() { 4 | while (next() != '\n') { 5 | if (current == EOF) return 6 | } 7 | next() // skip '\n' 8 | } 9 | 10 | fun Lexer.skipMultiLineComment() { 11 | next() // skip '*' 12 | do { 13 | if (current == EOF) return 14 | } while ((current != '*') or (next() != '/')) 15 | next() // skip '/' 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/common/Diagnostic.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | data class Diagnostic( 4 | val position: Int, override val message: String, 5 | val secondPosition: Int = -1, 6 | val columnDelta: Int = 0 7 | ) : Exception(message) { 8 | 9 | override fun toString(): String { 10 | return if (secondPosition < 0) { 11 | message 12 | } else { 13 | "$message \uD83D\uDDB0 toggle position" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/LinterBase.kt: -------------------------------------------------------------------------------- 1 | package semantic 2 | 3 | import common.Diagnostic 4 | import syntax.lexer.Token 5 | import syntax.tree.Expression 6 | 7 | abstract class LinterBase { 8 | private val warnings = ArrayList() 9 | 10 | fun getWarnings(): List = warnings.sortedBy { it.position } 11 | 12 | protected fun Token.warn(message: String) { 13 | warnings.add(Diagnostic(start, message)) 14 | } 15 | 16 | protected fun Expression.warn(message: String) { 17 | root().warn(message) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sloc: -------------------------------------------------------------------------------- 1 | # sloc: count significant lines of code 2 | # INsignificant lines contain only spaces and/or braces 3 | 4 | # $1 directory 5 | # $2 extension 6 | function countInDirectory { 7 | lines=$(find "src/$1" -name "*.$2" -exec grep -vP "^[{ }]*\r?$" {} + | wc -l) 8 | if [ $lines -gt 0 ] 9 | then 10 | printf "$1: $lines\n" 11 | fi 12 | } 13 | 14 | # $1 language 15 | # $2 extension 16 | function countForLanguage { 17 | printf "$1\n" 18 | printf "==========\n" 19 | countInDirectory main $2 20 | countInDirectory test $2 21 | printf "\n" 22 | } 23 | 24 | countForLanguage "Kotlin" "kt" 25 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/Node.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import syntax.lexer.Token 4 | 5 | abstract class Node { 6 | fun walk(enter: (Node) -> Unit, leave: (Node) -> Unit) { 7 | enter(this) 8 | walkChildren(enter, leave) 9 | leave(this) 10 | } 11 | 12 | fun walkChildren(enter: (Node) -> Unit, leave: (Node) -> Unit) { 13 | forEachChild { 14 | it.walk(enter, leave) 15 | } 16 | } 17 | 18 | open fun forEachChild(action: (Node) -> Unit) { 19 | } 20 | 21 | abstract fun root(): Token 22 | 23 | override fun toString(): String = root().toString() 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Void.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | object VoidType : Type { 4 | override fun pointer(): Type = VoidPointerType 5 | 6 | override fun count(): Int = 0 7 | 8 | override fun addConst(): Type = ConstVoidType 9 | 10 | override fun toString(): String = "void" 11 | 12 | override fun declaration(parent: String): String = "void$parent" 13 | } 14 | 15 | object ConstVoidType : Type { 16 | override fun pointer(): Type = ConstVoidPointerType 17 | 18 | override fun count(): Int = 0 19 | 20 | override fun isConst(): Boolean = true 21 | 22 | override fun addConst(): Type = this 23 | 24 | override fun unqualified(): Type = VoidType 25 | 26 | override fun toString(): String = "const void" 27 | 28 | override fun declaration(parent: String): String = "const void$parent" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/common/Maps.kt: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | fun > M.puts(keys: Array, value: V): M { 4 | for (key in keys) { 5 | put(key, value) 6 | } 7 | return this 8 | } 9 | 10 | fun > M.puts(k1: K, k2: K, value: V): M { 11 | put(k1, value) 12 | put(k2, value) 13 | return this 14 | } 15 | 16 | fun > M.puts(k1: K, k2: K, k3: K, k4: K, value: V): M { 17 | put(k1, value) 18 | put(k2, value) 19 | put(k3, value) 20 | put(k4, value) 21 | return this 22 | } 23 | 24 | fun > M.puts(k1: K, k2: K, k3: K, k4: K, k5: K, k6: K, value: V): M { 25 | put(k1, value) 26 | put(k2, value) 27 | put(k3, value) 28 | put(k4, value) 29 | put(k5, value) 30 | put(k6, value) 31 | return this 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/Identifiers.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import syntax.lexer.TokenKind.IDENTIFIER 4 | 5 | tailrec fun Lexer.identifierOrKeyword(): Token = when (next()) { 6 | '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', 7 | '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', 8 | '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> identifierOrKeyword() 9 | 10 | else -> { 11 | val lexeme = lexeme() 12 | when (val value: Any? = identifiersOrKeywords[lexeme]) { 13 | is TokenKind -> verbatim(value) 14 | 15 | is String -> token(IDENTIFIER, value) 16 | 17 | else -> { 18 | identifiersOrKeywords[lexeme] = lexeme 19 | token(IDENTIFIER, lexeme) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Function.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | data class FunctionType(val returnType: Type, val parameters: List) : Type { 4 | companion object { 5 | operator fun invoke(returnType: Type, vararg parameters: Type) = FunctionType(returnType, parameters.toList()) 6 | 7 | fun declarationMarker(): FunctionType = FunctionType(VoidType) 8 | val DEFINITION_MARKER: FunctionType = declarationMarker().apply { defined = true } 9 | } 10 | 11 | var defined: Boolean = false 12 | 13 | override fun requiresStorage(): Boolean = false 14 | 15 | override fun count(): Int = 0 16 | 17 | override fun decayed(): Type = pointer() 18 | 19 | override fun toString(): String = declaration("") 20 | 21 | override fun declaration(parent: String): String { 22 | val params = parameters.joinToString(transform = Type::toString, prefix = "(", separator = ",", postfix = ")") 23 | return if (parent.isPointer()) { 24 | returnType.declaration("($parent)$params") 25 | } else { 26 | returnType.declaration("$parent$params") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/BasicBlock.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | class BasicBlock { 4 | private val statements = ArrayList() 5 | fun getStatements(): List = statements 6 | 7 | fun isOpen(): Boolean { 8 | return !isClosed() 9 | } 10 | 11 | fun isClosed(): Boolean { 12 | return statements.lastOrNull() is TransferringControl 13 | } 14 | 15 | fun isEmpty(): Boolean { 16 | return statements.isEmpty() 17 | } 18 | 19 | fun add(statement: FlatStatement) { 20 | assert(isOpen()) 21 | statements.add(statement) 22 | } 23 | 24 | fun replaceSwitchPlaceholderWithRealSwitch(replacement: HashSwitch) { 25 | assert(statements.last() === SwitchPlaceholder) 26 | statements[statements.lastIndex] = replacement 27 | } 28 | 29 | var isReachable = false 30 | private set 31 | 32 | fun exploreReachability(resolve: (String) -> BasicBlock) { 33 | if (!isReachable) { 34 | isReachable = true 35 | statements.lastOrNull()?.forEachSuccessor { label -> 36 | resolve(label).exploreReachability(resolve) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Array.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | data class ArrayType(var size: Int, val elementType: Type) : Type { 4 | override fun sizeof(): Int = size * elementType.sizeof() 5 | 6 | override fun sizeof(offset: Int): Int { 7 | if (offset >= count()) return sizeof() 8 | 9 | val n = elementType.count() 10 | return offset / n * elementType.sizeof() + elementType.sizeof(offset % n) 11 | } 12 | 13 | override fun decayed(): Type = PointerType(elementType) 14 | 15 | override fun count(): Int = size * elementType.count() 16 | 17 | override fun isConst(): Boolean = elementType.isConst() 18 | 19 | override fun addConst(): Type = if (isConst()) this else ArrayType(size, elementType.addConst()) 20 | 21 | override fun unqualified(): Type = if (isConst()) ArrayType(size, elementType.unqualified()) else this 22 | 23 | fun dimensions(): Int = if (elementType is ArrayType) elementType.dimensions() + 1 else 1 24 | 25 | override fun toString(): String = declaration("") 26 | 27 | override fun declaration(parent: String): String { 28 | return if (parent.isPointer()) { 29 | elementType.declaration("($parent)[$size]") 30 | } else { 31 | elementType.declaration("$parent[$size]") 32 | } 33 | } 34 | } 35 | 36 | fun Type.isString(): Boolean = this is ArrayType && elementType.unqualified() == SignedCharType 37 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/Lexer.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import common.Diagnostic 4 | 5 | const val EOF = '\u0000' 6 | 7 | class Lexer(private val input: String) { 8 | var start: Int = -1 9 | private set 10 | 11 | var index: Int = -1 12 | private set 13 | 14 | fun startAtIndex() { 15 | start = index 16 | } 17 | 18 | var current: Char = next() 19 | private set 20 | 21 | fun next(): Char { 22 | current = if (++index < input.length) input[index] else EOF 23 | return current 24 | } 25 | 26 | fun previous(): Char { 27 | current = input[--index] 28 | return current 29 | } 30 | 31 | fun lexeme(): String { 32 | return input.substring(start, index) 33 | } 34 | 35 | fun token(kind: TokenKind): Token { 36 | return token(kind, lexeme()) 37 | } 38 | 39 | fun token(kind: TokenKind, text: String): Token { 40 | return token(kind, text, text) 41 | } 42 | 43 | fun token(kind: TokenKind, source: String, text: String): Token { 44 | return Token(kind, start, source, text) 45 | } 46 | 47 | fun verbatim(kind: TokenKind): Token { 48 | return token(kind, kind.lexeme) 49 | } 50 | 51 | fun nextVerbatim(kind: TokenKind): Token { 52 | next() 53 | return verbatim(kind) 54 | } 55 | 56 | fun error(message: String): Nothing { 57 | throw Diagnostic(index, message) 58 | } 59 | 60 | val identifiersOrKeywords = HashMap(keywords) 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Struct.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import semantic.Symbol 4 | import syntax.lexer.Token 5 | import syntax.lexer.missingIdentifier 6 | 7 | abstract class CompletableType : Type { 8 | private var complete = false 9 | 10 | override fun isComplete(): Boolean = complete 11 | 12 | fun makeComplete(): Type { 13 | assert(!complete) 14 | complete = true 15 | return this 16 | } 17 | } 18 | 19 | class StructType(val name: Token, val members: List) : CompletableType() { 20 | override fun sizeof(): Int = members.sumOf { it.type.sizeof() } 21 | 22 | override fun sizeof(offset: Int): Int { 23 | var size = 0 24 | var off = offset 25 | for (member in members) { 26 | size += member.type.sizeof(off) 27 | off -= member.type.count() 28 | if (off <= 0) break 29 | } 30 | return size 31 | } 32 | 33 | override fun count(): Int = members.sumOf { it.type.count() } 34 | 35 | override fun canCastFromDecayed(source: Type): Boolean = (this === source) 36 | 37 | fun member(name: Token): Symbol? = members.find { it.name.text === name.text } 38 | 39 | override fun toString(): String = "struct $name" 40 | 41 | override fun declaration(parent: String): String = "struct $name$parent" 42 | } 43 | 44 | val StructTypeLater = StructType(missingIdentifier, emptyList()) 45 | 46 | class StructTag(val structType: StructType) : Type { 47 | override fun requiresStorage(): Boolean = false 48 | 49 | override fun count(): Int = 0 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/ExternalDefinitions.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import semantic.types.FunctionType 4 | import syntax.lexer.TokenKind.* 5 | import syntax.tree.* 6 | 7 | fun Parser.translationUnit(): TranslationUnit { 8 | return TranslationUnit(list1Until(END_OF_INPUT, ::externalDeclaration)) 9 | } 10 | 11 | fun Parser.externalDeclaration(): Node { 12 | val specifiers = declarationSpecifiers1declareDefTagName() 13 | if (current == SEMICOLON && specifiers.isDeclaratorOptional()) { 14 | return Declaration(specifiers, emptyList()).semicolon() 15 | } 16 | val firstNamedDeclarator = namedDeclarator() 17 | if (firstNamedDeclarator.declarator.leaf() is Declarator.Function) { 18 | if (current == SEMICOLON && lookahead.kind == OPENING_BRACE) { 19 | token.error("function definitions require no semicolon") 20 | } 21 | if (current == OPENING_BRACE) { 22 | symbolTable.declare(firstNamedDeclarator.name, FunctionType.DEFINITION_MARKER, 0) 23 | return symbolTable.rescoped { 24 | braced { 25 | FunctionDefinition(specifiers, firstNamedDeclarator, list0Until(CLOSING_BRACE, ::statement), token) 26 | } 27 | } 28 | } 29 | } 30 | val isTypedefName = specifiers.storageClass == TYPEDEF 31 | declare(firstNamedDeclarator, isTypedefName) 32 | val declarators = commaSeparatedList1(initDeclarator(firstNamedDeclarator)) { 33 | initDeclarator().apply { declare(this, isTypedefName) } 34 | } 35 | return Declaration(specifiers, declarators).semicolon() 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/Characters.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import syntax.lexer.TokenKind.CHARACTER_CONSTANT 4 | import syntax.lexer.TokenKind.STRING_LITERAL 5 | 6 | fun Lexer.characterConstant(): Token { 7 | val executionChar = when (next()) { 8 | '\\' -> escapeSequence() 9 | 10 | in '\u0020'..'\u007e' -> current 11 | in '\u00a0'..'\u00ff' -> current 12 | 13 | else -> error("illegal character inside character constant") 14 | } 15 | if (next() != '\'') error("character constant must be closed by '") 16 | 17 | next() 18 | return token(CHARACTER_CONSTANT, lexeme(), executionChar.toString()) 19 | } 20 | 21 | fun Lexer.escapeSequence(): Char = when (next()) { 22 | '\'', '\"', '?', '\\' -> current 23 | 24 | 'a' -> '\u0007' 25 | 'b' -> '\u0008' 26 | 't' -> '\u0009' 27 | 'n' -> '\u000a' 28 | 'v' -> '\u000b' 29 | 'f' -> '\u000c' 30 | 'r' -> '\u000d' 31 | 32 | '0' -> '\u0000' 33 | 34 | else -> error("illegal escape character") 35 | } 36 | 37 | fun Lexer.stringLiteral(): Token { 38 | val sb = StringBuilder() 39 | while (true) { 40 | val executionChar = when (next()) { 41 | '\\' -> escapeSequence() 42 | 43 | '\"' -> { 44 | next() 45 | return token(STRING_LITERAL, lexeme(), sb.toString().intern()) 46 | } 47 | 48 | in '\u0020'..'\u007e' -> current 49 | in '\u00a0'..'\u00ff' -> current 50 | 51 | else -> error("illegal character inside string literal") 52 | } 53 | sb.append(executionChar) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/External.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import interpreter.BasicBlock 4 | import semantic.types.StructType 5 | import semantic.types.StructTypeLater 6 | import syntax.lexer.Token 7 | 8 | class TranslationUnit(val externalDeclarations: List) : Node() { 9 | val functions = externalDeclarations.filterIsInstance() 10 | val declarations: List 11 | 12 | init { 13 | declarations = ArrayList() 14 | walkChildren({}) { 15 | if (it is Declaration) { 16 | declarations.add(it) 17 | } 18 | } 19 | } 20 | 21 | override fun forEachChild(action: (Node) -> Unit) { 22 | externalDeclarations.forEach(action) 23 | } 24 | 25 | override fun root(): Token = externalDeclarations.first().root() 26 | 27 | override fun toString(): String = "translation unit" 28 | } 29 | 30 | class FunctionDefinition( 31 | val specifiers: DeclarationSpecifiers, 32 | val namedDeclarator: NamedDeclarator, 33 | val body: List, 34 | val closingBrace: Token 35 | ) : Node() { 36 | fun name(): String = namedDeclarator.name.text 37 | 38 | val parameters: List = 39 | (namedDeclarator.declarator.leaf() as Declarator.Function).parameters.map(FunctionParameter::namedDeclarator) 40 | 41 | var stackFrameType: StructType = StructTypeLater 42 | 43 | lateinit var controlFlowGraph: LinkedHashMap 44 | 45 | override fun forEachChild(action: (Node) -> Unit) { 46 | // action(declarator) 47 | body.forEach(action) 48 | } 49 | 50 | override fun root(): Token = namedDeclarator.name 51 | 52 | override fun toString(): String = namedDeclarator.toString() 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/Token.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import common.Diagnostic 4 | import syntax.lexer.TokenKind.IDENTIFIER 5 | import syntax.lexer.TokenKind.STRING_LITERAL 6 | 7 | class Token(val kind: TokenKind, val start: Int, val source: String, val text: String) { 8 | val end: Int 9 | get() = start + source.length 10 | 11 | fun withTokenKind(replacement: TokenKind): Token = Token(replacement, start, source, text) 12 | 13 | fun tagged(): Token = Token(kind, start, source, "#$text".intern()) 14 | 15 | fun wasProvided(): Boolean = kind == IDENTIFIER 16 | 17 | fun error(message: String): Nothing { 18 | throw Diagnostic(start, message) 19 | } 20 | 21 | fun error(message: String, columnDelta: Int): Nothing { 22 | throw Diagnostic(start, message, columnDelta = columnDelta) 23 | } 24 | 25 | fun error(before: String, after: String): Nothing { 26 | throw Diagnostic(start, before + after, columnDelta = -before.length) 27 | } 28 | 29 | fun error(message: String, previous: Token): Nothing { 30 | throw Diagnostic(start, message, previous.start) 31 | } 32 | 33 | // 0123 456 index 34 | // "ABC\nXYZ" 35 | // 0123456789 origin 36 | fun stringErrorAt(index: Int, message: String): Nothing { 37 | assert(kind == STRING_LITERAL) 38 | var origin = 1 39 | repeat(index) { 40 | if (source[origin] == '\\') ++origin 41 | ++origin 42 | } 43 | throw Diagnostic(start + origin, message) 44 | } 45 | 46 | override fun toString(): String = source 47 | } 48 | 49 | fun fakeIdentifier(name: String) = Token(IDENTIFIER, Int.MIN_VALUE, name, name) 50 | 51 | val missingIdentifier = fakeIdentifier("") 52 | val hiddenIdentifier = fakeIdentifier("_") 53 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Type.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import interpreter.Value 4 | 5 | interface Type { 6 | fun requiresStorage(): Boolean = true 7 | 8 | fun isComplete(): Boolean = sizeof() > 0 9 | 10 | fun sizeof(): Int = 0 11 | 12 | fun sizeof(offset: Int): Int = if (offset == 0) 0 else sizeof() 13 | 14 | fun decayed(): Type = this 15 | 16 | fun pointer(): Type = PointerType(this) 17 | 18 | fun count(): Int = 1 19 | 20 | fun canCastFrom(source: Type): Boolean = canCastFromDecayed(source.decayed()) 21 | 22 | fun canCastFromDecayed(source: Type): Boolean = false 23 | 24 | fun cast(source: Value): Value = source 25 | 26 | fun isConst(): Boolean = false 27 | 28 | fun addConst(): Type = Const(this) 29 | 30 | fun unqualified(): Type = this 31 | 32 | fun applyQualifiersTo(target: Type): Type = target 33 | 34 | fun declaration(parent: String): String { 35 | throw AssertionError("$javaClass.declaration($parent)") 36 | } 37 | 38 | fun String.isPointer(): Boolean = this.isNotEmpty() && this.first() == '*' 39 | } 40 | 41 | data class Const(val underlying: Type) : Type by underlying { 42 | override fun pointer(): Type = PointerType(this) 43 | 44 | override fun isConst(): Boolean = true 45 | 46 | override fun addConst(): Type = this 47 | 48 | override fun unqualified(): Type = underlying 49 | 50 | override fun applyQualifiersTo(target: Type): Type = target.addConst() 51 | 52 | override fun toString(): String = declaration("") 53 | 54 | override fun declaration(parent: String): String { 55 | return if (underlying is ComparablePointerType) { 56 | underlying.declaration("const$parent") 57 | } else { 58 | "const $underlying$parent" 59 | } 60 | } 61 | } 62 | 63 | object Later : Type 64 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/Autocompletion.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import common.Diagnostic 4 | import syntax.lexer.Lexer 5 | import syntax.lexer.TokenKind 6 | 7 | fun autocompleteIdentifier(textBeforeSelection: String): List { 8 | val lexer = Lexer(textBeforeSelection) 9 | val parser = Parser(lexer) 10 | val suffixes = parser.fittingSuffixes(textBeforeSelection) 11 | val lcp = longestCommonPrefix(suffixes) 12 | return if (lcp.isEmpty()) { 13 | suffixes 14 | } else { 15 | listOf(lcp) 16 | } 17 | } 18 | 19 | private fun Parser.fittingSuffixes(textBeforeSelection: String): List { 20 | try { 21 | translationUnit() 22 | } catch (diagnostic: Diagnostic) { 23 | if (diagnostic.position != textBeforeSelection.length) throw diagnostic 24 | 25 | if (previous.kind == TokenKind.IDENTIFIER && previous.end == textBeforeSelection.length) { 26 | return when (beforePrevious.kind) { 27 | TokenKind.DOT, TokenKind.HYPHEN_MORE -> suffixesIn(allMemberNames.asSequence()) 28 | 29 | else -> suffixesIn(symbolTable.names()) 30 | } 31 | } 32 | } 33 | return emptyList() 34 | } 35 | 36 | private fun Parser.suffixesIn(names: Sequence): List { 37 | val prefix = previous.text 38 | val prefixLength = prefix.length 39 | 40 | return names 41 | .filter { it.length > prefixLength && it.startsWith(prefix) } 42 | .map { it.substring(prefixLength) } 43 | .toList() 44 | } 45 | 46 | private fun longestCommonPrefix(strings: List): String { 47 | val shortestString = strings.minByOrNull(String::length) ?: "" 48 | shortestString.forEachIndexed { index, ch -> 49 | if (!strings.all { it[index] == ch }) { 50 | return shortestString.substring(0, index) 51 | } 52 | } 53 | return shortestString 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/Segment.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import semantic.types.Type 4 | 5 | class Segment(val type: Type) { 6 | private val memory: MutableList = MutableList(type.count()) { IndeterminateValue } 7 | var readOnlyErrorMessage: String? = null 8 | 9 | var alive = true 10 | private set 11 | 12 | fun kill() { 13 | assert(alive) 14 | alive = false 15 | } 16 | 17 | fun checkAlive() { 18 | if (!alive) throw AssertionError("dangling pointer") 19 | } 20 | 21 | fun count(): Int { 22 | return type.count() 23 | } 24 | 25 | operator fun get(offset: Int): Value { 26 | checkAlive() 27 | return memory[offset] 28 | } 29 | 30 | operator fun set(offset: Int, newValue: Value) { 31 | checkAlive() 32 | if (readOnlyErrorMessage != null) throw AssertionError(readOnlyErrorMessage) 33 | val oldValue = memory[offset] 34 | assert(oldValue === IndeterminateValue || oldValue.type() == newValue.type()) { 35 | "$oldValue === $IndeterminateValue || ${oldValue.type()} == ${newValue.type()}" 36 | } 37 | memory[offset] = newValue 38 | } 39 | 40 | operator fun set(obj: Object, newValue: Value) { 41 | set(obj.offset, newValue) 42 | } 43 | 44 | fun replace(offset: Int, count: Int, source: Segment, sourceOffset: Int) { 45 | checkAlive() 46 | if (readOnlyErrorMessage != null) throw AssertionError(readOnlyErrorMessage) 47 | source.checkAlive() 48 | 49 | val sourceMemory = source.memory 50 | for (i in 0 until count) { 51 | memory[offset + i] = sourceMemory[sourceOffset + i] 52 | } 53 | } 54 | 55 | companion object { 56 | private var state = System.currentTimeMillis().toInt() 57 | 58 | // generates 4,294,967,296 unique addresses before repeating 59 | fun randomAddress(): Int { 60 | state = state * 214013 + 2531011 61 | return state 62 | } 63 | } 64 | 65 | val address = randomAddress() 66 | } 67 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Pointer.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import interpreter.Value 4 | 5 | interface ComparablePointerType : Type 6 | 7 | data class PointerType(val referencedType: Type) : ComparablePointerType { 8 | override fun sizeof(): Int = 4 9 | 10 | override fun canCastFromDecayed(source: Type): Boolean { 11 | if (source === VoidPointerType) return true 12 | 13 | if (source === ConstVoidPointerType) return referencedType.isConst() 14 | 15 | return canCastFromPointer(source) 16 | } 17 | 18 | private fun canCastFromPointer(source: Type): Boolean { 19 | if (source !is PointerType) return false 20 | 21 | val sourceReferenced = source.referencedType 22 | return referencedType == sourceReferenced || referencedType.unqualified() == sourceReferenced 23 | } 24 | 25 | override fun cast(source: Value): Value { 26 | if (!canCastFromPointer(source.type().decayed())) 27 | throw AssertionError("${source.type()}\n cannot be converted to \n$this") 28 | 29 | return source.decayed() 30 | } 31 | 32 | override fun toString(): String = declaration("") 33 | 34 | override fun declaration(parent: String): String { 35 | return referencedType.declaration("*$parent") 36 | } 37 | } 38 | 39 | object VoidPointerType : ComparablePointerType { 40 | override fun sizeof(): Int = 4 41 | 42 | override fun canCastFromDecayed(source: Type): Boolean { 43 | if (source === this) return true 44 | return source is PointerType && !source.referencedType.isConst() 45 | } 46 | 47 | override fun toString(): String = "void*" 48 | 49 | override fun declaration(parent: String): String = "void*$parent" 50 | } 51 | 52 | object ConstVoidPointerType : ComparablePointerType { 53 | override fun sizeof(): Int = 4 54 | 55 | override fun canCastFromDecayed(source: Type): Boolean { 56 | if (source === this || source === VoidPointerType) return true 57 | return source is PointerType 58 | } 59 | 60 | override fun toString(): String = "const void*" 61 | 62 | override fun declaration(parent: String): String = "const void*$parent" 63 | } 64 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/TypeSpecifiers.kt: -------------------------------------------------------------------------------- 1 | package semantic 2 | 3 | import semantic.types.* 4 | import syntax.lexer.TokenKind.* 5 | import syntax.lexer.TokenKindSet 6 | 7 | val enumStructUnion = TokenKindSet.of(ENUM, STRUCT, UNION) 8 | val storageClasses = TokenKindSet.of(TYPEDEF, EXTERN, STATIC, AUTO, REGISTER) 9 | 10 | val typeSpecifierIdentifier = TokenKindSet.of(IDENTIFIER) 11 | 12 | val typeSpecifiers = mapOf( 13 | TokenKindSet.of(VOID) to VoidType, 14 | TokenKindSet.of(CHAR) to SignedCharType, 15 | TokenKindSet.of(SIGNED, CHAR) to SignedCharType, 16 | TokenKindSet.of(UNSIGNED, CHAR) to UnsignedCharType, 17 | TokenKindSet.of(SHORT) to SignedShortType, 18 | TokenKindSet.of(SHORT, INT) to SignedShortType, 19 | TokenKindSet.of(SIGNED, SHORT) to SignedShortType, 20 | TokenKindSet.of(SIGNED, SHORT, INT) to SignedShortType, 21 | TokenKindSet.of(UNSIGNED, SHORT) to UnsignedShortType, 22 | TokenKindSet.of(UNSIGNED, SHORT, INT) to UnsignedShortType, 23 | TokenKindSet.of(INT) to SignedIntType, 24 | TokenKindSet.of(SIGNED) to SignedIntType, 25 | TokenKindSet.of(SIGNED, INT) to SignedIntType, 26 | TokenKindSet.of(UNSIGNED) to UnsignedIntType, 27 | TokenKindSet.of(UNSIGNED, INT) to UnsignedIntType, 28 | TokenKindSet.of(LONG) to SignedIntType, 29 | TokenKindSet.of(LONG, INT) to SignedIntType, 30 | TokenKindSet.of(SIGNED, LONG) to SignedIntType, 31 | TokenKindSet.of(SIGNED, LONG, INT) to SignedIntType, 32 | TokenKindSet.of(UNSIGNED, LONG) to UnsignedIntType, 33 | TokenKindSet.of(UNSIGNED, LONG, INT) to UnsignedIntType, 34 | TokenKindSet.of(FLOAT) to FloatType, 35 | TokenKindSet.of(DOUBLE) to DoubleType, 36 | TokenKindSet.of(ENUM) to Later, 37 | TokenKindSet.of(STRUCT) to Later, 38 | typeSpecifierIdentifier to Later, 39 | ) 40 | 41 | fun main() { 42 | var spread = 0x1_0000_0001 43 | while (!perfect(spread)) { 44 | spread += 2 45 | } 46 | println("return (bits * 0x%x).ushr(32).toInt()".format(spread)) 47 | } 48 | 49 | private fun perfect(spread: Long): Boolean { 50 | var set = 0 51 | for (key in typeSpecifiers.keys) { 52 | val hash = (key.bits * spread).ushr(32).toInt() 53 | val index = hash xor (hash ushr 16) and 31 54 | val mask = 1 shl index 55 | if (set and mask != 0) return false 56 | set = set or mask 57 | } 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/FlatStatements.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import syntax.lexer.Token 4 | import syntax.lexer.missingIdentifier 5 | import syntax.tree.DeclarationSpecifiers 6 | import syntax.tree.Expression 7 | import syntax.tree.NamedDeclarator 8 | 9 | sealed class FlatStatement { 10 | abstract fun root(): Token 11 | 12 | open fun forEachSuccessor(action: (String) -> Unit) { 13 | } 14 | } 15 | 16 | sealed class TransferringControl : FlatStatement() 17 | 18 | class Jump(val keyword: Token, val target: String) : TransferringControl() { 19 | override fun root(): Token = keyword 20 | 21 | override fun forEachSuccessor(action: (String) -> Unit) { 22 | action(target) 23 | } 24 | } 25 | 26 | class ImplicitContinue(val keyword: Token, val target: String) : TransferringControl() { 27 | override fun root(): Token = keyword 28 | 29 | override fun forEachSuccessor(action: (String) -> Unit) { 30 | action(target) 31 | } 32 | } 33 | 34 | class JumpIf(val condition: Expression, val th3n: String, val e1se: String) : TransferringControl() { 35 | override fun root(): Token = condition.root() 36 | 37 | override fun forEachSuccessor(action: (String) -> Unit) { 38 | action(th3n) 39 | action(e1se) 40 | } 41 | } 42 | 43 | object SwitchPlaceholder : TransferringControl() { 44 | override fun root(): Token = missingIdentifier 45 | } 46 | 47 | class HashSwitch(val control: Expression, val cases: HashMap, val default: String) : 48 | TransferringControl() { 49 | override fun root(): Token = control.root() 50 | 51 | override fun forEachSuccessor(action: (String) -> Unit) { 52 | cases.values.forEach { action(it) } 53 | action(default) 54 | } 55 | } 56 | 57 | class FlatDeclaration(val specifiers: DeclarationSpecifiers, val namedDeclarators: List) : 58 | FlatStatement() { 59 | override fun root(): Token = specifiers.root() 60 | } 61 | 62 | class FlatExpressionStatement(val expression: Expression) : FlatStatement() { 63 | override fun root(): Token = expression.root() 64 | } 65 | 66 | class FlatReturn(val r3turn: Token, val result: Expression?) : TransferringControl() { 67 | override fun root(): Token = r3turn 68 | } 69 | 70 | class FlatAssert(val condition: Expression) : FlatStatement() { 71 | override fun root(): Token = condition.root() 72 | } 73 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/TokenKindSet.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import java.lang.Long.lowestOneBit 4 | import java.lang.Long.numberOfTrailingZeros 5 | 6 | private val TokenKind.bitmask: Long 7 | get() { 8 | assert(ordinal < Long.SIZE_BITS) { "$this@$ordinal is not among the first ${Long.SIZE_BITS} enum constants" } 9 | return 1L shl ordinal 10 | } 11 | 12 | class TokenKindSet(val bits: Long) { 13 | companion object { 14 | val EMPTY = TokenKindSet(0L) 15 | 16 | fun of(kind: TokenKind): TokenKindSet { 17 | return TokenKindSet(kind.bitmask) 18 | } 19 | 20 | fun of(kind1: TokenKind, kind2: TokenKind): TokenKindSet { 21 | return TokenKindSet(kind1.bitmask or kind2.bitmask) 22 | } 23 | 24 | fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind): TokenKindSet { 25 | return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask) 26 | } 27 | 28 | fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind): TokenKindSet { 29 | return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask or kind4.bitmask) 30 | } 31 | 32 | fun of(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind, kind5: TokenKind): TokenKindSet { 33 | return TokenKindSet(kind1.bitmask or kind2.bitmask or kind3.bitmask or kind4.bitmask or kind5.bitmask) 34 | } 35 | } 36 | 37 | fun contains(kind: TokenKind): Boolean { 38 | return (bits and kind.bitmask) != 0L 39 | } 40 | 41 | operator fun plus(kind: TokenKind): TokenKindSet { 42 | return TokenKindSet(bits or kind.bitmask) 43 | } 44 | 45 | fun isEmpty(): Boolean { 46 | return bits == 0L 47 | } 48 | 49 | fun first(): TokenKind { 50 | return TokenKind.entries[numberOfTrailingZeros(bits)] 51 | } 52 | 53 | override fun equals(other: Any?): Boolean { 54 | return other is TokenKindSet && this.bits == other.bits 55 | } 56 | 57 | override fun hashCode(): Int { 58 | return (bits * 0x10418282f).ushr(32).toInt() 59 | } 60 | 61 | override fun toString(): String { 62 | return generateSequence(bits) { b -> b xor lowestOneBit(b) } 63 | .takeWhile { b -> b != 0L } 64 | .map { b -> TokenKind.entries[numberOfTrailingZeros(b)] } 65 | .joinToString(", ", "[", "]") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/LeftDenotations.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import syntax.lexer.Token 4 | import syntax.lexer.TokenKind.* 5 | import syntax.tree.* 6 | 7 | abstract class LeftDenotation(val precedence: Int) { 8 | abstract fun Parser.parse(left: Expression, operator: Token): Expression 9 | } 10 | 11 | object SubscriptDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { 12 | override fun Parser.parse(left: Expression, operator: Token): Expression { 13 | return Subscript(left, operator, expression() before CLOSING_BRACKET) 14 | } 15 | } 16 | 17 | object FunctionCallDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { 18 | override fun Parser.parse(left: Expression, operator: Token): Expression { 19 | return FunctionCall(left, commaSeparatedList0(CLOSING_PAREN, ::functionCallArgument) before CLOSING_PAREN) 20 | } 21 | } 22 | 23 | object DirectMemberDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { 24 | override fun Parser.parse(left: Expression, operator: Token): Expression { 25 | return DirectMemberAccess(left, operator, expect(IDENTIFIER)) 26 | } 27 | } 28 | 29 | object IndirectMemberDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { 30 | override fun Parser.parse(left: Expression, operator: Token): Expression { 31 | return IndirectMemberAccess(left, operator, expect(IDENTIFIER)) 32 | } 33 | } 34 | 35 | object PostfixCrementDenotation : LeftDenotation(PRECEDENCE_POSTFIX) { 36 | override fun Parser.parse(left: Expression, operator: Token): Expression { 37 | return Postfix(left, operator) 38 | } 39 | } 40 | 41 | class LeftAssociativeDenotation(precedence: Int, val factory: (Expression, Token, Expression) -> Expression) : 42 | LeftDenotation(precedence) { 43 | override fun Parser.parse(left: Expression, operator: Token): Expression { 44 | return factory(left, operator, subexpression(precedence)) 45 | } 46 | } 47 | 48 | class RightAssociativeDenotation(precedence: Int, val factory: (Expression, Token, Expression) -> Expression) : 49 | LeftDenotation(precedence) { 50 | override fun Parser.parse(left: Expression, operator: Token): Expression { 51 | return factory(left, operator, subexpression(precedence - 1)) 52 | } 53 | } 54 | 55 | class ConditionalDenotation(precedence: Int) : LeftDenotation(precedence) { 56 | override fun Parser.parse(left: Expression, operator: Token): Expression { 57 | return Conditional(left, operator, expression(), expect(COLON), subexpression(precedence - 1)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/Declarator.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import semantic.types.Later 4 | import semantic.types.Type 5 | import syntax.lexer.Token 6 | import syntax.lexer.hiddenIdentifier 7 | 8 | class NamedDeclarator(val name: Token, val declarator: Declarator) : Node() { 9 | override fun forEachChild(action: (Node) -> Unit) { 10 | if (declarator is Declarator.Initialized) { 11 | action(declarator.init) 12 | } 13 | } 14 | 15 | override fun root(): Token = name 16 | 17 | var type: Type = Later 18 | var offset: Int = 1234567890 19 | 20 | fun hidden(): NamedDeclarator { 21 | val result = NamedDeclarator(hiddenIdentifier, declarator) 22 | result.type = type 23 | result.offset = offset 24 | return result 25 | } 26 | 27 | override fun toString(): String = "$name : $type" 28 | 29 | inline fun map(f: (Declarator) -> Declarator): NamedDeclarator = NamedDeclarator(name, f(declarator)) 30 | } 31 | 32 | class FunctionParameter(val specifiers: DeclarationSpecifiers, val namedDeclarator: NamedDeclarator) 33 | 34 | sealed class Declarator { 35 | fun leaf(): Declarator = leaf(Identity) 36 | 37 | protected abstract fun leaf(parent: Declarator): Declarator 38 | 39 | object Identity : Declarator() { 40 | override fun leaf(parent: Declarator): Declarator = parent 41 | } 42 | 43 | class Pointer(val child: Declarator, val qualifiers: List) : Declarator() { 44 | override fun leaf(parent: Declarator): Declarator = child.leaf(this) 45 | } 46 | 47 | class Array(val child: Declarator, val size: Expression?) : Declarator() { 48 | override fun leaf(parent: Declarator): Declarator = child.leaf(this) 49 | } 50 | 51 | class Function(val child: Declarator, val parameters: List) : Declarator() { 52 | override fun leaf(parent: Declarator): Declarator = child.leaf(this) 53 | } 54 | 55 | class Initialized(val declarator: Declarator, val init: Initializer) : Declarator() { 56 | override fun leaf(parent: Declarator): Declarator = declarator.leaf(Identity) 57 | } 58 | } 59 | 60 | abstract class Initializer : Node() 61 | 62 | class ExpressionInitializer(val expression: Expression) : Initializer() { 63 | override fun forEachChild(action: (Node) -> Unit) { 64 | action(expression) 65 | } 66 | 67 | override fun root(): Token = expression.root() 68 | } 69 | 70 | class InitializerList(val openBrace: Token, val list: List) : Initializer() { 71 | override fun forEachChild(action: (Node) -> Unit) { 72 | list.forEach(action) 73 | } 74 | 75 | override fun root(): Token = openBrace 76 | } 77 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/Numbers.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import syntax.lexer.TokenKind.* 4 | 5 | fun Lexer.constant(): Token { 6 | var seenDecimalPoint = false 7 | while (true) { 8 | when (next()) { 9 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> { 10 | } 11 | 12 | 'f', 'F' -> { 13 | next() 14 | return token(FLOAT_CONSTANT) 15 | } 16 | 17 | '.' -> { 18 | if (seenDecimalPoint) return token(DOUBLE_CONSTANT) 19 | seenDecimalPoint = true 20 | } 21 | 22 | else -> return token(if (seenDecimalPoint) DOUBLE_CONSTANT else INTEGER_CONSTANT) 23 | } 24 | } 25 | } 26 | 27 | fun Lexer.zero(): Token { 28 | next() 29 | if (current == 'x' || current == 'X') return hexadecimal() 30 | if (current == 'b' || current == 'B') return binary() 31 | previous() 32 | 33 | var seen8or9 = false 34 | var seenDecimalPoint = false 35 | while (true) { 36 | when (next()) { 37 | '0', '1', '2', '3', '4', '5', '6', '7' -> { 38 | } 39 | 40 | '8', '9' -> { 41 | seen8or9 = true 42 | } 43 | 44 | 'f', 'F' -> { 45 | next() 46 | return token(FLOAT_CONSTANT) 47 | } 48 | 49 | '.' -> { 50 | if (seenDecimalPoint) return token(DOUBLE_CONSTANT) 51 | seenDecimalPoint = true 52 | } 53 | 54 | else -> { 55 | if (seenDecimalPoint) return token(DOUBLE_CONSTANT) 56 | if (!seen8or9) return token(INTEGER_CONSTANT) 57 | 58 | error("octal literal indicated by leading digit 0 cannot contain digit 8 or 9") 59 | } 60 | } 61 | } 62 | } 63 | 64 | fun Lexer.hexadecimal(): Token { 65 | while (true) { 66 | when (next()) { 67 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 68 | 'A', 'B', 'C', 'D', 'E', 'F', 69 | 'a', 'b', 'c', 'd', 'e', 'f' -> { 70 | } 71 | 72 | else -> { 73 | if (index - start > 2) return token(INTEGER_CONSTANT) 74 | 75 | error("hexadecimal literal indicated by leading ${lexeme()} must contain at least one digit") 76 | } 77 | } 78 | } 79 | } 80 | 81 | fun Lexer.binary(): Token { 82 | while (true) { 83 | when (next()) { 84 | '0', '1' -> { 85 | } 86 | 87 | else -> { 88 | if (index - start > 2) return token(INTEGER_CONSTANT) 89 | 90 | error("binary literal indicated by leading ${lexeme()} must contain at least one digit") 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/TokenKind.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | enum class TokenKind(val lexeme: String) { 4 | ASSERT("assert"), 5 | AUTO("auto"), 6 | BREAK("break"), 7 | CASE("case"), 8 | CHAR("char"), 9 | CONST("const"), 10 | CONTINUE("continue"), 11 | DEFAULT("default"), 12 | DO("do"), 13 | DOUBLE("double"), 14 | ELSE("else"), 15 | ENUM("enum"), 16 | EXTERN("extern"), 17 | FLOAT("float"), 18 | FOR("for"), 19 | GOTO("goto"), 20 | IF("if"), 21 | INT("int"), 22 | LONG("long"), 23 | REGISTER("register"), 24 | RETURN("return"), 25 | SHORT("short"), 26 | SIGNED("signed"), 27 | SIZEOF("sizeof"), 28 | STATIC("static"), 29 | STRUCT("struct"), 30 | SWITCH("switch"), 31 | TYPEDEF("typedef"), 32 | UNION("union"), 33 | UNSIGNED("unsigned"), 34 | VOID("void"), 35 | VOLATILE("volatile"), 36 | WHILE("while"), 37 | 38 | FLOAT_CONSTANT("FLOAT CONSTANT"), 39 | DOUBLE_CONSTANT("DOUBLE CONSTANT"), 40 | INTEGER_CONSTANT("INTEGER CONSTANT"), 41 | CHARACTER_CONSTANT("CHARACTER CONSTANT"), 42 | STRING_LITERAL("STRING LITERAL"), 43 | IDENTIFIER("IDENTIFIER"), 44 | PRINTF("PRINTF"), 45 | SCANF("SCANF"), 46 | END_OF_INPUT("END OF INPUT"), 47 | 48 | OPENING_BRACKET("["), 49 | CLOSING_BRACKET("]"), 50 | OPENING_PAREN("("), 51 | CLOSING_PAREN(")"), 52 | DOT("."), 53 | HYPHEN_MORE("->"), 54 | PLUS_PLUS("++"), 55 | HYPHEN_HYPHEN("--"), 56 | AMPERSAND("&"), 57 | ASTERISK("*"), 58 | PLUS("+"), 59 | HYPHEN("-"), 60 | TILDE("~"), 61 | BANG("!"), 62 | SLASH("/"), 63 | PERCENT("%"), 64 | LESS_LESS("<<"), 65 | MORE_MORE(">>"), 66 | LESS("<"), 67 | MORE(">"), 68 | LESS_EQUAL("<="), 69 | MORE_EQUAL(">="), 70 | EQUAL_EQUAL("=="), 71 | BANG_EQUAL("!="), 72 | CARET("^"), 73 | BAR("|"), 74 | AMPERSAND_AMPERSAND("&&"), 75 | BAR_BAR("||"), 76 | QUESTION("?"), 77 | COLON(":"), 78 | EQUAL("="), 79 | ASTERISK_EQUAL("*="), 80 | SLASH_EQUAL("/="), 81 | PERCENT_EQUAL("%="), 82 | PLUS_EQUAL("+="), 83 | HYPHEN_EQUAL("-="), 84 | LESS_LESS_EQUAL("<<="), 85 | MORE_MORE_EQUAL(">>="), 86 | AMPERSAND_EQUAL("&="), 87 | CARET_EQUAL("^="), 88 | BAR_EQUAL("|="), 89 | COMMA(","), 90 | OPENING_BRACE("{"), 91 | CLOSING_BRACE("}"), 92 | SEMICOLON(";"); 93 | 94 | override fun toString(): String = lexeme 95 | 96 | companion object { 97 | val KEYWORDS = entries.subList(ASSERT.ordinal, WHILE.ordinal + 1) 98 | val OPERATORS_SEPARATORS = entries.subList(OPENING_BRACKET.ordinal, SEMICOLON.ordinal + 1) 99 | } 100 | } 101 | 102 | val keywords: Map = TokenKind.KEYWORDS.associateBy(TokenKind::lexeme) 103 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/NullDenotations.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import syntax.lexer.Token 4 | import syntax.lexer.TokenKind.* 5 | import syntax.tree.* 6 | 7 | abstract class NullDenotation { 8 | abstract fun Parser.parse(token: Token): Expression 9 | } 10 | 11 | object IdentifierDenotation : NullDenotation() { 12 | override fun Parser.parse(token: Token): Expression { 13 | return when (token.text) { 14 | "printf" -> parenthesized { printfCall(token.withTokenKind(PRINTF)) } 15 | "scanf" -> parenthesized { scanfCall(token.withTokenKind(SCANF)) } 16 | 17 | else -> Identifier(token) 18 | } 19 | } 20 | 21 | private fun Parser.printfCall(printf: Token): Expression { 22 | val format = expect(STRING_LITERAL) 23 | val arguments = if (current == COMMA) { 24 | next() 25 | commaSeparatedList1(::assignmentExpression) 26 | } else { 27 | emptyList() 28 | } 29 | return PrintfCall(printf, format, arguments) 30 | } 31 | 32 | private fun Parser.scanfCall(scanf: Token): Expression { 33 | val format = expect(STRING_LITERAL) 34 | val arguments = if (current == COMMA) { 35 | next() 36 | commaSeparatedList1(::assignmentExpression) 37 | } else { 38 | emptyList() 39 | } 40 | return ScanfCall(scanf, format, arguments) 41 | } 42 | } 43 | 44 | object ConstantDenotation : NullDenotation() { 45 | override fun Parser.parse(token: Token): Expression { 46 | return Constant(token) 47 | } 48 | } 49 | 50 | object StringLiteralDenotation : NullDenotation() { 51 | override fun Parser.parse(token: Token): Expression { 52 | return StringLiteral(token) 53 | } 54 | } 55 | 56 | object PossibleCastDenotation : NullDenotation() { 57 | override fun Parser.parse(token: Token): Expression { 58 | val specifiers = declarationSpecifiers0() 59 | return if (specifiers.list.isEmpty()) { 60 | expression() before CLOSING_PAREN 61 | } else { 62 | val declarator = abstractDeclarator() before CLOSING_PAREN 63 | Cast(token, specifiers, declarator, subexpression(PRECEDENCE_PREFIX)) 64 | } 65 | } 66 | } 67 | 68 | object PrefixDenotation : NullDenotation() { 69 | override fun Parser.parse(token: Token): Expression { 70 | val operand = subexpression(PRECEDENCE_PREFIX) 71 | return when (token.kind) { 72 | PLUS_PLUS, HYPHEN_HYPHEN -> Prefix(token, operand) 73 | AMPERSAND -> Reference(token, operand) 74 | ASTERISK -> Dereference(token, operand) 75 | PLUS -> UnaryPlus(token, operand) 76 | HYPHEN -> UnaryMinus(token, operand) 77 | TILDE -> BitwiseNot(token, operand) 78 | BANG -> LogicalNot(token, operand) 79 | 80 | else -> error("no parse for $token") 81 | } 82 | } 83 | } 84 | 85 | object SizeofDenotation : NullDenotation() { 86 | override fun Parser.parse(token: Token): Expression { 87 | return if (current == OPENING_PAREN && isDeclarationSpecifier(lookahead)) { 88 | parenthesized { SizeofType(token, declarationSpecifiers0(), abstractDeclarator()) } 89 | } else { 90 | SizeofExpression(token, subexpression(PRECEDENCE_PREFIX)) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/Statements.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import common.Diagnostic 4 | import syntax.lexer.Token 5 | import syntax.lexer.TokenKind.* 6 | import syntax.tree.* 7 | 8 | fun Parser.statement(): Statement = when (current) { 9 | 10 | IF -> IfThenElse(accept(), condition(), statement(), optional(ELSE, ::statement)) 11 | 12 | SWITCH -> Switch(accept(), condition(), statement()) 13 | 14 | CASE -> Case(accept(), expression() before COLON, statement()) 15 | 16 | DEFAULT -> Default(accept() before COLON, statement()) 17 | 18 | WHILE -> While(accept(), condition(), statement()) 19 | 20 | DO -> Do(accept(), statement() before WHILE, condition()).semicolon() 21 | 22 | FOR -> symbolTable.scoped { 23 | val f0r = accept() before OPENING_PAREN 24 | For( 25 | f0r, 26 | forInit(f0r), 27 | ::expression optionalBefore SEMICOLON, 28 | ::expression optionalBefore CLOSING_PAREN, 29 | statement() 30 | ) 31 | } 32 | 33 | GOTO -> Goto(accept(), expect(IDENTIFIER)).semicolon() 34 | 35 | CONTINUE -> Continue(accept()).semicolon() 36 | 37 | BREAK -> Break(accept()).semicolon() 38 | 39 | RETURN -> Return(accept(), ::expression optionalBefore SEMICOLON) 40 | 41 | ASSERT -> Assert(accept(), expression()).semicolon() 42 | 43 | TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, CONST, VOLATILE, 44 | VOID, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, SIGNED, UNSIGNED, 45 | STRUCT, UNION, ENUM -> declaration() 46 | 47 | OPENING_BRACE -> symbolTable.scoped { 48 | Block(token, braced { 49 | list0Until(CLOSING_BRACE, ::statement) 50 | }) 51 | } 52 | 53 | IDENTIFIER -> when { 54 | lookahead.kind == COLON -> LabeledStatement(accept() before COLON, statement()) 55 | 56 | isTypedefName(token) -> declaration() 57 | 58 | else -> ExpressionStatement(expression()).semicolon() 59 | } 60 | 61 | SEMICOLON -> token.error("unexpected semicolon") 62 | 63 | else -> ExpressionStatement(expression()).semicolon() 64 | } 65 | 66 | private fun Parser.forInit(f0r: Token): Statement? = when (current) { 67 | SEMICOLON -> null.semicolon() 68 | 69 | TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, CONST, VOLATILE, 70 | VOID, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, SIGNED, UNSIGNED, 71 | STRUCT, UNION, ENUM -> forInitDeclaration(f0r) 72 | 73 | IDENTIFIER -> when { 74 | isTypedefName(token) -> forInitDeclaration(f0r) 75 | 76 | else -> forInitExpressionStatement(f0r) 77 | } 78 | 79 | else -> forInitExpressionStatement(f0r) 80 | } 81 | 82 | private const val FOR_LOOP_SYNTAX = "for (init; condition; update)\n ^ ^\nSemicolons, NOT commas!" 83 | 84 | private fun Parser.forInitDeclaration(f0r: Token): Statement { 85 | try { 86 | return declaration() 87 | } catch (diagnostic: Diagnostic) { 88 | if (diagnostic.message.endsWith(" was already declared elsewhere")) { 89 | f0r.error(FOR_LOOP_SYNTAX) 90 | } else { 91 | throw diagnostic 92 | } 93 | } 94 | } 95 | 96 | private fun Parser.forInitExpressionStatement(f0r: Token): Statement { 97 | val result = ExpressionStatement(expression()) 98 | if (current == CLOSING_PAREN) { 99 | f0r.error(FOR_LOOP_SYNTAX) 100 | } 101 | return result.semicolon() 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/DeclarationSpecifier.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import semantic.types.Later 4 | import semantic.types.Type 5 | import syntax.lexer.Token 6 | import syntax.lexer.TokenKind 7 | import syntax.lexer.TokenKind.ENUM 8 | import syntax.lexer.TokenKind.STRUCT 9 | import syntax.lexer.TokenKindSet 10 | 11 | sealed class DeclarationSpecifier : Node() { 12 | abstract fun kind(): TokenKind 13 | 14 | class Primitive(val token: Token) : DeclarationSpecifier() { 15 | override fun kind(): TokenKind = token.kind 16 | 17 | override fun root(): Token = token 18 | } 19 | 20 | class StructDef(val name: Token, val body: List) : DeclarationSpecifier() { 21 | override fun kind(): TokenKind = STRUCT 22 | 23 | override fun root(): Token = name 24 | 25 | override fun forEachChild(action: (Node) -> Unit) { 26 | body.forEach { action(it) } 27 | } 28 | 29 | override fun toString(): String = if (name.wasProvided()) "struct $name" else "struct" 30 | } 31 | 32 | class StructRef(val name: Token) : DeclarationSpecifier() { 33 | override fun kind(): TokenKind = STRUCT 34 | 35 | override fun root(): Token = name 36 | 37 | override fun toString(): String = "struct $name" 38 | } 39 | 40 | class EnumDef(val name: Token, val body: List) : DeclarationSpecifier() { 41 | override fun kind(): TokenKind = ENUM 42 | 43 | override fun root(): Token = name 44 | 45 | override fun forEachChild(action: (Node) -> Unit) { 46 | body.forEach { action(it) } 47 | } 48 | 49 | override fun toString(): String = if (name.wasProvided()) "enum $name" else "enum" 50 | } 51 | 52 | class EnumRef(val name: Token) : DeclarationSpecifier() { 53 | override fun kind(): TokenKind = ENUM 54 | 55 | override fun root(): Token = name 56 | 57 | override fun toString(): String = "enum $name" 58 | } 59 | } 60 | 61 | class StructDeclaration(val specifiers: DeclarationSpecifiers, val declarators: List) : Node() { 62 | override fun root(): Token = specifiers.root() 63 | 64 | override fun forEachChild(action: (Node) -> Unit) { 65 | declarators.forEach { action(it) } 66 | } 67 | 68 | override fun toString(): String = specifiers.toString() 69 | } 70 | 71 | class DeclarationSpecifiers( 72 | val list: List, 73 | val storageClass: TokenKind, 74 | val qualifiers: TokenKindSet, 75 | val typeTokens: TokenKindSet 76 | ) : Node() { 77 | var type: Type = Later 78 | 79 | override fun root(): Token = list[0].root() 80 | 81 | override fun forEachChild(action: (Node) -> Unit) { 82 | list.forEach { action(it) } 83 | } 84 | 85 | override fun toString(): String = list.joinToString(" ") 86 | 87 | fun defTagName(): Token? { 88 | when (typeTokens.first()) { 89 | ENUM -> list.forEach { if (it is DeclarationSpecifier.EnumDef && it.name.wasProvided()) return it.name } 90 | 91 | STRUCT -> list.forEach { if (it is DeclarationSpecifier.StructDef && it.name.wasProvided()) return it.name } 92 | 93 | else -> {} 94 | } 95 | return null 96 | } 97 | 98 | fun isDeclaratorOptional(): Boolean { 99 | return (storageClass != TokenKind.TYPEDEF) && when (typeTokens.first()) { 100 | ENUM -> true 101 | 102 | STRUCT -> list.any { it is DeclarationSpecifier.StructDef && it.name.wasProvided() } 103 | 104 | else -> false 105 | } 106 | } 107 | } 108 | 109 | class Enumerator(val name: Token, val init: Expression?) : Node() { 110 | override fun root(): Token = name 111 | } 112 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/Expressions.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import syntax.lexer.TokenKind 4 | import syntax.lexer.TokenKind.* 5 | import syntax.tree.* 6 | 7 | const val PRECEDENCE_POSTFIX = 150 8 | const val PRECEDENCE_PREFIX = 140 9 | const val PRECEDENCE_COMMA = 10 10 | 11 | fun Parser.condition(): Expression { 12 | return parenthesized(::expression) 13 | } 14 | 15 | @Suppress("NOTHING_TO_INLINE") 16 | inline fun Parser.expression(): Expression { 17 | return subexpression(outerPrecedence = 0) 18 | } 19 | 20 | @Suppress("NOTHING_TO_INLINE") 21 | inline fun Parser.assignmentExpression(): Expression { 22 | return subexpression(outerPrecedence = PRECEDENCE_COMMA) 23 | } 24 | 25 | fun Parser.functionCallArgument(): Expression { 26 | if (isDeclarationSpecifier(token)) { 27 | token.error("function call arguments require no types, \nas opposed to function definition parameters") 28 | } 29 | return subexpression(outerPrecedence = PRECEDENCE_COMMA) 30 | } 31 | 32 | fun Parser.subexpression(outerPrecedence: Int): Expression { 33 | val nullDenotation = nullDenotations[current.ordinal] ?: illegalStartOf("expression") 34 | 35 | return subexpression(with(nullDenotation) { parse(accept()) }, outerPrecedence) 36 | } 37 | 38 | tailrec fun Parser.subexpression(left: Expression, outerPrecedence: Int): Expression { 39 | val leftDenotation = leftDenotations[current.ordinal] ?: return left 40 | if (leftDenotation.precedence <= outerPrecedence) return left 41 | 42 | return subexpression(with(leftDenotation) { parse(left, accept()) }, outerPrecedence) 43 | } 44 | 45 | private val nullDenotations = arrayOfNulls(128).apply { 46 | this[IDENTIFIER] = IdentifierDenotation 47 | this[DOUBLE_CONSTANT, FLOAT_CONSTANT, INTEGER_CONSTANT, CHARACTER_CONSTANT] = ConstantDenotation 48 | this[STRING_LITERAL] = StringLiteralDenotation 49 | this[OPENING_PAREN] = PossibleCastDenotation 50 | this[PLUS_PLUS, HYPHEN_HYPHEN, AMPERSAND, ASTERISK, PLUS, HYPHEN, TILDE, BANG] = PrefixDenotation 51 | this[SIZEOF] = SizeofDenotation 52 | } 53 | 54 | private val leftDenotations = arrayOfNulls(128).apply { 55 | this[OPENING_BRACKET] = SubscriptDenotation 56 | this[OPENING_PAREN] = FunctionCallDenotation 57 | this[DOT] = DirectMemberDenotation 58 | this[HYPHEN_MORE] = IndirectMemberDenotation 59 | this[PLUS_PLUS, HYPHEN_HYPHEN] = PostfixCrementDenotation 60 | 61 | this[ASTERISK, SLASH, PERCENT] = LeftAssociativeDenotation(130, ::Multiplicative) 62 | this[PLUS] = LeftAssociativeDenotation(120, ::Plus) 63 | this[HYPHEN] = LeftAssociativeDenotation(120, ::Minus) 64 | this[LESS_LESS, MORE_MORE] = LeftAssociativeDenotation(110, ::Shift) 65 | this[LESS, MORE, LESS_EQUAL, MORE_EQUAL] = LeftAssociativeDenotation(100, ::RelationalEquality) 66 | this[EQUAL_EQUAL, BANG_EQUAL] = LeftAssociativeDenotation(90, ::RelationalEquality) 67 | this[AMPERSAND] = LeftAssociativeDenotation(80, ::Bitwise) 68 | this[CARET] = LeftAssociativeDenotation(70, ::Bitwise) 69 | this[BAR] = LeftAssociativeDenotation(60, ::Bitwise) 70 | 71 | this[AMPERSAND_AMPERSAND] = RightAssociativeDenotation(50, ::Logical) 72 | this[BAR_BAR] = RightAssociativeDenotation(40, ::Logical) 73 | this[QUESTION] = ConditionalDenotation(30) 74 | this[EQUAL] = RightAssociativeDenotation(20, ::Assignment) 75 | this[PLUS_EQUAL] = RightAssociativeDenotation(20, ::PlusAssignment) 76 | this[HYPHEN_EQUAL] = RightAssociativeDenotation(20, ::MinusAssignment) 77 | this[COMMA] = RightAssociativeDenotation(PRECEDENCE_COMMA, ::Comma) 78 | } 79 | 80 | private operator fun Array.set(index: TokenKind, value: V) { 81 | this[index.ordinal] = value 82 | } 83 | 84 | private operator fun Array.set(vararg indexes: TokenKind, value: V) { 85 | for (index in indexes) { 86 | this[index.ordinal] = value 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/kotlin/syntax/parser/AutocompletionTest.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import org.junit.jupiter.api.Assertions.assertEquals 4 | import org.junit.jupiter.api.Test 5 | 6 | class AutocompletionTest { 7 | @Test 8 | fun fullSuffix() { 9 | val actual = autocompleteIdentifier("int foo, bar, baz; int main() { f") 10 | assertEquals(listOf("oo"), actual) 11 | } 12 | 13 | @Test 14 | fun partialSuffix() { 15 | val actual = autocompleteIdentifier("int foo, bar, baz; int main() { b") 16 | assertEquals(listOf("a"), actual) 17 | } 18 | 19 | @Test 20 | fun ambiguous() { 21 | val actual = autocompleteIdentifier("int foo, bar, baz; int main() { ba") 22 | assertEquals(listOf("r", "z"), actual) 23 | } 24 | 25 | @Test 26 | fun alreadyComplete() { 27 | val actual = autocompleteIdentifier("int foo, bar, baz; int main() { foo") 28 | assertEquals(emptyList(), actual) 29 | } 30 | 31 | @Test 32 | fun parameter() { 33 | val actual = autocompleteIdentifier("double f(double number, double numbest) { num") 34 | assertEquals(listOf("be"), actual) 35 | } 36 | 37 | @Test 38 | fun local() { 39 | val actual = autocompleteIdentifier("double f() { double number, numbest; num") 40 | assertEquals(listOf("be"), actual) 41 | } 42 | 43 | @Test 44 | fun parameterAndLocal() { 45 | val actual = autocompleteIdentifier("double f(double number) { double numbest; num") 46 | assertEquals(listOf("be"), actual) 47 | } 48 | 49 | @Test 50 | fun globalAndLocal() { 51 | val actual = autocompleteIdentifier("double number; double f() { double numbest; num") 52 | assertEquals(listOf("be"), actual) 53 | } 54 | 55 | @Test 56 | fun nestedLocals() { 57 | val actual = autocompleteIdentifier("double f() { double number; { double numbest; num") 58 | assertEquals(listOf("be"), actual) 59 | } 60 | 61 | @Test 62 | fun outOfScope() { 63 | val actual = autocompleteIdentifier("double f() { double number; { double numbest; } num") 64 | assertEquals(listOf("ber"), actual) 65 | } 66 | 67 | @Test 68 | fun recursion() { 69 | val actual = autocompleteIdentifier("void foo() { f") 70 | assertEquals(listOf("oo"), actual) 71 | } 72 | 73 | @Test 74 | fun backwardCall() { 75 | val actual = autocompleteIdentifier("void foo() {} void bar() { f") 76 | assertEquals(listOf("oo"), actual) 77 | } 78 | 79 | @Test 80 | fun forwardCall() { 81 | val actual = autocompleteIdentifier("void foo(); void bar() { f") 82 | assertEquals(listOf("oo"), actual) 83 | } 84 | 85 | @Test 86 | fun afterMutualRecursion() { 87 | val actual = autocompleteIdentifier("void baz(); void bar() { baz(); } void baz() { bar(); } void foo() { b") 88 | assertEquals(listOf("a"), actual) 89 | } 90 | 91 | @Test 92 | fun enumerationConstant() { 93 | val actual = autocompleteIdentifier("enum { WORD_SIZE = sizeof W") 94 | assertEquals(listOf("ORD_SIZE"), actual) 95 | } 96 | 97 | @Test 98 | fun directStructAccess() { 99 | val actual = 100 | autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f(struct Person p) { p.n") 101 | assertEquals(listOf("ame"), actual) 102 | } 103 | 104 | @Test 105 | fun indirectStructAccess() { 106 | val actual = 107 | autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f(struct Person * p) { p->n") 108 | assertEquals(listOf("ame"), actual) 109 | } 110 | 111 | @Test 112 | fun ignoreStruct() { 113 | val actual = autocompleteIdentifier("struct Person { int age; char name[12]; }; int noob; void f() { n") 114 | assertEquals(listOf("oob"), actual) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/lexer/NextToken.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import syntax.lexer.TokenKind.* 4 | 5 | tailrec fun Lexer.nextToken(): Token { 6 | startAtIndex() 7 | return when (current) { 8 | ' ', '\u0009', '\u000a', '\u000b', '\u000c', '\u000d' -> { 9 | next() 10 | nextToken() 11 | } 12 | 13 | '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', 14 | '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', 15 | '_' -> identifierOrKeyword() 16 | 17 | '1', '2', '3', '4', '5', '6', '7', '8', '9' -> constant() 18 | '0' -> zero() 19 | 20 | '\'' -> characterConstant() 21 | 22 | '\"' -> stringLiteral() 23 | 24 | '(' -> nextVerbatim(OPENING_PAREN) 25 | ')' -> nextVerbatim(CLOSING_PAREN) 26 | ',' -> nextVerbatim(COMMA) 27 | '.' -> nextVerbatim(DOT) 28 | ':' -> nextVerbatim(COLON) 29 | ';' -> nextVerbatim(SEMICOLON) 30 | '?' -> nextVerbatim(QUESTION) 31 | '[' -> nextVerbatim(OPENING_BRACKET) 32 | ']' -> nextVerbatim(CLOSING_BRACKET) 33 | '{' -> nextVerbatim(OPENING_BRACE) 34 | '}' -> nextVerbatim(CLOSING_BRACE) 35 | '~' -> nextVerbatim(TILDE) 36 | 37 | '!' -> when (next()) { 38 | '=' -> nextVerbatim(BANG_EQUAL) 39 | else -> verbatim(BANG) 40 | } 41 | 42 | '%' -> when (next()) { 43 | '=' -> nextVerbatim(PERCENT_EQUAL) 44 | else -> verbatim(PERCENT) 45 | } 46 | 47 | '&' -> when (next()) { 48 | '=' -> nextVerbatim(AMPERSAND_EQUAL) 49 | '&' -> nextVerbatim(AMPERSAND_AMPERSAND) 50 | else -> verbatim(AMPERSAND) 51 | } 52 | 53 | '*' -> when (next()) { 54 | '=' -> nextVerbatim(ASTERISK_EQUAL) 55 | else -> verbatim(ASTERISK) 56 | } 57 | 58 | '+' -> when (next()) { 59 | '=' -> nextVerbatim(PLUS_EQUAL) 60 | '+' -> nextVerbatim(PLUS_PLUS) 61 | else -> verbatim(PLUS) 62 | } 63 | 64 | '-' -> when (next()) { 65 | '=' -> nextVerbatim(HYPHEN_EQUAL) 66 | '-' -> nextVerbatim(HYPHEN_HYPHEN) 67 | '>' -> nextVerbatim(HYPHEN_MORE) 68 | else -> verbatim(HYPHEN) 69 | } 70 | 71 | '/' -> when (next()) { 72 | '/' -> { 73 | skipSingleLineComment() 74 | nextToken() 75 | } 76 | 77 | '*' -> { 78 | skipMultiLineComment() 79 | nextToken() 80 | } 81 | 82 | '=' -> nextVerbatim(SLASH_EQUAL) 83 | 84 | else -> verbatim(SLASH) 85 | } 86 | 87 | '<' -> when (next()) { 88 | '=' -> nextVerbatim(LESS_EQUAL) 89 | 90 | '<' -> when (next()) { 91 | '=' -> nextVerbatim(LESS_LESS_EQUAL) 92 | else -> verbatim(LESS_LESS) 93 | } 94 | 95 | else -> verbatim(LESS) 96 | } 97 | 98 | '=' -> when (next()) { 99 | '=' -> nextVerbatim(EQUAL_EQUAL) 100 | else -> verbatim(EQUAL) 101 | } 102 | 103 | '>' -> when (next()) { 104 | '=' -> nextVerbatim(MORE_EQUAL) 105 | 106 | '>' -> when (next()) { 107 | '=' -> nextVerbatim(MORE_MORE_EQUAL) 108 | else -> verbatim(MORE_MORE) 109 | } 110 | 111 | else -> verbatim(MORE) 112 | } 113 | 114 | '^' -> when (next()) { 115 | '=' -> nextVerbatim(CARET_EQUAL) 116 | else -> verbatim(CARET) 117 | } 118 | 119 | '|' -> when (next()) { 120 | '=' -> nextVerbatim(BAR_EQUAL) 121 | '|' -> nextVerbatim(BAR_BAR) 122 | else -> verbatim(BAR) 123 | } 124 | 125 | EOF -> verbatim(END_OF_INPUT) 126 | 127 | else -> error("illegal input character") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/SymbolTable.kt: -------------------------------------------------------------------------------- 1 | package semantic 2 | 3 | import semantic.types.FunctionType 4 | import semantic.types.Type 5 | import syntax.lexer.Token 6 | import syntax.tree.Identifier 7 | 8 | data class Symbol(val name: Token, val type: Type, val offset: Int) { 9 | val usages = ArrayList() 10 | 11 | override fun toString(): String = name.text 12 | } 13 | 14 | class SymbolTable { 15 | private val scopes = Array>(128) { HashMap() } 16 | private var current = 0 17 | private val closedScopes = ArrayList>() 18 | private val allSymbols = ArrayList() 19 | 20 | fun atGlobalScope(): Boolean { 21 | return current == 0 22 | } 23 | 24 | fun openScope() { 25 | scopes[++current] = HashMap() 26 | } 27 | 28 | fun closeScope() { 29 | assert(!atGlobalScope()) { "Attempt to close the global scope" } 30 | if (current == 1) { 31 | closedScopes.clear() 32 | } else { 33 | closedScopes.add(scopes[current]) 34 | } 35 | --current 36 | } 37 | 38 | fun reopenScope() { 39 | ++current 40 | } 41 | 42 | inline fun scoped(action: () -> T): T { 43 | openScope() 44 | val result = action() 45 | closeScope() 46 | return result 47 | } 48 | 49 | inline fun rescoped(action: () -> T): T { 50 | reopenScope() 51 | val result = action() 52 | closeScope() 53 | return result 54 | } 55 | 56 | fun lookup(name: Token): Symbol? { 57 | val text = name.text 58 | for (i in current downTo 0) { 59 | scopes[i][text]?.let { symbol -> return symbol } 60 | } 61 | return null 62 | } 63 | 64 | fun lookupInClosedScopes(name: Token): Symbol? { 65 | val text = name.text 66 | for (i in closedScopes.lastIndex downTo 0) { 67 | closedScopes[i][text]?.let { symbol -> return symbol } 68 | } 69 | return null 70 | } 71 | 72 | fun declare(name: Token, type: Type, offset: Int): Symbol { 73 | return declareIn(scopes[current], name, type, offset) 74 | } 75 | 76 | fun declareOutside(name: Token, type: Type, offset: Int): Symbol { 77 | return declareIn(scopes[current - 1], name, type, offset) 78 | } 79 | 80 | private fun declareIn(scope: HashMap, name: Token, type: Type, offset: Int): Symbol { 81 | val text = name.text 82 | val previous = scope[text] 83 | if (previous != null) { 84 | if (previous.type is FunctionType && !previous.type.defined && type is FunctionType && type.defined) { 85 | if (previous.type == type) { 86 | previous.type.defined = true 87 | previous.usages.add(Identifier(name).also { it.symbol = previous }) 88 | return previous 89 | } else { 90 | name.error("function definition signature does not agree with function declaration signature") 91 | } 92 | } else { 93 | name.error("symbol $name already declared in current scope", previous.name) 94 | } 95 | } else { 96 | val symbol = Symbol(name, type, offset) 97 | scope[text] = symbol 98 | allSymbols.add(symbol) 99 | return symbol 100 | } 101 | } 102 | 103 | fun currentFunction(): Symbol? { 104 | return scopes[0].values.maxByOrNull { symbol -> symbol.name.start } 105 | } 106 | 107 | fun names(): Sequence = sequence { 108 | for (i in current downTo 0) { 109 | for ((_, symbol) in scopes[i]) { 110 | yield(symbol.name.text) 111 | } 112 | } 113 | } 114 | 115 | fun symbolAt(position: Int): Symbol? { 116 | for (symbol in allSymbols) { 117 | if (symbol.name.start <= position && position <= symbol.name.end) return symbol 118 | for (usage in symbol.usages) { 119 | if (usage.name.start <= position && position <= usage.name.end) return symbol 120 | } 121 | } 122 | return null 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/Statement.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import syntax.lexer.Token 4 | 5 | abstract class Statement : Node() 6 | 7 | class Declaration(val specifiers: DeclarationSpecifiers, val namedDeclarators: List) : Statement() { 8 | override fun forEachChild(action: (Node) -> Unit) { 9 | namedDeclarators.forEach(action) 10 | } 11 | 12 | override fun root(): Token = specifiers.root() 13 | 14 | override fun toString(): String = specifiers.toString() 15 | } 16 | 17 | class Block(val openBrace: Token, val statements: List) : Statement() { 18 | override fun forEachChild(action: (Node) -> Unit) { 19 | statements.forEach(action) 20 | } 21 | 22 | override fun root(): Token = openBrace 23 | } 24 | 25 | class ExpressionStatement(val expression: Expression) : Statement() { 26 | override fun forEachChild(action: (Node) -> Unit) { 27 | action(expression) 28 | } 29 | 30 | override fun root(): Token = expression.root() 31 | 32 | override fun toString(): String = "${super.toString()} ;" 33 | } 34 | 35 | class IfThenElse(val iF: Token, val condition: Expression, val th3n: Statement, val e1se: Statement?) : Statement() { 36 | override fun forEachChild(action: (Node) -> Unit) { 37 | action(condition) 38 | action(th3n) 39 | e1se?.let { action(it) } 40 | } 41 | 42 | override fun root(): Token = iF 43 | } 44 | 45 | class Switch(val switch: Token, val control: Expression, val body: Statement) : Statement() { 46 | override fun forEachChild(action: (Node) -> Unit) { 47 | action(control) 48 | action(body) 49 | } 50 | 51 | override fun root(): Token = switch 52 | } 53 | 54 | class Case(val case: Token, val choice: Expression, val body: Statement) : Statement() { 55 | override fun forEachChild(action: (Node) -> Unit) { 56 | action(choice) 57 | action(body) 58 | } 59 | 60 | override fun root(): Token = case 61 | } 62 | 63 | class Default(val default: Token, val body: Statement) : Statement() { 64 | override fun forEachChild(action: (Node) -> Unit) { 65 | action(body) 66 | } 67 | 68 | override fun root(): Token = default 69 | } 70 | 71 | class While(val whi1e: Token, val condition: Expression, val body: Statement) : Statement() { 72 | override fun forEachChild(action: (Node) -> Unit) { 73 | action(condition) 74 | action(body) 75 | } 76 | 77 | override fun root(): Token = whi1e 78 | } 79 | 80 | class Do(val d0: Token, val body: Statement, val condition: Expression) : Statement() { 81 | override fun forEachChild(action: (Node) -> Unit) { 82 | action(body) 83 | action(condition) 84 | } 85 | 86 | override fun root(): Token = d0 87 | } 88 | 89 | class For( 90 | val f0r: Token, 91 | val init: Statement?, 92 | val condition: Expression?, 93 | val update: Expression?, 94 | val body: Statement 95 | ) : Statement() { 96 | override fun forEachChild(action: (Node) -> Unit) { 97 | init?.run(action) 98 | condition?.run(action) 99 | update?.run(action) 100 | action(body) 101 | } 102 | 103 | override fun root(): Token = f0r 104 | } 105 | 106 | class Continue(val continu3: Token) : Statement() { 107 | override fun root(): Token = continu3 108 | } 109 | 110 | class Break(val br3ak: Token) : Statement() { 111 | override fun root(): Token = br3ak 112 | } 113 | 114 | class Return(val r3turn: Token, val result: Expression?) : Statement() { 115 | override fun forEachChild(action: (Node) -> Unit) { 116 | result?.run(action) 117 | } 118 | 119 | override fun root(): Token = r3turn 120 | } 121 | 122 | class Assert(val ass3rt: Token, val condition: Expression) : Statement() { 123 | override fun forEachChild(action: (Node) -> Unit) { 124 | action(condition) 125 | } 126 | 127 | override fun root(): Token = ass3rt 128 | } 129 | 130 | class LabeledStatement(val label: Token, val statement: Statement) : Statement() { 131 | override fun forEachChild(action: (Node) -> Unit) { 132 | action(statement) 133 | } 134 | 135 | override fun root(): Token = label 136 | } 137 | 138 | class Goto(val goto: Token, val label: Token) : Statement() { 139 | override fun root(): Token = goto 140 | } 141 | -------------------------------------------------------------------------------- /src/main/kotlin/text/Char.kt: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | fun Char.quote(): String = when (this) { 4 | '\u0000' -> "'\\0'" 5 | '\u0007' -> "'\\a'" 6 | '\u0008' -> "'\\b'" 7 | '\u0009' -> "'\\t'" 8 | '\u000a' -> "'\\n'" 9 | '\u000b' -> "'\\v'" 10 | '\u000c' -> "'\\f'" 11 | '\u000d' -> "'\\r'" 12 | ' ' -> "' '" 13 | '!' -> "'!'" 14 | '"' -> "'\"'" 15 | '#' -> "'#'" 16 | '$' -> "'$'" 17 | '%' -> "'%'" 18 | '&' -> "'&'" 19 | '\'' -> "'\\\''" 20 | '(' -> "'('" 21 | ')' -> "')'" 22 | '*' -> "'*'" 23 | '+' -> "'+'" 24 | ',' -> "','" 25 | '-' -> "'-'" 26 | '.' -> "'.'" 27 | '/' -> "'/'" 28 | '0' -> "'0'" 29 | '1' -> "'1'" 30 | '2' -> "'2'" 31 | '3' -> "'3'" 32 | '4' -> "'4'" 33 | '5' -> "'5'" 34 | '6' -> "'6'" 35 | '7' -> "'7'" 36 | '8' -> "'8'" 37 | '9' -> "'9'" 38 | ':' -> "':'" 39 | ';' -> "';'" 40 | '<' -> "'<'" 41 | '=' -> "'='" 42 | '>' -> "'>'" 43 | '?' -> "'?'" 44 | '@' -> "'@'" 45 | 'A' -> "'A'" 46 | 'B' -> "'B'" 47 | 'C' -> "'C'" 48 | 'D' -> "'D'" 49 | 'E' -> "'E'" 50 | 'F' -> "'F'" 51 | 'G' -> "'G'" 52 | 'H' -> "'H'" 53 | 'I' -> "'I'" 54 | 'J' -> "'J'" 55 | 'K' -> "'K'" 56 | 'L' -> "'L'" 57 | 'M' -> "'M'" 58 | 'N' -> "'N'" 59 | 'O' -> "'O'" 60 | 'P' -> "'P'" 61 | 'Q' -> "'Q'" 62 | 'R' -> "'R'" 63 | 'S' -> "'S'" 64 | 'T' -> "'T'" 65 | 'U' -> "'U'" 66 | 'V' -> "'V'" 67 | 'W' -> "'W'" 68 | 'X' -> "'X'" 69 | 'Y' -> "'Y'" 70 | 'Z' -> "'Z'" 71 | '[' -> "'['" 72 | '\\' -> "'\\\\'" 73 | ']' -> "']'" 74 | '^' -> "'^'" 75 | '_' -> "'_'" 76 | '`' -> "'`'" 77 | 'a' -> "'a'" 78 | 'b' -> "'b'" 79 | 'c' -> "'c'" 80 | 'd' -> "'d'" 81 | 'e' -> "'e'" 82 | 'f' -> "'f'" 83 | 'g' -> "'g'" 84 | 'h' -> "'h'" 85 | 'i' -> "'i'" 86 | 'j' -> "'j'" 87 | 'k' -> "'k'" 88 | 'l' -> "'l'" 89 | 'm' -> "'m'" 90 | 'n' -> "'n'" 91 | 'o' -> "'o'" 92 | 'p' -> "'p'" 93 | 'q' -> "'q'" 94 | 'r' -> "'r'" 95 | 's' -> "'s'" 96 | 't' -> "'t'" 97 | 'u' -> "'u'" 98 | 'v' -> "'v'" 99 | 'w' -> "'w'" 100 | 'x' -> "'x'" 101 | 'y' -> "'y'" 102 | 'z' -> "'z'" 103 | '{' -> "'{'" 104 | '|' -> "'|'" 105 | '}' -> "'}'" 106 | '~' -> "'~'" 107 | 108 | ' ' -> "' '" 109 | '¡' -> "'¡'" 110 | '¢' -> "'¢'" 111 | '£' -> "'£'" 112 | '¤' -> "'¤'" 113 | '¥' -> "'¥'" 114 | '¦' -> "'¦'" 115 | '§' -> "'§'" 116 | '¨' -> "'¨'" 117 | '©' -> "'©'" 118 | 'ª' -> "'ª'" 119 | '«' -> "'«'" 120 | '¬' -> "'¬'" 121 | '­' -> "'­'" 122 | '®' -> "'®'" 123 | '¯' -> "'¯'" 124 | '°' -> "'°'" 125 | '±' -> "'±'" 126 | '²' -> "'²'" 127 | '³' -> "'³'" 128 | '´' -> "'´'" 129 | 'µ' -> "'µ'" 130 | '¶' -> "'¶'" 131 | '·' -> "'·'" 132 | '¸' -> "'¸'" 133 | '¹' -> "'¹'" 134 | 'º' -> "'º'" 135 | '»' -> "'»'" 136 | '¼' -> "'¼'" 137 | '½' -> "'½'" 138 | '¾' -> "'¾'" 139 | '¿' -> "'¿'" 140 | 'À' -> "'À'" 141 | 'Á' -> "'Á'" 142 | 'Â' -> "'Â'" 143 | 'Ã' -> "'Ã'" 144 | 'Ä' -> "'Ä'" 145 | 'Å' -> "'Å'" 146 | 'Æ' -> "'Æ'" 147 | 'Ç' -> "'Ç'" 148 | 'È' -> "'È'" 149 | 'É' -> "'É'" 150 | 'Ê' -> "'Ê'" 151 | 'Ë' -> "'Ë'" 152 | 'Ì' -> "'Ì'" 153 | 'Í' -> "'Í'" 154 | 'Î' -> "'Î'" 155 | 'Ï' -> "'Ï'" 156 | 'Ð' -> "'Ð'" 157 | 'Ñ' -> "'Ñ'" 158 | 'Ò' -> "'Ò'" 159 | 'Ó' -> "'Ó'" 160 | 'Ô' -> "'Ô'" 161 | 'Õ' -> "'Õ'" 162 | 'Ö' -> "'Ö'" 163 | '×' -> "'×'" 164 | 'Ø' -> "'Ø'" 165 | 'Ù' -> "'Ù'" 166 | 'Ú' -> "'Ú'" 167 | 'Û' -> "'Û'" 168 | 'Ü' -> "'Ü'" 169 | 'Ý' -> "'Ý'" 170 | 'Þ' -> "'Þ'" 171 | 'ß' -> "'ß'" 172 | 'à' -> "'à'" 173 | 'á' -> "'á'" 174 | 'â' -> "'â'" 175 | 'ã' -> "'ã'" 176 | 'ä' -> "'ä'" 177 | 'å' -> "'å'" 178 | 'æ' -> "'æ'" 179 | 'ç' -> "'ç'" 180 | 'è' -> "'è'" 181 | 'é' -> "'é'" 182 | 'ê' -> "'ê'" 183 | 'ë' -> "'ë'" 184 | 'ì' -> "'ì'" 185 | 'í' -> "'í'" 186 | 'î' -> "'î'" 187 | 'ï' -> "'ï'" 188 | 'ð' -> "'ð'" 189 | 'ñ' -> "'ñ'" 190 | 'ò' -> "'ò'" 191 | 'ó' -> "'ó'" 192 | 'ô' -> "'ô'" 193 | 'õ' -> "'õ'" 194 | 'ö' -> "'ö'" 195 | '÷' -> "'÷'" 196 | 'ø' -> "'ø'" 197 | 'ù' -> "'ù'" 198 | 'ú' -> "'ú'" 199 | 'û' -> "'û'" 200 | 'ü' -> "'ü'" 201 | 'ý' -> "'ý'" 202 | 'þ' -> "'þ'" 203 | 'ÿ' -> "'ÿ'" 204 | 205 | else -> "'\\${Integer.toOctalString(code)}'" 206 | } 207 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/Memory.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import semantic.Symbol 4 | import semantic.types.ArrayType 5 | import semantic.types.SignedCharType 6 | import semantic.types.StructType 7 | import semantic.types.Type 8 | import syntax.lexer.fakeIdentifier 9 | import syntax.lexer.missingIdentifier 10 | import syntax.tree.NamedDeclarator 11 | import kotlin.math.min 12 | 13 | fun Iterable.synthesizeStringConstantsType(): StructType { 14 | val symbols = ArrayList() 15 | val type = StructType(fakeIdentifier("string literals"), symbols) 16 | var offset = 0 17 | for (str in this) { 18 | val size = str.length + 1 19 | symbols.add(Symbol(missingIdentifier, ArrayType(size, SignedCharType), offset)) 20 | offset += size 21 | } 22 | return type 23 | } 24 | 25 | fun Iterable.synthesizeStaticVariablesType(): StructType { 26 | val symbols = ArrayList() 27 | val type = StructType(fakeIdentifier("static variables"), symbols) 28 | for (namedDeclarator in this) { 29 | with(namedDeclarator) { 30 | symbols.add(Symbol(name, this.type, offset)) 31 | } 32 | } 33 | return type 34 | } 35 | 36 | class Memory(stringLiterals: Iterable, variables: Iterable) { 37 | val stringObjects = HashMap() 38 | 39 | val stringConstants = Segment(stringLiterals.synthesizeStringConstantsType()) 40 | val staticVariables = Segment(variables.synthesizeStaticVariablesType()) 41 | val stack = ArrayList() 42 | val heap = ArrayList() 43 | 44 | init { 45 | var stringOffset = 0 46 | for (stringLiteral in stringLiterals) { 47 | stringObjects[stringLiteral] = 48 | Object(stringConstants, stringOffset, ArrayType(stringLiteral.length + 1, SignedCharType), 0, 1) 49 | for (x in stringLiteral) { 50 | stringConstants[stringOffset++] = Value.signedChar(x) 51 | } 52 | stringConstants[stringOffset++] = Value.NUL 53 | } 54 | stringConstants.readOnlyErrorMessage = "attempt to modify a string literal" 55 | } 56 | 57 | fun makeObject(symbol: Symbol): Object { 58 | return with(symbol) { 59 | if (offset < 0) { 60 | Object(staticVariables, offset + Int.MIN_VALUE, type, 0, 1) 61 | } else { 62 | Object(stack.last(), offset, type, 0, 1) 63 | } 64 | } 65 | } 66 | 67 | fun popStackFrameUnlessEntryPoint() { 68 | if (stack.size > 1) { 69 | stack.removeAt(stack.lastIndex).kill() 70 | } 71 | } 72 | 73 | fun malloc(type: Type): PointerValue { 74 | val segment = Segment(type) 75 | heap.add(segment) 76 | return PointerValue(Object(segment, 0, type, 0, 1)) 77 | } 78 | 79 | fun malloc(arrayType: ArrayType): PointerValue { 80 | val segment = Segment(arrayType) 81 | heap.add(segment) 82 | return PointerValue(Object(segment, 0, arrayType.elementType, 0, arrayType.size)) 83 | } 84 | 85 | fun free(pointer: PointerValue) { 86 | val obj = pointer.referenced 87 | val segment = obj.segment 88 | 89 | if (!segment.alive) throw AssertionError("dangling pointer") 90 | if (segment !in heap) throw AssertionError("free on non-heap segment") 91 | if (obj.offset != 0) throw AssertionError("free in the middle of segment") 92 | 93 | segment.kill() 94 | heap.remove(segment) 95 | } 96 | 97 | fun realloc(pointer: PointerValue, type: Type): PointerValue { 98 | val oldSegment = pointer.referenced.segment 99 | val newSegment = Segment(type) 100 | val smallerCount = min(oldSegment.count(), newSegment.count()) 101 | for (i in 0 until smallerCount) { 102 | newSegment[i] = oldSegment[i] 103 | } 104 | free(pointer) 105 | heap.add(newSegment) 106 | return PointerValue(Object(newSegment, 0, type, 0, 1)) 107 | } 108 | 109 | fun realloc(pointer: PointerValue, arrayType: ArrayType): PointerValue { 110 | val oldSegment = pointer.referenced.segment 111 | val newSegment = Segment(arrayType) 112 | val smallerCount = min(oldSegment.count(), newSegment.count()) 113 | for (i in 0 until smallerCount) { 114 | newSegment[i] = oldSegment[i] 115 | } 116 | free(pointer) 117 | heap.add(newSegment) 118 | return PointerValue(Object(newSegment, 0, arrayType.elementType, 0, arrayType.size)) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/Parser.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import common.Diagnostic 4 | import semantic.SymbolTable 5 | import syntax.lexer.Lexer 6 | import syntax.lexer.Token 7 | import syntax.lexer.TokenKind 8 | import syntax.lexer.TokenKind.* 9 | import syntax.lexer.nextToken 10 | 11 | class Parser(private val lexer: Lexer) { 12 | var beforePrevious: Token = Token(END_OF_INPUT, 0, "", "") 13 | private set 14 | 15 | var previous: Token = Token(END_OF_INPUT, 0, "", "") 16 | private set 17 | 18 | var token: Token = lexer.nextToken() 19 | private set 20 | 21 | var current: TokenKind = token.kind 22 | private set 23 | 24 | var lookahead: Token = lexer.nextToken() 25 | private set 26 | 27 | fun next(): TokenKind { 28 | beforePrevious = previous 29 | previous = token 30 | token = lookahead 31 | current = token.kind 32 | lookahead = lexer.nextToken() 33 | return current 34 | } 35 | 36 | fun accept(): Token { 37 | val result = token 38 | next() 39 | return result 40 | } 41 | 42 | fun expect(expected: TokenKind): Token { 43 | if (current != expected) throw Diagnostic(previous.end, "expected $expected") 44 | return accept() 45 | } 46 | 47 | fun T.semicolon(): T { 48 | expect(SEMICOLON) 49 | return this 50 | } 51 | 52 | infix fun T.before(expected: TokenKind): T { 53 | expect(expected) 54 | return this 55 | } 56 | 57 | fun illegalStartOf(rule: String): Nothing { 58 | token.error("illegal start of $rule") 59 | } 60 | 61 | fun notImplementedYet(feature: String): Nothing { 62 | token.error("$feature not implemented yet") 63 | } 64 | 65 | inline fun commaSeparatedList1(first: T, parse: () -> T): List { 66 | val list = mutableListOf(first) 67 | while (current == COMMA) { 68 | next() 69 | list.add(parse()) 70 | } 71 | return list 72 | } 73 | 74 | inline fun commaSeparatedList1(parse: () -> T): List { 75 | return commaSeparatedList1(parse(), parse) 76 | } 77 | 78 | inline fun commaSeparatedList0(terminator: TokenKind, parse: () -> T): List { 79 | return if (current == terminator) { 80 | emptyList() 81 | } else { 82 | commaSeparatedList1(parse) 83 | } 84 | } 85 | 86 | inline fun trailingCommaSeparatedList1(terminator: TokenKind, parse: () -> T): List { 87 | val list = mutableListOf(parse()) 88 | while (current == COMMA && next() != terminator) { 89 | list.add(parse()) 90 | } 91 | return list 92 | } 93 | 94 | inline fun list1While(proceed: () -> Boolean, parse: () -> T): List { 95 | val list = mutableListOf(parse()) 96 | while (proceed()) { 97 | list.add(parse()) 98 | } 99 | return list 100 | } 101 | 102 | inline fun list0While(proceed: () -> Boolean, parse: () -> T): List { 103 | return if (!proceed()) { 104 | emptyList() 105 | } else { 106 | list1While(proceed, parse) 107 | } 108 | } 109 | 110 | inline fun list1Until(terminator: TokenKind, parse: () -> T): List { 111 | return list1While({ current != terminator }, parse) 112 | } 113 | 114 | inline fun list0Until(terminator: TokenKind, parse: () -> T): List { 115 | return list0While({ current != terminator }, parse) 116 | } 117 | 118 | inline fun collectWhile(proceed: () -> Boolean): List { 119 | return list0While(proceed, ::accept) 120 | } 121 | 122 | inline fun parenthesized(parse: () -> T): T { 123 | expect(OPENING_PAREN) 124 | val result = parse() 125 | expect(CLOSING_PAREN) 126 | return result 127 | } 128 | 129 | inline fun braced(parse: () -> T): T { 130 | expect(OPENING_BRACE) 131 | val result = parse() 132 | expect(CLOSING_BRACE) 133 | return result 134 | } 135 | 136 | infix fun (() -> T).optionalBefore(terminator: TokenKind): T? { 137 | return if (current == terminator) { 138 | next() 139 | null 140 | } else { 141 | this() before terminator 142 | } 143 | } 144 | 145 | inline fun optional(indicator: TokenKind, parse: () -> T): T? { 146 | return if (current != indicator) { 147 | null 148 | } else { 149 | next() 150 | parse() 151 | } 152 | } 153 | 154 | val symbolTable = SymbolTable() 155 | val allMemberNames = HashSet() 156 | } 157 | -------------------------------------------------------------------------------- /src/test/kotlin/syntax/lexer/LexerTest.kt: -------------------------------------------------------------------------------- 1 | package syntax.lexer 2 | 3 | import org.junit.jupiter.api.Assertions.assertEquals 4 | import org.junit.jupiter.api.Assertions.assertSame 5 | import org.junit.jupiter.api.Test 6 | import syntax.lexer.TokenKind.IDENTIFIER 7 | import syntax.lexer.TokenKind.STRING_LITERAL 8 | 9 | import kotlin.random.Random 10 | 11 | class LexerTest { 12 | private var lexer = Lexer("") 13 | 14 | @Test 15 | fun identifiers() { 16 | lexer = 17 | Lexer("a z a0 z9 a_z foo _bar the_quick_brown_fox_jumps_over_the_lazy_dog THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG") 18 | 19 | expectIdentifier("a") 20 | expectIdentifier("z") 21 | expectIdentifier("a0") 22 | expectIdentifier("z9") 23 | expectIdentifier("a_z") 24 | expectIdentifier("foo") 25 | expectIdentifier("_bar") 26 | expectIdentifier("the_quick_brown_fox_jumps_over_the_lazy_dog") 27 | expectIdentifier("THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG") 28 | } 29 | 30 | @Test 31 | fun stringLiterals() { 32 | lexer = Lexer( 33 | """ 34 | "hello" 35 | "hi there" 36 | "say \"hi\"" 37 | "\"please\" is the magic word" 38 | "use \\n for a new line" 39 | """ 40 | ) 41 | 42 | expectStringLiteral("hello") 43 | expectStringLiteral("hi there") 44 | expectStringLiteral("""say "hi"""") 45 | expectStringLiteral(""""please" is the magic word""") 46 | expectStringLiteral("""use \n for a new line""") 47 | } 48 | 49 | @Test 50 | fun singleLineComments() { 51 | lexer = Lexer( 52 | """// comment #1 53 | a 54 | // comment #2 55 | // comment #3 56 | b 57 | c // comment #4 58 | d// comment #5 59 | e//""" 60 | ) 61 | 62 | expectIdentifier("a") 63 | expectIdentifier("b") 64 | expectIdentifier("c") 65 | expectIdentifier("d") 66 | expectIdentifier("e") 67 | } 68 | 69 | @Test 70 | fun multiLineComments() { 71 | lexer = Lexer( 72 | """/* 73 | comment #1 74 | */ 75 | a /* comment #2 */ 76 | b /*/ comment #3*/ 77 | c /**/ 78 | d/***/ 79 | e /* / ** / *** /*/ 80 | f /*""" 81 | ) 82 | 83 | expectIdentifier("a") 84 | expectIdentifier("b") 85 | expectIdentifier("c") 86 | expectIdentifier("d") 87 | expectIdentifier("e") 88 | expectIdentifier("f") 89 | } 90 | 91 | @Test 92 | fun keywords() { 93 | for (kind in TokenKind.KEYWORDS) { 94 | lexemeRoundTrip(kind) 95 | } 96 | } 97 | 98 | @Test 99 | fun operatorsSeparators() { 100 | for (kind in TokenKind.OPERATORS_SEPARATORS) { 101 | lexemeRoundTrip(kind) 102 | } 103 | } 104 | 105 | private fun lexemeRoundTrip(kindIn: TokenKind) { 106 | val lexemeIn = kindIn.lexeme 107 | lexer = Lexer(lexemeIn) 108 | val kindOut = lexer.nextToken().kind 109 | val lexemeOut = kindOut.lexeme 110 | assertSame(lexemeIn, lexemeOut) 111 | } 112 | 113 | @Test 114 | fun nearKeywords() { 115 | for (kind in TokenKind.KEYWORDS) { 116 | val keyword = kind.lexeme 117 | val init = keyword.substring(0, keyword.length - 1) 118 | val last = keyword.last() 119 | identifierRoundTrip(init) 120 | identifierRoundTrip(init + randomLetterOtherThan(last)) 121 | identifierRoundTrip(keyword + randomLetter()) 122 | } 123 | } 124 | 125 | private fun identifierRoundTrip(identifier: String) { 126 | lexer = Lexer(identifier) 127 | expectIdentifier(identifier) 128 | } 129 | 130 | private fun randomLetter(): Char = (Random.nextInt(26) + 97).toChar() 131 | 132 | private fun randomLetterOtherThan(forbidden: Char): Char { 133 | val ch = (Random.nextInt(26 - 1) + 97).toChar() 134 | // ch is a random character between a and y. 135 | // In case ch is the forbidden character, 'z' is available. 136 | // All characters will be chosen with the same probability. 137 | return if (ch == forbidden) 'z' else ch 138 | } 139 | 140 | private fun expectIdentifier(identifier: String) { 141 | val token = lexer.nextToken() 142 | assertEquals(IDENTIFIER, token.kind) 143 | assertEquals(identifier, token.text) 144 | } 145 | 146 | private fun expectStringLiteral(stringLiteral: String) { 147 | val token = lexer.nextToken() 148 | assertEquals(STRING_LITERAL, token.kind) 149 | assertEquals(stringLiteral, token.text) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/Flexer.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import common.puts 4 | import freditor.FlexerState 5 | import freditor.FlexerState.EMPTY 6 | import freditor.FlexerState.THIS 7 | import freditor.FlexerStateBuilder 8 | 9 | object Flexer : freditor.Flexer() { 10 | private val SLASH_SLASH = FlexerState('\n', null).setDefault(THIS) 11 | private val SLASH_ASTERISK___ASTERISK_SLASH = EMPTY.tail() 12 | private val SLASH_ASTERISK___ASTERISK = FlexerState('*', THIS, '/', SLASH_ASTERISK___ASTERISK_SLASH) 13 | private val SLASH_ASTERISK = FlexerState('*', SLASH_ASTERISK___ASTERISK).setDefault(THIS) 14 | 15 | private val CHAR_CONSTANT_END = EMPTY.tail() 16 | private val CHAR_CONSTANT_ESCAPE = FlexerState('\n', null) 17 | private val CHAR_CONSTANT_TAIL = 18 | FlexerState('\n', null, '\'', CHAR_CONSTANT_END, '\\', CHAR_CONSTANT_ESCAPE).setDefault(THIS) 19 | private val CHAR_CONSTANT_HEAD = CHAR_CONSTANT_TAIL.head() 20 | 21 | private val STRING_LITERAL_END = EMPTY.tail() 22 | private val STRING_LITERAL_ESCAPE = FlexerState('\n', null) 23 | private val STRING_LITERAL_TAIL = 24 | FlexerState('\n', null, '\"', STRING_LITERAL_END, '\\', STRING_LITERAL_ESCAPE).setDefault(THIS) 25 | private val STRING_LITERAL_HEAD = STRING_LITERAL_TAIL.head() 26 | 27 | init { 28 | SLASH_ASTERISK___ASTERISK.setDefault(SLASH_ASTERISK) 29 | CHAR_CONSTANT_ESCAPE.setDefault(CHAR_CONSTANT_TAIL) 30 | STRING_LITERAL_ESCAPE.setDefault(STRING_LITERAL_TAIL) 31 | } 32 | 33 | private val NUMBER_TAIL = FlexerState("..09AFXXafxx", THIS) 34 | private val NUMBER_HEAD = NUMBER_TAIL.head() 35 | 36 | private val IDENTIFIER_TAIL = FlexerState("09AZ__az", THIS) 37 | private val IDENTIFIER_HEAD = IDENTIFIER_TAIL.head() 38 | 39 | private val START = FlexerStateBuilder() 40 | .set('(', OPENING_PAREN) 41 | .set(')', CLOSING_PAREN) 42 | .set('[', OPENING_BRACKET) 43 | .set(']', CLOSING_BRACKET) 44 | .set('{', OPENING_BRACE) 45 | .set('}', CLOSING_BRACE) 46 | .set('\n', NEWLINE) 47 | .set(' ', SPACE_HEAD) 48 | .set('/', FlexerState('*', SLASH_ASTERISK, '/', SLASH_SLASH).head()) 49 | .set('\'', CHAR_CONSTANT_HEAD) 50 | .set('\"', STRING_LITERAL_HEAD) 51 | .set("09", NUMBER_HEAD) 52 | .set("AZ__az", IDENTIFIER_HEAD) 53 | .build() 54 | .verbatim( 55 | IDENTIFIER_TAIL, "assert", "auto", "break", "case", "char", "const", "continue", 56 | "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", 57 | "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", 58 | "unsigned", "void", "volatile", "while" 59 | ) 60 | .verbatim( 61 | EMPTY, "!", "!=", "%", "%=", "&", "&&", "&=", "*", "*=", "+", "++", "+=", ",", "-", "--", 62 | "-=", "->", ".", "/", "/=", ":", ";", "<", "<<", "<<=", "<=", "=", "==", ">", ">=", ">>", ">>=", "?", 63 | "^", "^=", "|", "|=", "||", "~" 64 | ) 65 | .setDefault(ERROR) 66 | 67 | override fun start(): FlexerState = START 68 | 69 | override fun pickColorForLexeme(previousState: FlexerState, endState: FlexerState): Int { 70 | return lexemeColors[endState] ?: 0x000000 71 | } 72 | 73 | private val lexemeColors = hashMapOf(ERROR to 0x808080) 74 | .puts( 75 | CHAR_CONSTANT_HEAD, 76 | CHAR_CONSTANT_TAIL, 77 | CHAR_CONSTANT_ESCAPE, 78 | STRING_LITERAL_HEAD, 79 | STRING_LITERAL_TAIL, 80 | STRING_LITERAL_ESCAPE, 81 | 0x808080 82 | ) 83 | .puts(SLASH_SLASH, SLASH_ASTERISK, SLASH_ASTERISK___ASTERISK, SLASH_ASTERISK___ASTERISK_SLASH, 0x008000) 84 | .puts(CHAR_CONSTANT_END, STRING_LITERAL_END, 0xdc009c) 85 | .puts(NUMBER_HEAD, NUMBER_TAIL, 0x6400c8) 86 | .puts( 87 | START.read( 88 | "assert", "auto", "break", "case", "const", "continue", "default", "do", "else", 89 | "enum", "extern", "for", "goto", "if", "register", "return", "sizeof", "static", "struct", "switch", 90 | "typedef", "union", "volatile", "while" 91 | ), 0x0000ff 92 | ) 93 | .puts(START.read("char", "double", "float", "int", "long", "short", "signed", "unsigned", "void"), 0x008080) 94 | .puts(START.read("(", ")", "[", "]", "{", "}"), 0xff0000) 95 | .puts( 96 | START.read( 97 | "!", "!=", "%", "%=", "&", "&&", "&=", "*", "*=", "+", "++", "+=", "-", "--", 98 | "-=", "->", ".", "/", "/=", ":", "<", "<<", "<<=", "<=", "=", "==", ">", ">=", ">>", ">>=", "?", 99 | "^", "^=", "|", "|=", "||", "~" 100 | ), 0x804040 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /src/test/kotlin/semantic/types/TypeToStringTest.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import org.junit.jupiter.api.Assertions.assertEquals 4 | import org.junit.jupiter.api.Test 5 | 6 | class TypeToStringTest { 7 | @Test 8 | fun array() { 9 | assertEquals("int[10]", ArrayType(10, SignedIntType).toString()) 10 | } 11 | 12 | @Test 13 | fun arrayOfArray() { 14 | assertEquals("int[1][2]", ArrayType(1, ArrayType(2, SignedIntType)).toString()) 15 | } 16 | 17 | @Test 18 | fun pointer() { 19 | assertEquals("int*", PointerType(SignedIntType).toString()) 20 | } 21 | 22 | @Test 23 | fun arrayOfPointers() { 24 | assertEquals("int*[10]", ArrayType(10, PointerType(SignedIntType)).toString()) 25 | } 26 | 27 | @Test 28 | fun pointerToArray() { 29 | assertEquals("int(*)[10]", PointerType(ArrayType(10, SignedIntType)).toString()) 30 | } 31 | 32 | @Test 33 | fun pointerToPointerToArray() { 34 | assertEquals("int(**)[10]", PointerType(PointerType(ArrayType(10, SignedIntType))).toString()) 35 | } 36 | 37 | @Test 38 | fun qsort() { 39 | val predicate = FunctionType(SignedIntType, ConstVoidPointerType, ConstVoidPointerType).pointer() 40 | val qsort = FunctionType(VoidType, VoidPointerType, UnsignedIntType, UnsignedIntType, predicate) 41 | 42 | assertEquals("void(void*,unsigned int,unsigned int,int(*)(const void*,const void*))", qsort.toString()) 43 | } 44 | 45 | @Test 46 | fun bsearch() { 47 | val predicate = FunctionType(SignedIntType, ConstVoidPointerType, ConstVoidPointerType).pointer() 48 | val bsearch = FunctionType( 49 | VoidPointerType, ConstVoidPointerType, ConstVoidPointerType, UnsignedIntType, UnsignedIntType, predicate 50 | ) 51 | 52 | assertEquals( 53 | "void*(const void*,const void*,unsigned int,unsigned int,int(*)(const void*,const void*))", 54 | bsearch.toString() 55 | ) 56 | } 57 | 58 | @Test 59 | fun function() { 60 | assertEquals("int()", FunctionType(SignedIntType).toString()) 61 | } 62 | 63 | @Test 64 | fun functionReturningPointer() { 65 | assertEquals("int*()", FunctionType(PointerType(SignedIntType)).toString()) 66 | } 67 | 68 | @Test 69 | fun functionReturningPointerToPointer() { 70 | assertEquals("int**()", FunctionType(PointerType(PointerType(SignedIntType))).toString()) 71 | } 72 | 73 | @Test 74 | fun functionReturningPointerToArray() { 75 | assertEquals("int(*())[10]", FunctionType(PointerType(ArrayType(10, SignedIntType))).toString()) 76 | } 77 | 78 | @Test 79 | fun functionReturningPointerToFunction() { 80 | val callback = PointerType(FunctionType(SignedIntType, SignedIntType, SignedIntType)) 81 | assertEquals("int(*(int(*)(int,int)))(int,int)", FunctionType(callback, callback).toString()) 82 | } 83 | 84 | @Test 85 | fun constant() { 86 | assertEquals("const int", Const(SignedIntType).toString()) 87 | } 88 | 89 | @Test 90 | fun arrayOfConst() { 91 | assertEquals("const int[10]", ArrayType(10, Const(SignedIntType)).toString()) 92 | } 93 | 94 | @Test 95 | fun pointerToConst() { 96 | assertEquals("const int*", PointerType(Const(SignedIntType)).toString()) 97 | } 98 | 99 | @Test 100 | fun constPointer() { 101 | assertEquals("int*const", Const(PointerType(SignedIntType)).toString()) 102 | } 103 | 104 | @Test 105 | fun constPointerToConst() { 106 | assertEquals("const int*const", Const(PointerType(Const(SignedIntType))).toString()) 107 | } 108 | 109 | @Test 110 | fun constPointerToConstPointerToConst() { 111 | assertEquals("const int*const*const", Const(PointerType(Const(PointerType(Const(SignedIntType))))).toString()) 112 | } 113 | 114 | @Test 115 | fun constPointerToArray() { 116 | assertEquals("int(*const)[10]", Const(PointerType(ArrayType(10, SignedIntType))).toString()) 117 | } 118 | 119 | @Test 120 | fun constPointerToArrayOfConst() { 121 | assertEquals("const int(*const)[10]", Const(PointerType(ArrayType(10, Const(SignedIntType)))).toString()) 122 | } 123 | 124 | @Test 125 | fun constPointerToFunction() { 126 | assertEquals( 127 | "int(*const)(int)", 128 | Const(PointerType(FunctionType(SignedIntType, SignedIntType))).toString() 129 | ) 130 | } 131 | 132 | @Test 133 | fun arrayOfConstPointersToVoid() { 134 | assertEquals("void*const[10]", ArrayType(10, Const(VoidPointerType)).toString()) 135 | } 136 | 137 | @Test 138 | fun arrayOfConstPointersToConstVoid() { 139 | assertEquals("const void*const[10]", ArrayType(10, Const(ConstVoidPointerType)).toString()) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/types/Arithmetic.kt: -------------------------------------------------------------------------------- 1 | package semantic.types 2 | 3 | import interpreter.ArithmeticValue 4 | import interpreter.Value 5 | import text.quote 6 | 7 | abstract class ArithmeticType : Type { 8 | abstract fun show(value: Double): String 9 | 10 | abstract val defaultValue: ArithmeticValue 11 | 12 | abstract fun rank(): Int 13 | 14 | open fun isIntegral(): Boolean = true 15 | 16 | abstract fun trim(x: Double): Double 17 | 18 | override fun canCastFromDecayed(source: Type): Boolean = source is ArithmeticType 19 | 20 | override fun cast(source: Value): Value = cast(source as ArithmeticValue) 21 | 22 | fun cast(source: ArithmeticValue): ArithmeticValue = 23 | if (source.type === this) source else ArithmeticValue(trim(source.value), this) 24 | 25 | fun integralPromotions(): ArithmeticType = this.max(SignedIntType) 26 | 27 | fun usualArithmeticConversions(that: ArithmeticType): ArithmeticType = integralPromotions().max(that) 28 | 29 | fun max(that: ArithmeticType): ArithmeticType = if (this.rank() < that.rank()) that else this 30 | 31 | override fun declaration(parent: String): String = "$this$parent" 32 | } 33 | 34 | object SignedCharType : ArithmeticType() { 35 | override fun sizeof(): Int = 1 36 | 37 | override fun show(value: Double): String = value.toInt().and(0xff).toChar().quote() 38 | 39 | override val defaultValue: ArithmeticValue = Value.NUL 40 | 41 | override fun rank(): Int = 0 42 | 43 | override fun trim(x: Double): Double { 44 | if (x < -128.0) throw ArithmeticException("char underflow $x") 45 | if (x > +127.0) throw ArithmeticException("char overflow $x") 46 | return x.toInt().toByte().toDouble() 47 | } 48 | 49 | override fun toString(): String = "char" 50 | } 51 | 52 | object UnsignedCharType : ArithmeticType() { 53 | override fun sizeof(): Int = 1 54 | 55 | override fun show(value: Double): String = value.toInt().toString() 56 | 57 | override val defaultValue: ArithmeticValue = Value.unsignedChar(0) 58 | 59 | override fun rank(): Int = 1 60 | 61 | override fun trim(x: Double): Double = x.toInt().and(0xff).toDouble() 62 | 63 | override fun toString(): String = "unsigned char" 64 | } 65 | 66 | object SignedShortType : ArithmeticType() { 67 | override fun sizeof(): Int = 2 68 | 69 | override fun show(value: Double): String = value.toInt().toString() 70 | 71 | override val defaultValue: ArithmeticValue = Value.signedShort(0) 72 | 73 | override fun rank(): Int = 2 74 | 75 | override fun trim(x: Double): Double { 76 | if (x < -32768.0) throw ArithmeticException("short underflow $x") 77 | if (x > +32767.0) throw ArithmeticException("short overflow $x") 78 | return x.toInt().toShort().toDouble() 79 | } 80 | 81 | override fun toString(): String = "short" 82 | } 83 | 84 | object UnsignedShortType : ArithmeticType() { 85 | override fun sizeof(): Int = 2 86 | 87 | override fun show(value: Double): String = value.toInt().toString() 88 | 89 | override val defaultValue: ArithmeticValue = Value.unsignedShort(0) 90 | 91 | override fun rank(): Int = 3 92 | 93 | override fun trim(x: Double): Double = x.toInt().and(0xffff).toDouble() 94 | 95 | override fun toString(): String = "unsigned short" 96 | } 97 | 98 | object SignedIntType : ArithmeticType() { 99 | override fun sizeof(): Int = 4 100 | 101 | override fun show(value: Double): String = value.toInt().toString() 102 | 103 | override val defaultValue: ArithmeticValue by lazy { Value.signedInt(0) } 104 | 105 | override fun rank(): Int = 4 106 | 107 | override fun trim(x: Double): Double { 108 | if (x < -2147483648.0) throw ArithmeticException("int underflow $x") 109 | if (x > +2147483647.0) throw ArithmeticException("int overflow $x") 110 | return x.toInt().toDouble() 111 | } 112 | 113 | override fun toString(): String = "int" 114 | } 115 | 116 | object UnsignedIntType : ArithmeticType() { 117 | override fun sizeof(): Int = 4 118 | 119 | override fun show(value: Double): String = value.toLong().toString() 120 | 121 | override val defaultValue: ArithmeticValue by lazy { Value.unsignedInt(0) } 122 | 123 | override fun rank(): Int = 5 124 | 125 | override fun trim(x: Double): Double = x.toLong().and(0xffffffff).toDouble() 126 | 127 | override fun toString(): String = "unsigned int" 128 | } 129 | 130 | object FloatType : ArithmeticType() { 131 | override fun sizeof(): Int = 4 132 | 133 | override fun show(value: Double): String = value.toFloat().toString() 134 | 135 | override val defaultValue: ArithmeticValue = Value.float(0f) 136 | 137 | override fun rank(): Int = 8 138 | 139 | override fun isIntegral(): Boolean = false 140 | 141 | override fun trim(x: Double): Double = x.toFloat().toDouble() 142 | 143 | override fun toString(): String = "float" 144 | } 145 | 146 | object DoubleType : ArithmeticType() { 147 | override fun sizeof(): Int = 8 148 | 149 | override fun show(value: Double): String = value.toString() 150 | 151 | override val defaultValue: ArithmeticValue = Value.double(0.0) 152 | 153 | override fun rank(): Int = 9 154 | 155 | override fun isIntegral(): Boolean = false 156 | 157 | override fun trim(x: Double): Double = x 158 | 159 | override fun toString(): String = "double" 160 | } 161 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | fredoverflow 7 | skorbut 8 | 0.1.0-SNAPSHOT 9 | 10 | 11 | UTF-8 12 | official 13 | 2.1.21 14 | 1.8 15 | 1.8 16 | 1.8 17 | MainKt 18 | ${java.home}/lib/rt.jar 19 | 20 | 21 | 22 | 23 | java-modules 24 | 25 | [9,) 26 | 27 | 28 | ${java.home}/jmods(!**.jar;!module-info.class) 29 | 30 | 31 | 32 | 33 | 34 | 35 | fredoverflow 36 | freditor 37 | 0.1.0-SNAPSHOT 38 | 39 | 40 | 41 | org.jetbrains.kotlin 42 | kotlin-stdlib 43 | ${kotlin.version} 44 | 45 | 46 | 47 | org.junit.jupiter 48 | junit-jupiter-api 49 | 5.14.0 50 | test 51 | 52 | 53 | 54 | 55 | src/main/kotlin 56 | src/test/kotlin 57 | 58 | 59 | 60 | org.jetbrains.kotlin 61 | kotlin-maven-plugin 62 | ${kotlin.version} 63 | 64 | 65 | compile 66 | 67 | compile 68 | 69 | 70 | 71 | 72 | test-compile 73 | 74 | test-compile 75 | 76 | 77 | 78 | 79 | 80 | 81 | maven-surefire-plugin 82 | 3.5.4 83 | 84 | 85 | 86 | maven-jar-plugin 87 | 3.4.2 88 | 89 | 90 | 91 | ${main.class} 92 | 93 | 94 | 95 | 96 | 97 | 98 | com.github.wvengen 99 | proguard-maven-plugin 100 | 2.6.1 101 | 102 | 103 | package 104 | 105 | proguard 106 | 107 | 108 | 109 | 110 | true 111 | META-INF/MANIFEST.MF,!META-INF/**,!**.kotlin_* 112 | ${project.artifactId}.jar 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/tree/Expression.kt: -------------------------------------------------------------------------------- 1 | package syntax.tree 2 | 3 | import interpreter.Value 4 | import semantic.Symbol 5 | import semantic.types.Later 6 | import semantic.types.Type 7 | import syntax.lexer.Token 8 | 9 | abstract class Expression : Node() { 10 | var type: Type = Later 11 | var value: Value? = null 12 | 13 | var isLocator: Boolean = false 14 | 15 | override fun toString(): String { 16 | value?.let { value -> 17 | return "${super.toString()} : $type ${value.show()}" 18 | } 19 | return "${super.toString()} : $type" 20 | } 21 | } 22 | 23 | abstract class Unary(val operator: Token, val operand: Expression) : Expression() { 24 | override fun forEachChild(action: (Node) -> Unit) { 25 | action(operand) 26 | } 27 | 28 | override fun root(): Token = operator 29 | } 30 | 31 | abstract class Binary(val left: Expression, val operator: Token, val right: Expression) : Expression() { 32 | override fun forEachChild(action: (Node) -> Unit) { 33 | action(left) 34 | action(right) 35 | } 36 | 37 | override fun root(): Token = operator 38 | } 39 | 40 | class Constant(val constant: Token) : Expression() { 41 | override fun root(): Token = constant 42 | } 43 | 44 | class StringLiteral(val literal: Token) : Expression() { 45 | override fun root(): Token = literal 46 | } 47 | 48 | class Identifier(val name: Token) : Expression() { 49 | lateinit var symbol: Symbol 50 | 51 | override fun root(): Token = name 52 | } 53 | 54 | class PrintfCall(val printf: Token, val format: Token, val arguments: List) : Expression() { 55 | override fun forEachChild(action: (Node) -> Unit) { 56 | arguments.forEach(action) 57 | } 58 | 59 | override fun root(): Token = printf 60 | 61 | override fun toString(): String = "printf ${format.source} : $type" 62 | } 63 | 64 | class ScanfCall(val scanf: Token, val format: Token, val arguments: List) : Expression() { 65 | override fun forEachChild(action: (Node) -> Unit) { 66 | arguments.forEach(action) 67 | } 68 | 69 | override fun root(): Token = scanf 70 | 71 | override fun toString(): String = "scanf ${format.source} : $type" 72 | } 73 | 74 | class Postfix(x: Expression, f: Token) : Unary(f, x) 75 | 76 | class Subscript(x: Expression, f: Token, y: Expression) : Binary(x, f, y) { 77 | override fun toString(): String = "[] : $type" 78 | } 79 | 80 | class FunctionCall(val function: Expression, val arguments: List) : Expression() { 81 | override fun forEachChild(action: (Node) -> Unit) { 82 | action(function) 83 | arguments.forEach(action) 84 | } 85 | 86 | override fun root(): Token = function.root() 87 | 88 | override fun toString(): String = "() : $type" 89 | } 90 | 91 | class DirectMemberAccess(val left: Expression, val dot: Token, val right: Token) : Expression() { 92 | override fun forEachChild(action: (Node) -> Unit) { 93 | action(left) 94 | } 95 | 96 | override fun root(): Token = dot 97 | } 98 | 99 | class IndirectMemberAccess(val left: Expression, val arrow: Token, val right: Token) : Expression() { 100 | override fun forEachChild(action: (Node) -> Unit) { 101 | action(left) 102 | } 103 | 104 | override fun root(): Token = arrow 105 | } 106 | 107 | class Prefix(f: Token, x: Expression) : Unary(f, x) 108 | 109 | class Reference(f: Token, x: Expression) : Unary(f, x) 110 | 111 | class Dereference(f: Token, x: Expression) : Unary(f, x) 112 | 113 | class UnaryPlus(f: Token, x: Expression) : Unary(f, x) 114 | 115 | class UnaryMinus(f: Token, x: Expression) : Unary(f, x) 116 | 117 | class BitwiseNot(f: Token, x: Expression) : Unary(f, x) 118 | 119 | class LogicalNot(f: Token, x: Expression) : Unary(f, x) 120 | 121 | class Cast(operator: Token, val specifiers: DeclarationSpecifiers, val declarator: Declarator, operand: Expression) : 122 | Unary(operator, operand) 123 | 124 | class SizeofType(val operator: Token, val specifiers: DeclarationSpecifiers, val declarator: Declarator) : 125 | Expression() { 126 | var operandType: Type = Later 127 | 128 | override fun root(): Token = operator 129 | } 130 | 131 | class SizeofExpression(f: Token, x: Expression) : Unary(f, x) 132 | 133 | class Multiplicative(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 134 | 135 | class Plus(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 136 | 137 | class Minus(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 138 | 139 | class Shift(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 140 | 141 | class RelationalEquality(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 142 | 143 | class Bitwise(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 144 | 145 | class Logical(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 146 | 147 | class Conditional( 148 | val condition: Expression, 149 | val question: Token, 150 | val th3n: Expression, 151 | val colon: Token, 152 | val e1se: Expression 153 | ) : Expression() { 154 | override fun forEachChild(action: (Node) -> Unit) { 155 | action(condition) 156 | action(th3n) 157 | action(e1se) 158 | } 159 | 160 | override fun root(): Token = question 161 | } 162 | 163 | class Assignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 164 | 165 | class PlusAssignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 166 | 167 | class MinusAssignment(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 168 | 169 | class Comma(x: Expression, f: Token, y: Expression) : Binary(x, f, y) 170 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/Value.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import semantic.types.* 4 | import syntax.lexer.Token 5 | 6 | data class Object(val segment: Segment, val offset: Int, val type: Type, val index: Int, val bound: Int) { 7 | init { 8 | if (index < 0) throw AssertionError("negative index $index") 9 | if (index > bound) throw AssertionError("index $index out of bounds $bound") 10 | } 11 | 12 | fun address(): Int { 13 | val hi = segment.address 14 | val lo = segment.type.sizeof(offset) 15 | return hi.shl(16) + lo 16 | } 17 | 18 | fun isSentinel(): Boolean = (index == bound) 19 | 20 | operator fun plus(delta: Int): Object = copy(offset = offset + delta * type.count(), index = index + delta) 21 | 22 | operator fun minus(delta: Int): Object = copy(offset = offset - delta * type.count(), index = index - delta) 23 | 24 | fun preventSentinelAccess() { 25 | if (isSentinel()) throw AssertionError("index $index out of bounds $bound") 26 | } 27 | 28 | fun checkReferable(): Object { 29 | segment.checkAlive() 30 | return this 31 | } 32 | 33 | fun isReferable(): Boolean { 34 | return segment.alive 35 | } 36 | 37 | fun evaluate(): Value { 38 | return when (val type = this.type.unqualified()) { 39 | is ArrayType -> { 40 | // array-to-pointer decay 41 | PointerValue(copy(type = type.elementType, index = 0, bound = type.size)) 42 | } 43 | 44 | is StructType -> { 45 | // structs are not values, they must be preserved as objects 46 | StructPseudoValue(this) 47 | } 48 | 49 | else -> { 50 | preventSentinelAccess() 51 | val result = segment[offset] 52 | if (result == IndeterminateValue) throw AssertionError("read from uninitialized variable") 53 | result 54 | } 55 | } 56 | } 57 | 58 | fun assign(newValue: Value) { 59 | preventSentinelAccess() 60 | segment[this] = newValue 61 | } 62 | } 63 | 64 | interface Value { 65 | fun type(): Type 66 | 67 | fun show(): String 68 | 69 | fun decayed(): Value = this 70 | 71 | fun store(segment: Segment, offset: Int): Int { 72 | segment[offset] = this 73 | return offset + 1 74 | } 75 | 76 | companion object { 77 | fun signedChar(x: Char): ArithmeticValue = ArithmeticValue(x.code.toByte().toDouble(), SignedCharType) 78 | fun unsignedChar(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), UnsignedCharType) 79 | 80 | fun signedShort(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), SignedShortType) 81 | fun unsignedShort(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), UnsignedShortType) 82 | 83 | fun signedInt(x: Int): ArithmeticValue = ArithmeticValue(x.toDouble(), SignedIntType) 84 | fun unsignedInt(x: Int): ArithmeticValue = 85 | ArithmeticValue(x.toLong().and(0xffffffff).toDouble(), UnsignedIntType) 86 | 87 | fun float(x: Float): ArithmeticValue = ArithmeticValue(x.toDouble(), FloatType) 88 | fun double(x: Double): ArithmeticValue = ArithmeticValue(x, DoubleType) 89 | 90 | val ONE = signedInt(1) 91 | val NUL = signedChar('\u0000') 92 | val ZERO = signedInt(0) 93 | val MINUS_ONE = signedInt(-1) 94 | 95 | fun truth(x: Boolean): ArithmeticValue = if (x) ONE else ZERO 96 | } 97 | } 98 | 99 | object VoidValue : Value { 100 | override fun type(): Type = VoidType 101 | 102 | override fun show(): String = "void" 103 | } 104 | 105 | data class ArithmeticValue(val value: Double, val type: ArithmeticType) : Value { 106 | override fun type(): Type = type 107 | 108 | override fun show(): String = type.show(value) 109 | 110 | operator fun plus(that: ArithmeticValue): ArithmeticValue = 111 | ArithmeticValue(value + that.value, type.usualArithmeticConversions(that.type)).trim() 112 | 113 | operator fun minus(that: ArithmeticValue): ArithmeticValue = 114 | ArithmeticValue(value - that.value, type.usualArithmeticConversions(that.type)).trim() 115 | 116 | operator fun times(that: ArithmeticValue): ArithmeticValue = 117 | ArithmeticValue(value * that.value, type.usualArithmeticConversions(that.type)).trim() 118 | 119 | operator fun div(that: ArithmeticValue): ArithmeticValue = 120 | ArithmeticValue(value / that.value, type.usualArithmeticConversions(that.type)).trim() 121 | 122 | operator fun rem(that: ArithmeticValue): ArithmeticValue = 123 | ArithmeticValue(value % that.value, type.usualArithmeticConversions(that.type)).trim() 124 | 125 | private fun trim(): ArithmeticValue = ArithmeticValue(type.trim(value), type) 126 | 127 | fun integralPromotions(): ArithmeticValue = type.integralPromotions().cast(this) 128 | 129 | fun isFalse(): Boolean = (value == 0.0) 130 | 131 | fun isTrue(): Boolean = (value != 0.0) 132 | 133 | fun normalizeBool(): ArithmeticValue = if (value == 0.0 || value == 1.0) this else Value.ONE 134 | } 135 | 136 | data class PointerValue(val referenced: Object) : Value { 137 | override fun type(): Type = PointerType(referenced.type) 138 | 139 | override fun show(): String = "%08x".format(referenced.address()) 140 | 141 | operator fun plus(delta: Int): PointerValue = PointerValue(referenced.checkReferable() + delta) 142 | 143 | operator fun minus(delta: Int): PointerValue = PointerValue(referenced.checkReferable() - delta) 144 | 145 | operator fun minus(base: PointerValue): Int { 146 | if (referenced.segment != base.referenced.segment) throw AssertionError("subtract across segments") 147 | return (referenced.offset - base.referenced.offset) / referenced.type.count() 148 | } 149 | 150 | infix fun equal(that: PointerValue): Boolean { 151 | return this.referenced.segment === that.referenced.segment && 152 | this.referenced.offset == that.referenced.offset 153 | } 154 | 155 | infix fun less(that: PointerValue): Boolean { 156 | if (this.referenced.segment !== that.referenced.segment) throw AssertionError("compare across segments") 157 | return this.referenced.offset < that.referenced.offset 158 | } 159 | } 160 | 161 | data class FunctionDesignator(val functionName: Token, val functionType: FunctionType) : Value { 162 | override fun type(): Type = functionType 163 | 164 | override fun show(): String = error("show on function designator") 165 | 166 | override fun decayed(): Value = FunctionPointerValue(this) 167 | } 168 | 169 | data class FunctionPointerValue(val designator: FunctionDesignator) : Value { 170 | override fun type(): Type = PointerType(designator.functionType) 171 | 172 | override fun show(): String = "&${designator.functionName}" 173 | } 174 | 175 | object IndeterminateValue : Value { 176 | override fun type(): Type = throw AssertionError("indeterminate value has no type") 177 | 178 | override fun show(): String = "" 179 | } 180 | 181 | data class StructPseudoValue(val struct: Object) : Value { 182 | override fun type(): Type = struct.type 183 | 184 | override fun show(): String = "SPV" 185 | 186 | override fun store(segment: Segment, offset: Int): Int { 187 | val count = struct.type.count() 188 | segment.replace(offset, count, struct.segment, struct.offset) 189 | return offset + count 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![swap](skorbut.png) 2 | 3 | ## What is Skorbut? 4 | 5 | Skorbut is a simple teaching environment for a subset of C with a memory visualizer. 6 | If you ever had trouble visualizing arrays and pointers, you have come to the right place. 7 | 8 | *Skorbut* is German for *scurvy*, an illness that is caused by a lack of Vitamin C. 9 | Skorbut also lacks C in the sense that it implements only a restricted subset of C. 10 | 11 | ## Getting started 12 | 13 | Please take the time to **read the following instructions carefully.** 14 | Most problems stem from skipping or misunderstanding important steps. 15 | 16 | ### ☕ Windows & macOS 17 | 18 | 1. Visit https://adoptium.net 19 | 20 | 2. Click "Latest release" button to download Java installer 21 | 22 | 3. Wait for download to finish 23 | 24 | 4. Open the `Downloads` folder (via Windows Explorer or Finder/Spotlight, respectively) and double-click `OpenJDK...` to start Java installer 25 | 26 | 5. Click Next, Next, Install, Finish 27 | 28 | 6. Click [skorbut.jar](https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar) to download Skorbut
29 | **If Skorbut fails to download**, continue with ⚠️ Troubleshooting *Windows*, or ⚠️ Troubleshooting *macOS* 30 | 31 | 7. Open the `Downloads` folder and double-click `skorbut.jar` to start Skorbut
32 | **If Skorbut fails to start**, continue with ⚠️ Troubleshooting *Windows*, or ⚠️ Troubleshooting *macOS* 33 | 34 | ### ⚠️ Troubleshooting *Windows* 35 | 36 | Steps 1 through 5 (install Java) worked, but steps 6 (download Skorbut) or 7 (start Skorbut) failed? Then read on. 37 | 38 | - Move your mouse over the script below 39 | - A button appears in the top right corner of the script 40 | - Click that button to copy the script 41 | ```cmd 42 | cd Downloads 43 | if exist skorbut.jar.zip erase skorbut.jar.zip 44 | curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar 45 | echo java -version > skorbut.cmd 46 | echo java -jar skorbut.jar >> skorbut.cmd 47 | skorbut.cmd 48 | 49 | ``` 50 | - Press the Windows key (the key on the bottom left with the Windows logo ⊞ on it) 51 | - Write `cmd` and confirm with Enter 52 | - A terminal appears 53 | - Right-click anywhere inside that terminal to paste and execute the script 54 | 55 | From now on, simply double-click `skorbut.cmd` in the `Downloads` folder to start Skorbut.
56 | Feel free to move `skorbut.jar` and `skorbut.cmd` to the Desktop or any other folder you prefer. 57 | 58 | ### ⚠️ Troubleshooting *macOS* 59 | 60 | Steps 1 through 5 (install Java) worked, but steps 6 (download Skorbut) or 7 (start Skorbut) failed? Then read on. 61 | 62 | - Move your mouse over the script below 63 | - A button appears in the top right corner of the script 64 | - Click that button to copy the script 65 | ```sh 66 | cd Downloads 67 | curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar 68 | chmod +x skorbut.jar 69 | echo java -version > skorbut.sh 70 | echo java -jar skorbut.jar >> skorbut.sh 71 | chmod +x skorbut.sh 72 | ./skorbut.sh 73 | 74 | ``` 75 | - Press `Command⌘ Space` (or click the magnifying glass 🔍 in the top right corner of the screen) to open Spotlight 76 | - Write `terminal` and confirm with Enter 77 | - A terminal appears 78 | - Press `Command⌘ V` to paste and execute the script 79 | 80 | From now on, simply double-click `skorbut.sh` in the `Downloads` folder to start Skorbut.
81 | Feel free to move `skorbut.jar` and `skorbut.sh` to the Desktop or any other folder you prefer. 82 | 83 | ### 🐧 Ubuntu, Linux Mint, Debian... 84 | 85 | ```sh 86 | sudo apt install default-jdk 87 | cd Downloads 88 | curl -o skorbut.jar https://raw.githubusercontent.com/fredoverflow/skorbut/release/skorbut.jar 89 | chmod +x skorbut.jar 90 | echo java -version > skorbut.sh 91 | echo java -jar -Dsun.java2d.opengl=True skorbut.jar >> skorbut.sh 92 | chmod +x skorbut.sh 93 | ./skorbut.sh 94 | 95 | ``` 96 | 97 | From now on, simply double-click `skorbut.sh` in the `Downloads` folder to start Skorbut.
98 | Feel free to move `skorbut.jar` and `skorbut.sh` to the Desktop or any other folder you prefer. 99 | 100 | ### I would rather compile Skorbut from source! 101 | 102 | ``` 103 | git clone https://github.com/fredoverflow/freditor 104 | cd freditor 105 | mvn install 106 | cd .. 107 | git clone https://github.com/fredoverflow/skorbut 108 | cd skorbut 109 | mvn package 110 | ``` 111 | 112 | The executable `skorbut.jar` will be located inside the `target` folder. 113 | 114 | ## How do I save my code? 115 | 116 | The code is automatically saved to a new file each time you click the start button. 117 | The save folder is named `skorbut`, and it is located in your home directory. 118 | The full path is displayed in the title bar. 119 | 120 | ## Does Skorbut support auto-indentation? 121 | 122 | Yes, just hit Enter or Tab. 123 | 124 | ## What about auto-completion? 125 | 126 | Ctrl+Space after an identifier auto-completes to the longest common prefix of all identifiers in scope. 127 | For example, if `foo`, `bar` and `baz` are in scope, then `f` will be auto-completed to `foo`, 128 | but `b` will only be auto-completed to `ba`, because both `bar` and `baz` start with `ba`. 129 | 130 | Skorbut has scope-aware auto-completion for all identifiers *except* `struct` members; 131 | those are auto-completed globally, i.e. after `.` or `->`, *all* `struct` members are considered for auto-completion. 132 | (That's because auto-completion has no type information available yet. Changing this would be a Herculean task.) 133 | 134 | ## What features of C are currently missing? 135 | 136 | Non-exhaustive list off the top of my head: 137 | 138 | | Feature | Priority | 139 | | ------------------- | -------- | 140 | | preprocessor | very low | 141 | | variadic functions | very low | 142 | | compound assignment | low | 143 | | null pointer | medium | 144 | | union | very low | 145 | | return struct | low | 146 | 147 | ## Wait, no preprocessor? How do I `#include `? 148 | 149 | You don't need to include anything, the following standard library functions are already available: 150 | 151 | - printf 152 | - scanf 153 | - puts 154 | - putchar 155 | - getchar 156 | - malloc 157 | - free 158 | - realloc 159 | - qsort 160 | - bsearch 161 | - strlen 162 | - strcmp 163 | - pow 164 | - time 165 | 166 | ## What about my own header files? 167 | 168 | Skorbut does not support multiple translation units, so there would be no point in supporting header files. 169 | 170 | ## How do I define constants without `#define`? 171 | 172 | For integral constants, you can use anonymous enumerations like `enum { N = 10 };` 173 | 174 | ## Keyboard shortcuts 175 | 176 | | Windows | Effect | Macintosh | 177 | | -----------: | :-------------------: | ---------------- | 178 | | F1 | show type | F1 | 179 | | F3 | declaration
usages | F3 | 180 | | F5 | step into | F5 | 181 | | F6 | step over | F6 | 182 | | F7 | step return | F7 | 183 | | Tab
Enter | auto-indent | Tab
Enter | 184 | | Ctrl Space | auto-complete | Control (Shift) Space | 185 | | Ctrl Alt R | rename symbol | Command Option R | 186 | | Ctrl D | delete line | Command D | 187 | | Ctrl C | copy | Command C | 188 | | Ctrl X | cut | Command X | 189 | | Ctrl V | paste | Command V | 190 | | Ctrl Z | undo | Command Z | 191 | | Ctrl Y | redo | Command Y | 192 | -------------------------------------------------------------------------------- /src/main/kotlin/semantic/Linter.kt: -------------------------------------------------------------------------------- 1 | package semantic 2 | 3 | import interpreter.ArithmeticValue 4 | import interpreter.BasicBlock 5 | import interpreter.ImplicitContinue 6 | import interpreter.returnType 7 | import semantic.types.FunctionType 8 | import semantic.types.VoidType 9 | import semantic.types.isString 10 | import syntax.lexer.Token 11 | import syntax.lexer.TokenKind.* 12 | import syntax.tree.* 13 | 14 | class Linter(val translationUnit: TranslationUnit) : LinterBase() { 15 | init { 16 | detectLowHangingFruit() 17 | detectUnusedVariables() 18 | detectMissingReturns() 19 | detectUnreachableCode() 20 | } 21 | 22 | private fun detectLowHangingFruit() { 23 | translationUnit.walk({}) { 24 | when (it) { 25 | is Comma -> { 26 | it.left.detectOperatorWithoutEffect() 27 | } 28 | 29 | is ExpressionStatement -> { 30 | if (it.expression is FunctionCall) { 31 | if (it.expression.type !== VoidType) { 32 | it.expression.warn("ignored function result") 33 | } 34 | } else { 35 | it.expression.detectOperatorWithoutEffect() 36 | } 37 | } 38 | 39 | is IfThenElse -> { 40 | it.condition.detectSuspiciousCondition() 41 | } 42 | 43 | is While -> { 44 | it.condition.detectSuspiciousCondition() 45 | } 46 | 47 | is Do -> { 48 | it.condition.detectSuspiciousCondition() 49 | } 50 | 51 | is For -> { 52 | it.condition?.detectSuspiciousCondition() 53 | it.update?.detectOperatorWithoutEffect() 54 | } 55 | 56 | is Assert -> { 57 | it.condition.detectSuspiciousCondition() 58 | } 59 | } 60 | } 61 | } 62 | 63 | private fun Expression.detectOperatorWithoutEffect() { 64 | val root = root() 65 | when (root.kind) { 66 | EQUAL_EQUAL -> { 67 | root.warn("== is comparison, did you mean = instead?") 68 | } 69 | 70 | IDENTIFIER -> when (type) { 71 | is FunctionType -> root.warn("missing () for function call") 72 | 73 | else -> root.warn("$root has no effect") 74 | } 75 | 76 | PLUS, HYPHEN -> when (this) { 77 | is Binary -> root.warn("$root has no effect, did you mean $root= instead?") 78 | 79 | else -> root.warn("$root has no effect") 80 | } 81 | 82 | OPENING_PAREN, SIZEOF, OPENING_BRACKET, DOT, HYPHEN_MORE, 83 | AMPERSAND, ASTERISK, TILDE, BANG, SLASH, PERCENT, 84 | LESS_LESS, MORE_MORE, LESS, MORE, LESS_EQUAL, MORE_EQUAL, BANG_EQUAL, 85 | CARET, BAR, AMPERSAND_AMPERSAND, BAR_BAR, 86 | DOUBLE_CONSTANT, FLOAT_CONSTANT, INTEGER_CONSTANT, 87 | CHARACTER_CONSTANT, STRING_LITERAL -> { 88 | root.warn("$root has no effect") 89 | } 90 | 91 | else -> { 92 | } 93 | } 94 | } 95 | 96 | private fun Expression.detectSuspiciousCondition() { 97 | when (this) { 98 | is Assignment -> { 99 | warn("= is assignment, did you mean == instead?") 100 | } 101 | 102 | is RelationalEquality -> { 103 | when { 104 | left is RelationalEquality -> { 105 | val op1 = left.operator 106 | val op2 = this.operator 107 | warn("a${op1}b${op2}c does not do what you think it does. You probably want a${op1}b && b${op2}c instead.") 108 | } 109 | 110 | left.type.isString() || right.type.isString() -> { 111 | warn("a $operator b compares string addresses. To compare characters: strcmp(a, b) $operator 0") 112 | } 113 | 114 | operator.kind == LESS || operator.kind == BANG_EQUAL -> { 115 | if (right is FunctionCall && right.function is Identifier && right.function.name.text == "strlen") { 116 | warn("consider replacing SLOW i${operator.kind}strlen(s) with FAST s[i]") 117 | } 118 | } 119 | } 120 | } 121 | 122 | is Logical -> { 123 | left.detectSuspiciousCondition() 124 | right.detectSuspiciousCondition() 125 | } 126 | } 127 | val x = value 128 | if (x is ArithmeticValue && this !is Constant) { 129 | warn("condition is always ${x.isTrue()}") 130 | } 131 | } 132 | 133 | private fun detectUnusedVariables() { 134 | val unusedVariables = HashSet() 135 | translationUnit.walk({ node -> 136 | when (node) { 137 | is FunctionDefinition -> { 138 | node.parameters.forEach { unusedVariables.add(it.name) } 139 | } 140 | 141 | is Declaration -> { 142 | if (node.specifiers.storageClass != TYPEDEF) { 143 | for (namedDeclarator in node.namedDeclarators) { 144 | unusedVariables.add(namedDeclarator.name) 145 | if (namedDeclarator.declarator is Declarator.Initialized) { 146 | namedDeclarator.declarator.init.walk({}) { 147 | when (it) { 148 | is Identifier -> { 149 | unusedVariables.remove(it.symbol.name) 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | }) { node -> 159 | when (node) { 160 | is Identifier -> { 161 | unusedVariables.remove(node.symbol.name) 162 | } 163 | } 164 | } 165 | unusedVariables.forEach { 166 | it.warn("unused variable $it") 167 | } 168 | } 169 | 170 | private fun detectMissingReturns() { 171 | translationUnit.functions.forEach { it.detectMissingReturn() } 172 | } 173 | 174 | private fun FunctionDefinition.detectMissingReturn() { 175 | if (returnType() !== VoidType) { 176 | val exit = controlFlowGraph.values.last() 177 | if (exit.isReachable && exit.isOpen()) { 178 | root().warn("function ${name()} does not return a result on all code paths") 179 | } 180 | } 181 | } 182 | 183 | private fun detectUnreachableCode() { 184 | translationUnit.functions.forEach { it.detectUnreachableCode() } 185 | } 186 | 187 | private fun FunctionDefinition.detectUnreachableCode() { 188 | controlFlowGraph.values.asSequence() 189 | .filterNot(BasicBlock::isReachable) 190 | .flatMap(BasicBlock::getStatements) 191 | .filterNot { it.root().start < 0 } 192 | .firstOrNull() 193 | ?.apply { 194 | if (this is ImplicitContinue) { 195 | root().warn("loop never repeats") 196 | } else { 197 | root().warn("unreachable code") 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/MemoryUI.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import common.Counter 4 | import freditor.Fronts 5 | import interpreter.Memory 6 | import interpreter.PointerValue 7 | import interpreter.Segment 8 | import semantic.types.ArrayType 9 | import semantic.types.StructType 10 | import semantic.types.Type 11 | 12 | import java.awt.* 13 | import java.awt.geom.Line2D 14 | import java.awt.geom.QuadCurve2D 15 | 16 | import javax.swing.* 17 | import javax.swing.border.CompoundBorder 18 | import javax.swing.border.EmptyBorder 19 | import javax.swing.border.LineBorder 20 | import javax.swing.border.TitledBorder 21 | import kotlin.math.pow 22 | import kotlin.math.sqrt 23 | 24 | class MemoryUI(var memory: Memory) : JPanel() { 25 | init { 26 | layout = BoxLayout(this, BoxLayout.Y_AXIS) 27 | } 28 | 29 | private val emptyBorder = EmptyBorder(8, 8, 8, 8) 30 | private val stringsBorder = LineBorder(Color.LIGHT_GRAY, 2, true) 31 | private val staticsBorder = LineBorder(Color.GRAY, 2, true) 32 | private val stackBorder = LineBorder(Color.BLUE, 2, true) 33 | private val heapBorder = LineBorder(Color(128, 0, 128), 2, true) 34 | private var lineBorder = stringsBorder 35 | private val rigidWidth = Dimension(300, 0) 36 | 37 | private fun Segment.objectComponent(title: String, qualified: Type): JComponent { 38 | val type = qualified.unqualified() 39 | val address = valueIndex 40 | val component = when (type) { 41 | is ArrayType -> arrayComponent(type) 42 | is StructType -> structComponent(type) 43 | else -> scalarComponent() 44 | } 45 | component.alignmentX = Component.LEFT_ALIGNMENT 46 | component.border = CompoundBorder( 47 | emptyBorder, 48 | TitledBorder(lineBorder, title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION, Fronts.sansSerif) 49 | ) 50 | objects[this]!![address][type] = component 51 | return component 52 | } 53 | 54 | private fun Segment.arrayComponent(type: ArrayType): JComponent { 55 | val cells = JPanel() 56 | val axis = if (type.dimensions().and(1) == 1) BoxLayout.X_AXIS else BoxLayout.Y_AXIS 57 | cells.layout = BoxLayout(cells, axis) 58 | for (i in 0 until type.size) { 59 | cells.add(objectComponent(i.toString(), type.elementType)) 60 | } 61 | return cells 62 | } 63 | 64 | private fun Segment.structComponent(type: StructType): JComponent { 65 | val cells = JPanel() 66 | val axis = BoxLayout.Y_AXIS 67 | cells.layout = BoxLayout(cells, axis) 68 | for (symbol in type.members) { 69 | val name = symbol.name.text 70 | if (!name.startsWith('_')) { 71 | cells.add(objectComponent(name, symbol.type)) 72 | } else { 73 | valueIndex += symbol.type.count() 74 | } 75 | } 76 | return cells 77 | } 78 | 79 | private fun Segment.scalarComponent(): JComponent { 80 | val value = this[valueIndex++] 81 | val scalar = JLabel(" %3s ".format(value.show())) 82 | scalar.font = Fronts.monospaced 83 | if (value is PointerValue && value.referenced.isReferable()) { 84 | pointers[scalar] = value 85 | } 86 | return scalar 87 | } 88 | 89 | private val objects = HashMap>>() 90 | private val pointers = LinkedHashMap() 91 | private var valueIndex = 0 92 | 93 | fun update() { 94 | removeAll() 95 | objects.clear() 96 | pointers.clear() 97 | updateSingleSegment(memory.stringConstants, stringsBorder) 98 | updateSingleSegment(memory.staticVariables, staticsBorder) 99 | updateMultipleSegments(memory.heap, heapBorder) 100 | add(Box.createVerticalGlue()) 101 | updateMultipleSegments(memory.stack, stackBorder) 102 | revalidate() 103 | repaint() 104 | } 105 | 106 | private fun updateSingleSegment(segment: Segment, border: LineBorder) { 107 | val structType = segment.type as StructType 108 | if (structType.members.isNotEmpty()) { 109 | lineBorder = border 110 | update(segment) 111 | } 112 | } 113 | 114 | private fun update(segment: Segment) { 115 | objects[segment] = MutableList(segment.type.count()) { HashMap() } 116 | valueIndex = 0 117 | with(segment) { 118 | if (type is StructType) { 119 | val title = "%04x %s".format(address.and(0xffff), type.name.text) 120 | val component = objectComponent(title, type) 121 | val rigidArea = Box.createRigidArea(rigidWidth) as JComponent 122 | rigidArea.alignmentX = JComponent.LEFT_ALIGNMENT 123 | component.add(rigidArea) 124 | add(component) 125 | } else { 126 | val title = "%04x".format(address.and(0xffff)) 127 | add(objectComponent(title, type)) 128 | } 129 | } 130 | } 131 | 132 | private fun updateMultipleSegments(segments: List, border: LineBorder) { 133 | lineBorder = border 134 | segments.asReversed().forEach(::update) 135 | } 136 | 137 | override fun paint(graphics: Graphics) { 138 | super.paint(graphics) 139 | val g2d = graphics as Graphics2D 140 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) 141 | g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE) 142 | g2d.stroke = stroke 143 | g2d.color = color 144 | val pointerCounter = Counter() 145 | for ((source, pointer) in pointers.entries) { 146 | val place = pointerCounter.count(pointer) 147 | val obj = pointer.referenced 148 | val offset = if (obj.isSentinel()) obj.minus(1).offset else obj.offset 149 | objects[obj.segment]!![offset][obj.type.unqualified()]?.let { target -> 150 | val sourcePos = SwingUtilities.convertPoint(source, 0, 0, this) 151 | val targetPos = SwingUtilities.convertPoint(target, 0, 0, this) 152 | val x1 = sourcePos.x + source.width / 2 153 | val y1 = sourcePos.y + source.height / 2 154 | val x2 = targetPos.x + if (obj.isSentinel()) target.width else lerp(source.width, 0.5.pow(place * 0.5), 16) 155 | var y2 = targetPos.y + 8 156 | if (y2 < y1) { 157 | y2 = targetPos.y + target.height - 8 158 | } 159 | drawPointer(g2d, x1.toDouble(), y1.toDouble(), x2.toDouble(), y2.toDouble()) 160 | } 161 | } 162 | } 163 | 164 | private fun lerp(zero: Int, x: Double, one: Int): Int = (zero.toDouble() * (1 - x) + one.toDouble() * x).toInt() 165 | 166 | private fun drawPointer(g2d: Graphics2D, x1: Double, y1: Double, x2: Double, y2: Double) { 167 | var deltaX = x2 - x1 168 | var deltaY = y2 - y1 169 | if (deltaY > 0) { 170 | deltaX = -deltaX 171 | deltaY = -deltaY 172 | } 173 | val controlX = (x1 + x2 - deltaY) * 0.5 174 | val controlY = (y1 + y2 + deltaX) * 0.5 175 | g2d.draw(QuadCurve2D.Double(x1, y1, controlX, controlY, x2, y2)) 176 | 177 | deltaX = controlX - x2 178 | deltaY = controlY - y2 179 | val scaleFactor = 8.0 / sqrt((deltaX * deltaX + deltaY * deltaY) * 2) 180 | deltaX *= scaleFactor 181 | deltaY *= scaleFactor 182 | g2d.draw(Line2D.Double(x2, y2, x2 + (deltaX - deltaY), y2 + (deltaY + deltaX))) 183 | g2d.draw(Line2D.Double(x2, y2, x2 + (deltaX + deltaY), y2 + (deltaY - deltaX))) 184 | } 185 | 186 | private val stroke = BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND) 187 | private val color = Color(1.0f, 0.5f, 0f, 0.75f) 188 | } 189 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/BuildControlFlowGraph.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import semantic.types.ArithmeticType 4 | import syntax.lexer.missingIdentifier 5 | import syntax.tree.* 6 | 7 | val unusedEmptyHashMap = HashMap() 8 | 9 | class State( 10 | val continueTarget: String, val breakTarget: String, 11 | val switchControlType: ArithmeticType?, 12 | val cases: HashMap, var default: String? 13 | ) { 14 | constructor() : this("", "", null, unusedEmptyHashMap, null) 15 | 16 | fun openLoop(continueTarget: String, breakTarget: String): State { 17 | return State(continueTarget, breakTarget, switchControlType, cases, default) 18 | } 19 | 20 | fun openSwitch(controlType: ArithmeticType, breakTarget: String): State { 21 | return State(continueTarget, breakTarget, controlType, HashMap(), null) 22 | } 23 | } 24 | 25 | class BuildControlFlowGraph(function: FunctionDefinition) { 26 | private val controlFlowGraph = LinkedHashMap() 27 | 28 | private var lastGeneratedLabel = -1 29 | private var currentLabelStr = "" 30 | private var currentBasicBlock = BasicBlock() 31 | 32 | private fun generateLabel(): String { 33 | return "${++lastGeneratedLabel}" 34 | } 35 | 36 | private fun insertLabel(label: String) { 37 | if (!currentBasicBlock.isEmpty()) { 38 | jumpIfOpen(label) 39 | currentBasicBlock = BasicBlock() 40 | } 41 | currentLabelStr = label 42 | controlFlowGraph[currentLabelStr] = currentBasicBlock 43 | } 44 | 45 | private fun add(statement: FlatStatement) { 46 | if (currentBasicBlock.isClosed()) { 47 | currentBasicBlock = BasicBlock() 48 | currentLabelStr = generateLabel() 49 | controlFlowGraph[currentLabelStr] = currentBasicBlock 50 | } 51 | currentBasicBlock.add(statement) 52 | } 53 | 54 | private fun jumpIfOpen(target: String) { 55 | if (currentBasicBlock.isOpen()) { 56 | add(Jump(missingIdentifier, target)) 57 | } 58 | } 59 | 60 | init { 61 | currentLabelStr = generateLabel() 62 | insertLabel(currentLabelStr) 63 | 64 | function.body.flatten(State()) 65 | 66 | val entry = controlFlowGraph.values.first() 67 | entry.exploreReachability { controlFlowGraph[it]!! } 68 | function.controlFlowGraph = controlFlowGraph 69 | } 70 | 71 | private fun List.flatten(state: State) { 72 | for (statement in this) { 73 | statement.flatten(state) 74 | } 75 | } 76 | 77 | private fun Statement.flatten(state: State) { 78 | when (this) { 79 | is Block -> { 80 | statements.flatten(state) 81 | } 82 | 83 | is Declaration -> { 84 | add(FlatDeclaration(specifiers, namedDeclarators)) 85 | } 86 | 87 | is ExpressionStatement -> { 88 | add(FlatExpressionStatement(expression)) 89 | } 90 | 91 | is LabeledStatement -> { 92 | insertLabel(label.text) 93 | statement.flatten(state) 94 | } 95 | 96 | is Goto -> { 97 | add(Jump(goto, label.text)) 98 | } 99 | 100 | is IfThenElse -> { 101 | if (e1se == null) { 102 | val execute = generateLabel() 103 | val done = generateLabel() 104 | 105 | add(JumpIf(condition, execute, done)) 106 | 107 | insertLabel(execute) 108 | th3n.flatten(state) 109 | 110 | insertLabel(done) 111 | } else { 112 | val executeThen = generateLabel() 113 | val executeElse = generateLabel() 114 | val done = generateLabel() 115 | 116 | add(JumpIf(condition, executeThen, executeElse)) 117 | 118 | insertLabel(executeThen) 119 | th3n.flatten(state) 120 | jumpIfOpen(done) 121 | 122 | insertLabel(executeElse) 123 | e1se.flatten(state) 124 | 125 | insertLabel(done) 126 | } 127 | } 128 | 129 | is Switch -> { 130 | val done = generateLabel() 131 | 132 | @Suppress("NAME_SHADOWING") 133 | val state = state.openSwitch(control.type.unqualified() as ArithmeticType, breakTarget = done) 134 | 135 | add(SwitchPlaceholder) 136 | val basicBlock = currentBasicBlock 137 | body.flatten(state) 138 | basicBlock.replaceSwitchPlaceholderWithRealSwitch( 139 | HashSwitch(control, state.cases, state.default ?: done) 140 | ) 141 | 142 | insertLabel(done) 143 | } 144 | 145 | is Case -> { 146 | if (state.switchControlType == null) { 147 | case.error("case label must be nested inside a switch") 148 | } 149 | val caseLabel = generateLabel() 150 | insertLabel(caseLabel) 151 | val previous = state.cases.put( 152 | state.switchControlType.integralPromotions().cast(choice.value as ArithmeticValue), 153 | caseLabel 154 | ) 155 | if (previous != null) { 156 | case.error("duplicate case label") 157 | } 158 | body.flatten(state) 159 | } 160 | 161 | is Default -> { 162 | if (state.switchControlType == null) { 163 | default.error("default label must be nested inside a switch") 164 | } 165 | if (state.default != null) { 166 | default.error("duplicate default label") 167 | } 168 | val defaultLabel = generateLabel() 169 | insertLabel(defaultLabel) 170 | state.default = defaultLabel 171 | body.flatten(state) 172 | } 173 | 174 | is Do -> { 175 | val bodyStart = generateLabel() 176 | val checkCondition = generateLabel() 177 | val done = generateLabel() 178 | 179 | @Suppress("NAME_SHADOWING") 180 | val state = state.openLoop(continueTarget = checkCondition, breakTarget = done) 181 | 182 | insertLabel(bodyStart) 183 | body.flatten(state) 184 | 185 | insertLabel(checkCondition) 186 | add(JumpIf(condition, bodyStart, done)) 187 | 188 | insertLabel(done) 189 | } 190 | 191 | is While -> { 192 | val checkCondition = generateLabel() 193 | val bodyStart = generateLabel() 194 | val done = generateLabel() 195 | 196 | @Suppress("NAME_SHADOWING") 197 | val state = state.openLoop(continueTarget = checkCondition, breakTarget = done) 198 | 199 | insertLabel(checkCondition) 200 | add(JumpIf(condition, bodyStart, done)) 201 | 202 | insertLabel(bodyStart) 203 | body.flatten(state) 204 | add(ImplicitContinue(whi1e, checkCondition)) 205 | 206 | insertLabel(done) 207 | } 208 | 209 | is For -> { 210 | when (init) { 211 | is ExpressionStatement -> { 212 | add(FlatExpressionStatement(init.expression)) 213 | } 214 | 215 | is Declaration -> { 216 | add(FlatDeclaration(init.specifiers, init.namedDeclarators)) 217 | } 218 | } 219 | 220 | val checkCondition = generateLabel() 221 | val loopStart = generateLabel() 222 | val updateCounter = generateLabel() 223 | val done = generateLabel() 224 | 225 | @Suppress("NAME_SHADOWING") 226 | val state = state.openLoop(continueTarget = updateCounter, breakTarget = done) 227 | 228 | if (condition != null) { 229 | insertLabel(checkCondition) 230 | add(JumpIf(condition, loopStart, done)) 231 | 232 | insertLabel(loopStart) 233 | body.flatten(state) 234 | 235 | insertLabel(updateCounter) 236 | if (update != null) { 237 | add(FlatExpressionStatement(update)) 238 | } 239 | add(ImplicitContinue(f0r, checkCondition)) 240 | 241 | insertLabel(done) 242 | } else { 243 | insertLabel(loopStart) 244 | body.flatten(state) 245 | 246 | insertLabel(updateCounter) 247 | if (update != null) { 248 | add(FlatExpressionStatement(update)) 249 | } 250 | add(ImplicitContinue(f0r, loopStart)) 251 | 252 | insertLabel(done) 253 | } 254 | } 255 | 256 | is Continue -> { 257 | if (state.continueTarget.isEmpty()) { 258 | continu3.error("continue must be nested inside a loop") 259 | } 260 | add(Jump(continu3, state.continueTarget)) 261 | } 262 | 263 | is Break -> { 264 | if (state.breakTarget.isEmpty()) { 265 | br3ak.error("break must be nested inside a loop or switch") 266 | } 267 | add(Jump(br3ak, state.breakTarget)) 268 | } 269 | 270 | is Return -> { 271 | add(FlatReturn(r3turn, result)) 272 | } 273 | 274 | is Assert -> { 275 | add(FlatAssert(condition)) 276 | } 277 | 278 | else -> { 279 | error("no flatten for $this") 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/main/kotlin/interpreter/Console.kt: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import semantic.types.DoubleType 4 | import semantic.types.FloatType 5 | import semantic.types.SignedCharType 6 | import semantic.types.SignedIntType 7 | import syntax.lexer.Token 8 | import text.skipDigits 9 | 10 | import java.util.concurrent.LinkedBlockingDeque 11 | import java.util.concurrent.atomic.AtomicBoolean 12 | 13 | class Console { 14 | private val output = StringBuilder() 15 | 16 | var isDirty: Boolean = false 17 | private set 18 | 19 | private var input = StringBuilder() 20 | 21 | private val queue = LinkedBlockingDeque() 22 | private val blocked = AtomicBoolean(false) 23 | 24 | fun isBlocked(): Boolean = blocked.get() 25 | 26 | var update: Function0? = null 27 | 28 | fun puts(str: PointerValue) { 29 | print(stringStartingAt(str)) 30 | putchar('\n') 31 | } 32 | 33 | fun putchar(x: Char) { 34 | output.append(x) 35 | isDirty = true 36 | } 37 | 38 | fun print(x: CharSequence) { 39 | output.append(x) 40 | isDirty = true 41 | } 42 | 43 | fun printf(format: Token, arguments: List): Int { 44 | val sb = StringBuilder() 45 | val args = arguments.iterator() 46 | val fmt = format.text 47 | var i = 0 48 | var k = fmt.indexOf('%') 49 | while (k != -1) { 50 | sb.append(fmt, i, k) 51 | if (fmt[++k] == '%') { 52 | sb.append('%') 53 | } else { 54 | i = k 55 | k = fmt.skipDigits(i) // width 56 | if (fmt[k] == '.') { 57 | k = fmt.skipDigits(k + 1) // precision 58 | } 59 | val specifier = fmt.substring(i - 1, k + 1) 60 | sb.append(formatValue(args.next(), specifier)) 61 | } 62 | i = k + 1 63 | k = fmt.indexOf('%', i) 64 | } 65 | sb.append(fmt, i, fmt.length) 66 | print(sb) 67 | return sb.length 68 | } 69 | 70 | private fun formatValue(value: Value, specifier: String): String { 71 | return when (specifier.last()) { 72 | 'c' -> specifier.format((value as ArithmeticValue).value.toLong().toInt().and(0xff)) 73 | 74 | 'i' -> specifier.replace('i', 'd').format((value as ArithmeticValue).value.toLong().toInt()) 75 | 76 | 'u' -> specifier.replace('u', 'd').format((value as ArithmeticValue).value.toLong().and(0xffffffff)) 77 | 78 | 'd', 'o', 'x', 'X' -> specifier.format((value as ArithmeticValue).value.toLong().toInt()) 79 | 80 | 'e', 'E', 'f', 'g', 'G' -> specifier.format(java.util.Locale.ENGLISH, (value as ArithmeticValue).value) 81 | 82 | 's' -> specifier.format(stringStartingAt(value as PointerValue)) 83 | 84 | 'p' -> specifier.replace('p', 's').format(value.show()) 85 | 86 | else -> error("illegal conversion specifier %${specifier.last()}") 87 | } 88 | } 89 | 90 | private fun stringStartingAt(start: PointerValue): CharSequence { 91 | val sb = StringBuilder() 92 | var ptr = start 93 | var x = (ptr.referenced.evaluate() as ArithmeticValue).value 94 | while (x != 0.0) { 95 | sb.append(x.toInt().and(0xff).toChar()) 96 | ptr += 1 97 | if (ptr.referenced.isSentinel()) error("missing NUL terminator") 98 | x = (ptr.referenced.evaluate() as ArithmeticValue).value 99 | } 100 | return sb 101 | } 102 | 103 | fun scanf(format: Token, arguments: List, after: Function0?): Int { 104 | val fmt = format.text 105 | var i = 0 106 | var a = 0 107 | while (i < fmt.length) { 108 | when (fmt[i++]) { 109 | '\t', '\n', ' ' -> skipWhitespace() 110 | 111 | '%' -> { 112 | val percent = i - 1 113 | if (i == fmt.length) format.stringErrorAt(percent, "incomplete conversion specifier") 114 | if (a == arguments.size) format.stringErrorAt(percent, "missing argument after format string") 115 | val arg = arguments[a++] as PointerValue 116 | val referenced = arg.referenced 117 | val type = referenced.type 118 | when (fmt[i++]) { 119 | 'd' -> { 120 | if (type !== SignedIntType) format.stringErrorAt(percent, "%d expects int*, not ${type.pointer()}") 121 | skipWhitespace() 122 | val x = scanInt() ?: return a - 1 123 | referenced.assign(Value.signedInt(x)) 124 | } 125 | 126 | 'f' -> { 127 | if (type !== FloatType) format.stringErrorAt(percent, "%f expects float*, not ${type.pointer()}") 128 | skipWhitespace() 129 | val x = scanDouble() ?: return a - 1 130 | referenced.assign(Value.float(x.toFloat())) 131 | } 132 | 133 | 'l' -> { 134 | if (i == fmt.length || fmt[i] != 'f') format.stringErrorAt(i, "missing f after %l") 135 | ++i 136 | if (type !== DoubleType) format.stringErrorAt(percent, "%lf expects double*, not ${type.pointer()}") 137 | skipWhitespace() 138 | val x = scanDouble() ?: return a - 1 139 | referenced.assign(Value.double(x)) 140 | } 141 | 142 | 'c' -> { 143 | if (type !== SignedCharType) format.stringErrorAt(percent, "%c expects char*, not ${type.pointer()}") 144 | referenced.assign(Value.signedChar(getchar())) 145 | } 146 | 147 | 's' -> { 148 | if (type !== SignedCharType) format.stringErrorAt(percent, "%s expects char*, not ${type.pointer()}") 149 | val maxLen = referenced.bound - referenced.index - 1 150 | format.stringErrorAt(percent, "%s is unsafe\nuse %${maxLen}s instead") 151 | } 152 | 153 | '1', '2', '3', '4', '5', '6', '7', '8', '9' -> { 154 | var len = fmt[i - 1] - '0' 155 | while (i < fmt.length && fmt[i] in '0'..'9') { 156 | len = len * 10 + (fmt[i++] - '0') 157 | } 158 | if (i == fmt.length || fmt[i] != 's') format.stringErrorAt(i, "missing s after %$len") 159 | ++i 160 | if (type !== SignedCharType) format.stringErrorAt(percent, "%s expects char*, not ${type.pointer()}") 161 | val maxLen = referenced.bound - referenced.index - 1 162 | if (len > maxLen) format.stringErrorAt(percent, "%${len}s is ${len - maxLen} too long\nuse %${maxLen}s instead") 163 | skipWhitespace() 164 | val x = scanString(len) 165 | var obj = referenced 166 | for (c in x) { 167 | obj.assign(Value.signedChar(c)) 168 | obj += 1 169 | } 170 | obj.assign(Value.NUL) 171 | } 172 | 173 | else -> format.stringErrorAt(percent, "illegal conversion specifier %${fmt[i - 1]}") 174 | } 175 | after?.invoke() 176 | } 177 | 178 | else -> if (getchar() != fmt[i - 1]) { 179 | unget() 180 | return a 181 | } 182 | } 183 | } 184 | return arguments.size 185 | } 186 | 187 | private fun skipWhitespace() { 188 | @Suppress("ControlFlowWithEmptyBody") 189 | while (getchar().isWhitespace()) { 190 | } 191 | unget() 192 | } 193 | 194 | private fun scanInt(): Int? { 195 | var c = getchar() 196 | var sign = 1 197 | if (c == '-') { 198 | sign = -1 199 | c = getchar() 200 | } 201 | if (c !in '0'..'9') return null 202 | var x = c - '0' 203 | while (getchar() in '0'..'9') { 204 | x = x * 10 + (current - '0') 205 | } 206 | unget() 207 | return sign * x 208 | } 209 | 210 | private fun scanDouble(): Double? { 211 | var c = getchar() 212 | var sign = 1 213 | if (c == '-') { 214 | sign = -1 215 | c = getchar() 216 | } 217 | if (c !in '0'..'9') return null 218 | var x = (c - '0').toDouble() 219 | while (getchar() in '0'..'9') { 220 | x = x * 10 + (current - '0') 221 | } 222 | if (current == '.') { 223 | var decimal = 1.0 224 | while (getchar() in '0'..'9') { 225 | decimal /= 10 226 | x += (current - '0') * decimal 227 | } 228 | } 229 | unget() 230 | return sign * x 231 | } 232 | 233 | private fun scanString(len: Int): CharSequence { 234 | val sb = StringBuilder() 235 | while (getchar() > ' ') { 236 | sb.append(current) 237 | if (sb.length == len) return sb 238 | } 239 | unget() 240 | return sb 241 | } 242 | 243 | fun getText(): String { 244 | isDirty = false 245 | if (!isBlocked()) return output.toString() 246 | 247 | val result = StringBuilder() 248 | result.append(output) 249 | result.append(input) 250 | result.append('_') 251 | return result.toString() 252 | } 253 | 254 | fun keyTyped(x: Char) { 255 | when (x) { 256 | in '\u0020'..'\u007e' -> input.append(x) 257 | in '\u00a0'..'\u00ff' -> input.append(x) 258 | 259 | '\b' -> backspace() 260 | 261 | '\n' -> enter() 262 | 263 | '\u0004', '\u001a' -> stop() 264 | } 265 | } 266 | 267 | private fun backspace() { 268 | val len = input.length 269 | if (len > 0) { 270 | input.setLength(len - 1) 271 | } 272 | } 273 | 274 | private fun enter() { 275 | input.append('\n') 276 | output.append(input) 277 | val temp = input 278 | input = StringBuilder() 279 | blocked.set(false) 280 | for (x in temp) { 281 | queue.put(x) 282 | } 283 | } 284 | 285 | fun stop() { 286 | queue.put('\uffff') 287 | } 288 | 289 | fun getchar(): Char { 290 | val x: Char? = queue.poll() 291 | if (x != null) return remember(x) 292 | 293 | blocked.set(true) 294 | update?.invoke() 295 | val y: Char = queue.take() 296 | return remember(y) 297 | } 298 | 299 | private fun remember(x: Char): Char { 300 | current = x 301 | return x 302 | } 303 | 304 | private var current = '\u0000' 305 | 306 | private fun unget() { 307 | queue.putFirst(current) 308 | current = '\u0000' 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/main/kotlin/syntax/parser/Declarations.kt: -------------------------------------------------------------------------------- 1 | package syntax.parser 2 | 3 | import semantic.enumStructUnion 4 | import semantic.storageClasses 5 | import semantic.typeSpecifierIdentifier 6 | import semantic.typeSpecifiers 7 | import semantic.types.FunctionType 8 | import semantic.types.MarkerIsTypedefName 9 | import semantic.types.MarkerNotTypedefName 10 | import syntax.lexer.Token 11 | import syntax.lexer.TokenKind 12 | import syntax.lexer.TokenKind.* 13 | import syntax.lexer.TokenKindSet 14 | import syntax.tree.* 15 | 16 | fun Parser.declare(namedDeclarator: NamedDeclarator, isTypedefName: Boolean) { 17 | val pseudoType = when { 18 | isTypedefName -> MarkerIsTypedefName 19 | 20 | namedDeclarator.declarator.leaf() is Declarator.Function -> FunctionType.declarationMarker() 21 | 22 | else -> MarkerNotTypedefName 23 | } 24 | symbolTable.declare(namedDeclarator.name, pseudoType, 0) 25 | } 26 | 27 | fun Parser.isTypedefName(token: Token): Boolean { 28 | val symbol = symbolTable.lookup(token) 29 | if (symbol == null) { 30 | val taggedSymbol = symbolTable.lookup(token.tagged()) 31 | if (taggedSymbol != null) { 32 | token.error("Did you forget struct/enum/union before ${token.text}?") 33 | } 34 | } 35 | return symbol?.type === MarkerIsTypedefName 36 | } 37 | 38 | fun Parser.isDeclarationSpecifier(token: Token): Boolean = when (token.kind) { 39 | TYPEDEF, EXTERN, STATIC, AUTO, REGISTER, 40 | CONST, VOLATILE, 41 | VOID, CHAR, SHORT, INT, LONG, SIGNED, UNSIGNED, FLOAT, DOUBLE, 42 | ENUM, STRUCT, UNION -> true 43 | 44 | IDENTIFIER -> isTypedefName(token) 45 | 46 | else -> false 47 | } 48 | 49 | fun Parser.declaration(): Statement { 50 | val specifiers = declarationSpecifiers1declareDefTagName() 51 | val isTypedef = specifiers.storageClass == TYPEDEF 52 | val declarators = commaSeparatedList0(SEMICOLON) { 53 | initDeclarator().apply { declare(this, isTypedef) } 54 | } 55 | if (current == OPENING_BRACE) { 56 | val inner = declarators.last().name 57 | val outer = symbolTable.currentFunction()!!.name 58 | inner.error("cannot define function $inner inside function $outer", outer) 59 | } 60 | return Declaration(specifiers, declarators).semicolon() 61 | } 62 | 63 | fun Parser.declarationSpecifiers1declareDefTagName(): DeclarationSpecifiers { 64 | val specifiers = declarationSpecifiers1() 65 | specifiers.defTagName()?.let { name -> 66 | symbolTable.declare(name, MarkerNotTypedefName, 0) 67 | } 68 | return specifiers 69 | } 70 | 71 | fun Parser.declarationSpecifiers1(): DeclarationSpecifiers { 72 | val specifiers = declarationSpecifiers0() 73 | if (specifiers.list.isEmpty()) { 74 | val symbol = symbolTable.lookup(token) 75 | if (symbol != null) { 76 | val taggedSymbol = symbolTable.lookup(token.tagged()) 77 | if (taggedSymbol != null) { 78 | token.error("Did you forget struct/enum/union before ${token.text}?") 79 | } 80 | } 81 | illegalStartOf("declaration") 82 | } 83 | return specifiers 84 | } 85 | 86 | fun Parser.declarationSpecifiers0(): DeclarationSpecifiers { 87 | var storageClass: TokenKind = VOID 88 | var qualifiers = TokenKindSet.EMPTY 89 | var typeTokens = TokenKindSet.EMPTY 90 | val list = ArrayList() 91 | 92 | loop@ while (true) { 93 | when (current) { 94 | TYPEDEF, EXTERN, STATIC, AUTO, REGISTER -> 95 | if (storageClass == VOID) { 96 | storageClass = current 97 | } else { 98 | val previous = list.first { storageClasses.contains(it.kind()) } 99 | token.error("multiple storage class specifiers", previous.root()) 100 | } 101 | 102 | CONST, VOLATILE -> 103 | if (qualifiers.contains(current)) { 104 | val previous = list.first { it.kind() == current } 105 | token.error("duplicate type qualifier", previous.root()) 106 | } else { 107 | qualifiers += current 108 | } 109 | 110 | IDENTIFIER -> 111 | if (typeTokens.isEmpty() && isTypedefName(token)) { 112 | typeTokens = typeSpecifierIdentifier 113 | } else { 114 | break@loop 115 | } 116 | 117 | VOID, CHAR, SHORT, INT, LONG, SIGNED, UNSIGNED, FLOAT, DOUBLE, 118 | ENUM, STRUCT, UNION -> 119 | if (!typeTokens.isEmpty() && enumStructUnion.contains(typeTokens.first())) { 120 | val previous = list.first { enumStructUnion.contains(it.kind()) } 121 | token.error( 122 | "Did you forget to terminate the previous ${previous.kind()} with a semicolon?", 123 | previous.root() 124 | ) 125 | } else if (typeTokens.contains(current)) { 126 | val previous = list.first { it.kind() == current } 127 | token.error("duplicate type specifier", previous.root()) 128 | } else { 129 | typeTokens += current 130 | if (typeTokens !in typeSpecifiers) token.error("illegal combination of type specifiers: $typeTokens") 131 | } 132 | 133 | else -> break@loop 134 | } 135 | list.add(declarationSpecifier()) 136 | } 137 | return DeclarationSpecifiers(list, storageClass, qualifiers, typeTokens) 138 | } 139 | 140 | fun Parser.declarationSpecifier(): DeclarationSpecifier = when (current) { 141 | ENUM -> enumSpecifier() 142 | 143 | STRUCT -> structSpecifier() 144 | 145 | UNION -> notImplementedYet("unions") 146 | 147 | else -> DeclarationSpecifier.Primitive(accept()) 148 | } 149 | 150 | fun Parser.enumSpecifier(): DeclarationSpecifier { 151 | return if (next() == OPENING_BRACE) { 152 | // anonymous enum 153 | DeclarationSpecifier.EnumDef(token, enumBody()) 154 | } else { 155 | val name = expect(IDENTIFIER).tagged() 156 | if (current == OPENING_BRACE) { 157 | // named enum 158 | DeclarationSpecifier.EnumDef(name, enumBody()) 159 | } else { 160 | DeclarationSpecifier.EnumRef(name) 161 | } 162 | } 163 | } 164 | 165 | fun Parser.enumBody(): List { 166 | return braced { 167 | commaSeparatedList1 { 168 | Enumerator(expect(IDENTIFIER), optional(EQUAL, ::assignmentExpression)).apply { 169 | symbolTable.declare(name, MarkerNotTypedefName, 0) 170 | } 171 | } 172 | } 173 | } 174 | 175 | fun Parser.structSpecifier(): DeclarationSpecifier { 176 | return if (next() == OPENING_BRACE) { 177 | // anonymous struct 178 | DeclarationSpecifier.StructDef(token, structBody()) 179 | } else { 180 | val name = expect(IDENTIFIER).tagged() 181 | if (current == OPENING_BRACE) { 182 | // named struct 183 | DeclarationSpecifier.StructDef(name, structBody()) 184 | } else { 185 | DeclarationSpecifier.StructRef(name) 186 | } 187 | } 188 | } 189 | 190 | fun Parser.structBody(): List { 191 | return braced { 192 | list1Until(CLOSING_BRACE) { 193 | StructDeclaration(declarationSpecifiers1(), commaSeparatedList1 { 194 | namedDeclarator().apply { allMemberNames.add(name.text) } 195 | }).semicolon() 196 | } 197 | } 198 | } 199 | 200 | fun Parser.initDeclarator(): NamedDeclarator { 201 | return initDeclarator(namedDeclarator()) 202 | } 203 | 204 | fun Parser.initDeclarator(namedDeclarator: NamedDeclarator): NamedDeclarator { 205 | return if (current == EQUAL) { 206 | next() 207 | with(namedDeclarator) { 208 | NamedDeclarator(name, Declarator.Initialized(declarator, initializer())) 209 | } 210 | } else { 211 | namedDeclarator 212 | } 213 | } 214 | 215 | fun Parser.initializer(): Initializer { 216 | return if (current == OPENING_BRACE) { 217 | InitializerList(token, braced { trailingCommaSeparatedList1(CLOSING_BRACE, ::initializer) }) 218 | } else { 219 | ExpressionInitializer(assignmentExpression()) 220 | } 221 | } 222 | 223 | fun Parser.namedDeclarator(): NamedDeclarator { 224 | if (current == ASTERISK) { 225 | next() 226 | val qualifiers = typeQualifierList() 227 | return namedDeclarator().map { Declarator.Pointer(it, qualifiers) } 228 | } 229 | var temp: NamedDeclarator = when (current) { 230 | OPENING_PAREN -> parenthesized(::namedDeclarator) 231 | 232 | IDENTIFIER -> NamedDeclarator(accept(), Declarator.Identity) 233 | 234 | else -> illegalStartOf("declarator") 235 | } 236 | while (true) { 237 | temp = when (current) { 238 | OPENING_BRACKET -> temp.map { Declarator.Array(it, declaratorArray()) } 239 | 240 | OPENING_PAREN -> temp.map { Declarator.Function(it, declaratorFunction()) } 241 | 242 | else -> return temp 243 | } 244 | } 245 | } 246 | 247 | fun Parser.typeQualifierList(): List { 248 | return collectWhile { current == CONST } 249 | } 250 | 251 | fun Parser.declaratorArray(): Expression? { 252 | expect(OPENING_BRACKET) 253 | return ::expression optionalBefore CLOSING_BRACKET 254 | } 255 | 256 | fun Parser.declaratorFunction(): List { 257 | return symbolTable.scoped { 258 | parenthesized { 259 | if (current == VOID && lookahead.kind == CLOSING_PAREN) { 260 | next() 261 | } 262 | commaSeparatedList0(CLOSING_PAREN) { 263 | val specifiers = declarationSpecifiers1declareDefTagName() 264 | val declarator = parameterDeclarator() 265 | if (declarator.name.wasProvided()) { 266 | declare(declarator, isTypedefName = false) 267 | } 268 | FunctionParameter(specifiers, declarator) 269 | } 270 | } 271 | } 272 | } 273 | 274 | fun Parser.abstractDeclarator(): Declarator { 275 | if (current == ASTERISK) { 276 | next() 277 | val qualifiers = typeQualifierList() 278 | return Declarator.Pointer(abstractDeclarator(), qualifiers) 279 | } 280 | var temp: Declarator = when (current) { 281 | OPENING_PAREN -> parenthesized(::abstractDeclarator) 282 | 283 | IDENTIFIER -> token.error("identifier in abstract declarator") 284 | 285 | else -> Declarator.Identity 286 | } 287 | while (true) { 288 | temp = when (current) { 289 | OPENING_BRACKET -> Declarator.Array(temp, declaratorArray()) 290 | 291 | OPENING_PAREN -> Declarator.Function(temp, declaratorFunction()) 292 | 293 | else -> return temp 294 | } 295 | } 296 | } 297 | 298 | fun Parser.parameterDeclarator(): NamedDeclarator { 299 | if (current == ASTERISK) { 300 | next() 301 | val qualifiers = typeQualifierList() 302 | return parameterDeclarator().map { Declarator.Pointer(it, qualifiers) } 303 | } 304 | var temp: NamedDeclarator = when (current) { 305 | OPENING_PAREN -> { 306 | if (isDeclarationSpecifier(lookahead)) { 307 | NamedDeclarator(token, Declarator.Function(Declarator.Identity, declaratorFunction())) 308 | } else { 309 | parenthesized(::parameterDeclarator) 310 | } 311 | } 312 | 313 | IDENTIFIER -> NamedDeclarator(accept(), Declarator.Identity) 314 | 315 | else -> NamedDeclarator(token, Declarator.Identity) 316 | } 317 | while (true) { 318 | temp = when (current) { 319 | OPENING_BRACKET -> temp.map { Declarator.Array(it, declaratorArray()) } 320 | 321 | OPENING_PAREN -> temp.map { Declarator.Function(it, declaratorFunction()) } 322 | 323 | else -> return temp 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/MainFrame.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import common.Diagnostic 4 | import freditor.* 5 | import interpreter.Interpreter 6 | import interpreter.Memory 7 | import semantic.Linter 8 | import semantic.TypeChecker 9 | import syntax.lexer.Lexer 10 | import syntax.lexer.keywords 11 | import syntax.parser.Parser 12 | import syntax.parser.autocompleteIdentifier 13 | import syntax.parser.translationUnit 14 | import syntax.tree.* 15 | import java.awt.BorderLayout 16 | import java.awt.Dimension 17 | import java.awt.EventQueue 18 | import java.awt.Toolkit 19 | import java.awt.event.KeyAdapter 20 | import java.awt.event.KeyEvent 21 | import java.util.concurrent.ArrayBlockingQueue 22 | import java.util.function.Consumer 23 | import javax.swing.* 24 | import javax.swing.event.TreeModelListener 25 | import javax.swing.tree.TreeModel 26 | import javax.swing.tree.TreePath 27 | 28 | private val NAME = Regex("""[A-Z_a-z][0-9A-Z_a-z]*""") 29 | 30 | object StopTheProgram : Exception() { 31 | private fun readResolve(): Any = StopTheProgram 32 | } 33 | 34 | fun T.sansSerif(): T { 35 | this.font = Fronts.sansSerif 36 | return this 37 | } 38 | 39 | class MainFrame : JFrame() { 40 | private val queue = ArrayBlockingQueue(1) 41 | private var interpreter = Interpreter("int main(){return 0;}") 42 | 43 | private val memoryUI = MemoryUI(Memory(emptySet(), emptyList())) 44 | private val scrolledMemory = JScrollPane(memoryUI) 45 | private val syntaxTree = JTree().sansSerif() 46 | private val scrolledSyntaxTree = JScrollPane(syntaxTree) 47 | private val visualizer = JTabbedPane().sansSerif() 48 | 49 | private val tabbedEditors = TabbedEditors("skorbut", Flexer, JavaIndenter.instance) { freditor -> 50 | val editor = FreditorUI(freditor, 0, 25) 51 | 52 | editor.addKeyListener(object : KeyAdapter() { 53 | override fun keyPressed(event: KeyEvent) { 54 | when (event.keyCode) { 55 | KeyEvent.VK_SPACE -> if (event.isControlDown) { 56 | autocompleteIdentifier() 57 | } 58 | 59 | KeyEvent.VK_R -> if (FreditorUI.isControlRespectivelyCommandDown(event) && event.isAltDown) { 60 | renameSymbol() 61 | } 62 | 63 | KeyEvent.VK_F1 -> showType() 64 | 65 | KeyEvent.VK_F3 -> jumpToDeclarationAndFindUsages() 66 | 67 | KeyEvent.VK_F5 -> into.doClick() 68 | KeyEvent.VK_F6 -> over.doClick() 69 | KeyEvent.VK_F7 -> r3turn.doClick() 70 | } 71 | } 72 | }) 73 | 74 | editor.onRightClick = Consumer { 75 | editor.clearDiagnostics() 76 | showType() 77 | } 78 | 79 | editor 80 | } 81 | 82 | private val editor 83 | get() = tabbedEditors.selectedEditor 84 | 85 | private val slider = JSlider(0, 11, 0).sansSerif() 86 | private val timer = Timer(1000) { queue.offer("into") } 87 | 88 | private val start = JButton("start").sansSerif() 89 | private val into = JButton("step into (F5)").sansSerif() 90 | private val over = JButton("step over (F6)").sansSerif() 91 | private val r3turn = JButton("step return (F7)").sansSerif() 92 | private val stop = JButton("stop").sansSerif() 93 | private val buttons = JPanel() 94 | 95 | private val scrolledDiagnostics = JScrollPane() 96 | private val consoleUI = JTextArea() 97 | private val output = JTabbedPane().sansSerif() 98 | 99 | private val controls = JPanel() 100 | 101 | private var targetStackDepth = Int.MAX_VALUE 102 | private var lastReceivedPosition = 0 103 | 104 | init { 105 | title = "skorbut version ${Release.compilationDate(MainFrame::class.java)} @ ${editor.file.parent}" 106 | 107 | scrolledMemory.preferredSize = Dimension(500, 500) 108 | scrolledSyntaxTree.preferredSize = Dimension(500, 500) 109 | 110 | visualizer.addTab("memory", scrolledMemory) 111 | visualizer.addTab("syntax tree", scrolledSyntaxTree) 112 | visualizer.addChangeListener { 113 | if (visualizer.selectedComponent === scrolledMemory) { 114 | memoryUI.update() 115 | } else { 116 | hideDirtySyntaxTree() 117 | } 118 | } 119 | 120 | if (editor.length() == 0) { 121 | editor.load(helloWorld) 122 | } 123 | tabbedEditors.tabs.addChangeListener { 124 | updateDiagnostics(emptyList()) 125 | hideDirtySyntaxTree() 126 | } 127 | 128 | val horizontalSplit = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, visualizer, tabbedEditors.tabs) 129 | horizontalSplit.preferredSize = Dimension(1000, 500) 130 | 131 | slider.majorTickSpacing = 1 132 | slider.paintLabels = true 133 | 134 | buttons.add(start) 135 | into.isEnabled = false 136 | buttons.add(into) 137 | over.isEnabled = false 138 | buttons.add(over) 139 | r3turn.isEnabled = false 140 | buttons.add(r3turn) 141 | stop.isEnabled = false 142 | buttons.add(stop) 143 | 144 | val diagnosticsPanel = JPanel() 145 | diagnosticsPanel.layout = BorderLayout() 146 | diagnosticsPanel.add(scrolledDiagnostics) 147 | 148 | consoleUI.font = Fronts.monospaced 149 | consoleUI.isEditable = false 150 | 151 | output.addTab("diagnostics", scrolledDiagnostics) 152 | output.addTab("console", JScrollPane(consoleUI)) 153 | output.preferredSize = Dimension(0, Toolkit.getDefaultToolkit().screenSize.height / 5) 154 | 155 | controls.layout = BoxLayout(controls, BoxLayout.Y_AXIS) 156 | controls.add(slider) 157 | controls.add(buttons) 158 | controls.add(output) 159 | 160 | val verticalSplit = JSplitPane(JSplitPane.VERTICAL_SPLIT, horizontalSplit, controls) 161 | verticalSplit.resizeWeight = 1.0 162 | add(verticalSplit) 163 | 164 | listenToSyntaxTree() 165 | listenToSlider() 166 | listenToButtons() 167 | listenToConsole() 168 | 169 | defaultCloseOperation = EXIT_ON_CLOSE 170 | tabbedEditors.saveOnExit(this) 171 | pack() 172 | isVisible = true 173 | editor.requestFocusInWindow() 174 | } 175 | 176 | private fun hideDirtySyntaxTree() { 177 | if (visualizer.selectedComponent === scrolledSyntaxTree) { 178 | if (!isRunning() && !tryCompile()) { 179 | visualizer.selectedComponent = scrolledMemory 180 | } 181 | } 182 | } 183 | 184 | private fun listenToSyntaxTree() { 185 | syntaxTree.addTreeSelectionListener { 186 | val node = it.newLeadSelectionPath?.lastPathComponent 187 | if (node is Node) { 188 | editor.setCursorTo(node.root().start) 189 | } 190 | editor.requestFocusInWindow() 191 | } 192 | } 193 | 194 | private fun listenToConsole() { 195 | consoleUI.addKeyListener(object : KeyAdapter() { 196 | override fun keyTyped(event: KeyEvent) { 197 | if (interpreter.console.isBlocked()) { 198 | interpreter.console.keyTyped(event.keyChar) 199 | updateConsole() 200 | } else { 201 | JOptionPane.showMessageDialog( 202 | this@MainFrame, 203 | "You probably forgot one STEP", 204 | "Nobody is waiting for input yet", 205 | JOptionPane.ERROR_MESSAGE 206 | ) 207 | } 208 | } 209 | }) 210 | } 211 | 212 | private fun updateConsole() { 213 | consoleUI.text = interpreter.console.getText() 214 | output.selectedIndex = 1 215 | requestFocusInBlockedConsoleOrEditor() 216 | } 217 | 218 | private fun requestFocusInBlockedConsoleOrEditor() { 219 | if (interpreter.console.isBlocked()) { 220 | consoleUI.requestFocusInWindow() 221 | } else { 222 | editor.requestFocusInWindow() 223 | } 224 | } 225 | 226 | private fun listenToSlider() { 227 | slider.addChangeListener { 228 | requestFocusInBlockedConsoleOrEditor() 229 | configureTimer() 230 | } 231 | } 232 | 233 | private fun configureTimer() { 234 | val d = delay() 235 | if (d < 0) { 236 | timer.stop() 237 | } else { 238 | timer.initialDelay = d 239 | timer.delay = d 240 | if (isRunning()) { 241 | timer.restart() 242 | } 243 | } 244 | } 245 | 246 | private fun delay(): Int { 247 | val wait = slider.value 248 | return if (wait == 0) -1 else 1.shl(slider.maximum - wait) 249 | } 250 | 251 | private fun isRunning(): Boolean = !start.isEnabled 252 | 253 | private fun listenToButtons() { 254 | start.addActionListener { 255 | editor.indent() 256 | editor.saveWithBackup() 257 | editor.clearDiagnostics() 258 | editor.requestFocusInWindow() 259 | queue.clear() 260 | if (tryCompile()) { 261 | run() 262 | } 263 | } 264 | 265 | into.addActionListener { 266 | requestFocusInBlockedConsoleOrEditor() 267 | queue.offer("into") 268 | } 269 | 270 | over.addActionListener { 271 | requestFocusInBlockedConsoleOrEditor() 272 | queue.offer("over") 273 | } 274 | 275 | r3turn.addActionListener { 276 | requestFocusInBlockedConsoleOrEditor() 277 | queue.offer("return") 278 | } 279 | 280 | stop.addActionListener { 281 | editor.requestFocusInWindow() 282 | timer.stop() 283 | queue.clear() 284 | queue.put("stop") 285 | interpreter.console.stop() 286 | } 287 | } 288 | 289 | private fun autocompleteIdentifier() { 290 | try { 291 | val suffixes = autocompleteIdentifier(editor.textBeforeSelection) 292 | if (suffixes.size == 1) { 293 | editor.insert(suffixes[0]) 294 | } else { 295 | println(suffixes.sorted().joinToString(", ")) 296 | } 297 | } catch (diagnostic: Diagnostic) { 298 | showDiagnostic(diagnostic) 299 | updateDiagnostics(arrayListOf(diagnostic)) 300 | } 301 | } 302 | 303 | private fun renameSymbol() { 304 | if (isRunning() || !tryCompile()) return 305 | 306 | val symbol = interpreter.typeChecker.symbolAt(editor.cursor()) ?: return 307 | 308 | val oldName = symbol.name.text 309 | val input = JOptionPane.showInputDialog( 310 | this, 311 | oldName, 312 | "rename symbol", 313 | JOptionPane.QUESTION_MESSAGE, 314 | null, 315 | null, 316 | oldName 317 | ) ?: return 318 | 319 | val newName = input.toString().trim() 320 | if (!NAME.matches(newName) || newName in keywords) return 321 | 322 | val positions = IntArray(1 + symbol.usages.size) 323 | positions[0] = symbol.name.start 324 | symbol.usages.forEachIndexed { index, usage -> 325 | positions[1 + index] = usage.name.start 326 | } 327 | editor.rename(oldName, newName, positions) 328 | tryCompile() 329 | } 330 | 331 | private fun detectRoot(node: Node) { 332 | val root = node.root() 333 | if (editor.cursor() in root.start until root.end) { 334 | root.error(" $node ", -1) 335 | } 336 | } 337 | 338 | private fun showType() { 339 | try { 340 | val translationUnit = Parser(Lexer(editor.text)).translationUnit() 341 | TypeChecker(translationUnit) 342 | 343 | translationUnit.walk({}) { node -> 344 | if (node is Expression) { 345 | detectRoot(node) 346 | } else if (node is FunctionDefinition) { 347 | detectRoot(node) 348 | node.parameters.forEach(::detectRoot) 349 | } else if (node is NamedDeclarator) { 350 | detectRoot(node) 351 | if (node.declarator is Declarator.Initialized) { 352 | node.declarator.init.walk({}, ::detectRoot) 353 | } 354 | } 355 | } 356 | } catch (diagnostic: Diagnostic) { 357 | showDiagnostic(diagnostic) 358 | } 359 | } 360 | 361 | private fun jumpToDeclarationAndFindUsages() { 362 | if (isRunning() || !tryCompile()) return 363 | 364 | val symbol = interpreter.typeChecker.symbolAt(editor.cursor()) ?: return 365 | 366 | val symbolStart = symbol.name.start 367 | editor.setCursorTo(symbolStart) 368 | 369 | val name = symbol.name.text 370 | val diagnostics = symbol.usages.map { usage -> 371 | val usageStart = usage.name.start 372 | val line = 1 + editor.lineOfPosition(usageStart) 373 | Diagnostic(usageStart, "usage of $name on line $line", symbolStart) 374 | } 375 | updateDiagnostics(diagnostics) 376 | } 377 | 378 | private fun tryCompile(): Boolean { 379 | try { 380 | compile() 381 | return true 382 | } catch (diagnostic: Diagnostic) { 383 | showDiagnostic(diagnostic) 384 | updateDiagnostics(arrayListOf(diagnostic)) 385 | } catch (other: Throwable) { 386 | showDiagnostic(other.message ?: "null") 387 | other.printStackTrace() 388 | } 389 | return false 390 | } 391 | 392 | private fun compile() { 393 | interpreter = Interpreter(editor.text) 394 | updateSyntaxTreeModel() 395 | 396 | val linter = Linter(interpreter.translationUnit) 397 | updateDiagnostics(linter.getWarnings()) 398 | } 399 | 400 | private fun run() { 401 | interpreter.onMemorySet = { memory -> 402 | EventQueue.invokeLater { 403 | memoryUI.memory = memory 404 | } 405 | } 406 | interpreter.before = ::pauseAt 407 | interpreter.after = { 408 | EventQueue.invokeAndWait { 409 | if (visualizer.selectedComponent === scrolledMemory) { 410 | memoryUI.update() 411 | } 412 | if (interpreter.console.isDirty) { 413 | updateConsole() 414 | } 415 | } 416 | } 417 | consoleUI.text = "" 418 | interpreter.console.update = { EventQueue.invokeAndWait(::updateConsole) } 419 | 420 | tabbedEditors.tabs.isEnabled = false 421 | start.isEnabled = false 422 | into.isEnabled = true 423 | over.isEnabled = true 424 | r3turn.isEnabled = true 425 | stop.isEnabled = true 426 | configureTimer() 427 | tryExecute() 428 | } 429 | 430 | private fun updateSyntaxTreeModel() { 431 | syntaxTree.model = object : TreeModel { 432 | override fun getRoot(): Any { 433 | return interpreter.translationUnit 434 | } 435 | 436 | override fun getChildCount(parent: Any): Int { 437 | var count = 0 438 | (parent as Node).forEachChild { ++count } 439 | return count 440 | } 441 | 442 | override fun getChild(parent: Any, index: Int): Any? { 443 | var child: Node? = null 444 | var i = 0 445 | (parent as Node).forEachChild { 446 | if (i == index) { 447 | child = it 448 | } 449 | ++i 450 | } 451 | return child 452 | } 453 | 454 | override fun getIndexOfChild(parent: Any, child: Any): Int { 455 | var index = -1 456 | var i = 0 457 | (parent as Node).forEachChild { 458 | if (it === child) { 459 | index = i 460 | } 461 | ++i 462 | } 463 | return index 464 | } 465 | 466 | override fun isLeaf(node: Any): Boolean { 467 | return getChildCount(node) == 0 468 | } 469 | 470 | override fun addTreeModelListener(l: TreeModelListener?) { 471 | } 472 | 473 | override fun removeTreeModelListener(l: TreeModelListener?) { 474 | } 475 | 476 | override fun valueForPathChanged(path: TreePath?, newValue: Any?) { 477 | } 478 | } 479 | } 480 | 481 | private fun showDiagnostic(diagnostic: Diagnostic) { 482 | editor.setCursorTo(diagnostic.position) 483 | editor.requestFocusInWindow() 484 | editor.showDiagnostic(diagnostic.message, diagnostic.position, diagnostic.columnDelta) 485 | } 486 | 487 | private fun showDiagnostic(position: Int, message: String) { 488 | editor.setCursorTo(position) 489 | editor.requestFocusInWindow() 490 | editor.showDiagnostic(message) 491 | } 492 | 493 | private fun showDiagnostic(message: String) { 494 | editor.requestFocusInWindow() 495 | editor.showDiagnostic(message) 496 | } 497 | 498 | private fun updateDiagnostics(diagnostics: List) { 499 | val list = JList(diagnostics.toTypedArray()).sansSerif() 500 | list.addListSelectionListener { event -> 501 | if (!event.valueIsAdjusting) { 502 | val index = list.selectedIndex 503 | if (index != -1) { 504 | val diagnostic = diagnostics[index] 505 | updateCaretPosition(diagnostic) 506 | editor.requestFocusInWindow() 507 | list.clearSelection() 508 | } 509 | } 510 | } 511 | scrolledDiagnostics.setViewportView(list) 512 | if (diagnostics.isEmpty()) { 513 | output.setTitleAt(0, "diagnostics") 514 | } else { 515 | output.setTitleAt(0, "diagnostics (${diagnostics.size})") 516 | output.selectedIndex = 0 517 | } 518 | } 519 | 520 | private fun updateCaretPosition(diagnostic: Diagnostic) { 521 | if (editor.cursor() != diagnostic.position) { 522 | editor.setCursorTo(diagnostic.position) 523 | } else if (diagnostic.secondPosition != -1) { 524 | editor.setCursorTo(diagnostic.secondPosition) 525 | } 526 | } 527 | 528 | private fun pauseAt(position: Int) { 529 | lastReceivedPosition = position 530 | val entry = if (interpreter.stackDepth <= targetStackDepth) { 531 | // Step into mode 532 | EventQueue.invokeLater { 533 | editor.setCursorTo(position) 534 | } 535 | // Block until the next button press 536 | queue.take() 537 | } else { 538 | // Step over/return mode 539 | // Don't block, but consume potential button presses, especially stop 540 | queue.poll() 541 | } 542 | when (entry) { 543 | "into" -> { 544 | targetStackDepth = Int.MAX_VALUE 545 | } 546 | 547 | "over" -> { 548 | targetStackDepth = interpreter.stackDepth 549 | } 550 | 551 | "return" -> { 552 | targetStackDepth = interpreter.stackDepth - 1 553 | } 554 | 555 | "stop" -> { 556 | timer.stop() 557 | throw StopTheProgram 558 | } 559 | } 560 | } 561 | 562 | private fun tryExecute() { 563 | Thread { 564 | try { 565 | targetStackDepth = Int.MAX_VALUE 566 | lastReceivedPosition = 0 567 | interpreter.run(editor.cursor(), editor.length()) 568 | } catch (_: StopTheProgram) { 569 | } catch (diagnostic: Diagnostic) { 570 | EventQueue.invokeLater { 571 | showDiagnostic(diagnostic) 572 | } 573 | } catch (other: Throwable) { 574 | EventQueue.invokeLater { 575 | showDiagnostic(lastReceivedPosition, other.message ?: "null") 576 | other.printStackTrace() 577 | } 578 | } finally { 579 | EventQueue.invokeLater { 580 | tabbedEditors.tabs.isEnabled = true 581 | start.isEnabled = true 582 | into.isEnabled = false 583 | over.isEnabled = false 584 | r3turn.isEnabled = false 585 | stop.isEnabled = false 586 | timer.stop() 587 | } 588 | } 589 | }.start() 590 | } 591 | } 592 | 593 | const val helloWorld = """void demo() 594 | { 595 | char a[] = "hi"; 596 | char * p = a; 597 | ++p; 598 | ++p; 599 | ++p; 600 | } 601 | """ 602 | --------------------------------------------------------------------------------