Skip to content

Commit

Permalink
Bitwise
Browse files Browse the repository at this point in the history
  • Loading branch information
Seggan committed Nov 21, 2023
1 parent 944d5d7 commit 996784a
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 59 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ subprojects {
}

group = "io.github.seggan"
version = "1.0-SNAPSHOT"
version = "0.1-SNAPSHOT"
7 changes: 6 additions & 1 deletion docs/index.papyri
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ Gradle installed. To build Metis, run the following command in the root director

@shell `./gradlew shadowJar`

This will create a JAR file in `app/build/libs` that can be run with `java -jar metis.jar`.
This will create a JAR file in `metis-app/build/libs` that can be run with `java -jar metis.jar`.

@h2 Changelog

@h2 { 0.1.0 }
[
{Initial release}
]

@h2 Credits

Metis is made by @href(`https://github.com/Seggan`) Seggan.
Expand Down
4 changes: 2 additions & 2 deletions metis-app/src/main/kotlin/io/github/seggan/metis/app/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private object Main : CliktCommand(name = "metis") {
if (it) {
CodeSource.constant("<stdin>", generateSequence(::readlnOrNull).joinToString("\n"))
} else {
CodeSource("INVALID") { throw AssertionError("This should never be called") }
CodeSource("INVALID") { throw AssertionError() }
}
},
).single().required()
Expand All @@ -48,7 +48,7 @@ private object Main : CliktCommand(name = "metis") {
override fun run() {
try {
if (syntaxHighlight) {
println(highlight(Lexer(source.mapText(HtmlEscape::unescapeHtml)).lex()))
println(highlight(Lexer.lex(source.mapText(HtmlEscape::unescapeHtml))))
} else {
val chunk = Chunk.load(source)
if (printChunk) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ fun highlight(tokens: List<Token>): String = createHTML(prettyPrint = false).spa
-> "keyword"

PLUS, MINUS, STAR, SLASH, PERCENT, RANGE, INCLUSIVE_RANGE, ELVIS, QUESTION_MARK, EQUALS, DOUBLE_EQUALS,
NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL
NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, BAND, BOR, BXOR, SHL,
SHR, SHRU, BNOT
-> "operator"

DOT, COLON, COMMA, SEMICOLON -> "punctuation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,11 @@ class Compiler private constructor(
}

private fun compileUnOp(op: AstNode.UnaryOp) = buildInsns(op.span) {
when (op.op) {
UnOp.NOT -> {
+compileExpression(op.expr)
+Insn.Not
}

UnOp.NEG -> {
+compileExpression(op.expr)
generateMetaCall("__neg__", 0)
}
+compileExpression(op.expr)
if (op.op == UnOp.NOT) {
+Insn.Not
} else {
generateMetaCall(op.op.metamethod!!, 0)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ enum class BinOp(internal val generateCode: InsnsBuilder.(List<FullInsn>, List<F
POW("__pow__"),
RANGE("__range__"),
INCLUSIVE_RANGE("__inclrange__"),
BAND("__band__"),
BOR("__bor__"),
BXOR("__bxor__"),
SHL("__shl__"),
SHR("__shr__"),
SHRU("__shru__"),
IN({ left, right ->
+right
+left
Expand Down Expand Up @@ -84,7 +90,8 @@ enum class BinOp(internal val generateCode: InsnsBuilder.(List<FullInsn>, List<F
})
}

enum class UnOp {
enum class UnOp(val metamethod: String? = null) {
NOT,
NEG,
NEG("__neg__"),
BNOT("__bnot__"),
}
21 changes: 17 additions & 4 deletions metis-lang/src/main/kotlin/io/github/seggan/metis/parsing/Lexer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import org.intellij.lang.annotations.Language

/**
* The Metis lexer. Converts source code into a list of [Token]s.
*
* @property source The [CodeSource] to lex.
*/
class Lexer(private val source: CodeSource) {
object Lexer {

private val matchers = mutableListOf<TokenMatcher>()

Expand Down Expand Up @@ -47,6 +45,13 @@ class Lexer(private val source: CodeSource) {
text("*", Token.Type.STAR)
text("/", Token.Type.SLASH)
text("%", Token.Type.PERCENT)
text("&", Token.Type.BAND)
text("|", Token.Type.BOR)
text("^", Token.Type.BXOR)
text("<<", Token.Type.SHL)
text(">>", Token.Type.SHR)
text(">>>", Token.Type.SHRU)
text("~", Token.Type.BNOT)
text("..<", Token.Type.RANGE)
text("..=", Token.Type.INCLUSIVE_RANGE)
text("?:", Token.Type.ELVIS)
Expand Down Expand Up @@ -88,9 +93,10 @@ class Lexer(private val source: CodeSource) {
/**
* Lexes the source code.
*
* @param source The [CodeSource] to lex.
* @return The list of [Token]s.
*/
fun lex(): List<Token> {
fun lex(source: CodeSource): List<Token> {
val tokens = mutableListOf<Token>()
val code = StringBuilder(source.text)
while (code.isNotEmpty()) {
Expand Down Expand Up @@ -226,6 +232,13 @@ data class Token(val type: Type, val text: String, val span: Span) {
STAR("'*'"),
SLASH("'/'"),
PERCENT("'%'"),
BAND("'&'"),
BOR("'|'"),
BXOR("'^'"),
SHL("'<<'"),
SHR("'>>'"),
SHRU("'>>>'"),
BNOT("'~'"),
RANGE("'..<'"),
INCLUSIVE_RANGE("'..='"),
ELVIS("'?:'"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,36 @@ class Parser(tokens: List<Token>, private val source: CodeSource) {

private fun parseElvis() = parseBinOp(::parseRange, mapOf(ELVIS to BinOp.ELVIS))
private fun parseRange() =
parseBinOp(::parseAddition, mapOf(RANGE to BinOp.RANGE, INCLUSIVE_RANGE to BinOp.INCLUSIVE_RANGE))
parseBinOp(::parseBitOr, mapOf(RANGE to BinOp.RANGE, INCLUSIVE_RANGE to BinOp.INCLUSIVE_RANGE))

private fun parseBitOr() = parseBinOp(::parseBitXor, mapOf(BOR to BinOp.BOR))
private fun parseBitXor() = parseBinOp(::parseBitAnd, mapOf(BXOR to BinOp.BXOR))
private fun parseBitAnd() = parseBinOp(::parseShift, mapOf(BAND to BinOp.BAND))
private fun parseShift() = parseBinOp(
::parseAddition,
mapOf(
SHL to BinOp.SHL,
SHR to BinOp.SHR,
SHRU to BinOp.SHRU
)
)

private fun parseAddition() = parseBinOp(::parseMultiplication, mapOf(PLUS to BinOp.PLUS, MINUS to BinOp.MINUS))
private fun parseMultiplication() =
parseBinOp(::parseUnary, mapOf(STAR to BinOp.TIMES, SLASH to BinOp.DIV, PERCENT to BinOp.MOD))

private fun parseUnary(): AstNode.Expression {
val op = tryConsume(NOT, MINUS)
val op = tryConsume(NOT, MINUS, BNOT)
if (op != null) {
val expr = parseUnary()
return AstNode.UnaryOp(if (op.type == NOT) UnOp.NOT else UnOp.NEG, expr, op.span + expr.span)
return AstNode.UnaryOp(
when (op.type) {
NOT -> UnOp.NOT
MINUS -> UnOp.NEG
BNOT -> UnOp.BNOT
else -> throw AssertionError()
}, expr, op.span + expr.span
)
}
return parsePostfix()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,7 @@ class State(parentState: State? = null) {
* @param source The source to load.
*/
fun runCode(source: CodeSource) {
val lexer = Lexer(source)
val parser = Parser(lexer.lex(), source)
val parser = Parser(Lexer.lex(source), source)
val compiler = Compiler()
loadChunk(compiler.compileCode(source.name, parser.parse()))
call(0)
Expand Down
41 changes: 37 additions & 4 deletions metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/Value.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ interface Value {
val metatable by lazy(::initString)
}

override fun toString() = value
override fun toString() = "\"$value\""
}

/**
Expand Down Expand Up @@ -186,9 +186,6 @@ interface Value {
return true
}

operator fun get(key: kotlin.String) = value[String(key)]
operator fun set(key: kotlin.String, value: Value) = this.setOrError(String(key), value)

companion object {

/**
Expand Down Expand Up @@ -394,6 +391,42 @@ fun Value.doubleValue() = this.convertTo<Value.Number>().value
*/
fun Value.stringValue() = this.convertTo<Value.String>().value

/**
* Converts this value to a [kotlin.Boolean], or throws an error if it cannot be converted.
*
* @throws MetisRuntimeException If the value cannot be converted.
*/
fun Value.booleanValue() = this.convertTo<Value.Boolean>().value

/**
* Converts this value to a [Value.Table], or throws an error if it cannot be converted.
*
* @throws MetisRuntimeException If the value cannot be converted.
*/
fun Value.tableValue() = this.convertTo<Value.Table>().value

/**
* Converts this value to a [Value.List], or throws an error if it cannot be converted.
*
* @throws MetisRuntimeException If the value cannot be converted.
*/
fun Value.listValue() = this.convertTo<Value.List>().value

/**
* Converts this value to a [Value.Bytes], or throws an error if it cannot be converted.
*
* @throws MetisRuntimeException If the value cannot be converted.
*/
fun Value.bytesValue() = this.convertTo<Value.Bytes>().value

operator fun MutableMap<Value, Value>.set(key: String, value: Value) {
this[Value.String(key)] = value
}

operator fun MutableMap<Value, Value>.get(key: String): Value? {
return this[Value.String(key)]
}

/**
* Look up a successive sequence of strings in this value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class Chunk(
}
}
val loaded =
state.globals.lookUpHierarchy("package", "loaded")!!.convertTo<Value.Table>()
state.globals.lookUpHierarchy("package", "loaded")!!.tableValue()
loaded[insn.name] = extra
extra
} else {
Expand Down Expand Up @@ -270,8 +270,7 @@ class Chunk(
* @return The loaded chunk.
*/
fun load(source: CodeSource): Chunk {
val lexer = Lexer(source)
val parser = Parser(lexer.lex(), source)
val parser = Parser(Lexer.lex(source), source)
val compiler = Compiler()
return compiler.compileCode(source.name, parser.parse())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import kotlin.collections.set
* @param chunk The chunk to run.
* @param args The arguments to pass to the chunk.
*/
class Coroutine(state: State, chunk: Chunk.Instance, args: Value.List) : Value {
class Coroutine(state: State, chunk: Chunk.Instance, args: List<Value>) : Value {

private val innerState = State(state)

Expand Down Expand Up @@ -74,7 +74,7 @@ class Coroutine(state: State, chunk: Chunk.Instance, args: Value.List) : Value {
Coroutine(
this,
fn.convertTo<Chunk.Instance>(),
if (args == Value.Null) Value.List() else args.convertTo<Value.List>()
if (args == Value.Null) Value.List() else args.listValue()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class NativeLoader(private val libs: List<NativeLibrary>) : ModuleLoader {
*/
object FileLoader : ModuleLoader {
override fun load(state: State, module: String): CallableValue? {
val searchPaths = state.globals.lookUpHierarchy("package", "path")!!.convertTo<Value.List>().map {
val searchPaths = state.globals.lookUpHierarchy("package", "path")!!.listValue().map {
state.currentDir.resolve(state.fileSystem.getPath(it.stringValue()))
}
for (searchPath in searchPaths) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ inline fun <T> translateIoError(block: () -> T): T {

internal val outStreamMetatable = buildTable { table ->
table["write"] = fourArgFunction(true) { self, buffer, off, len ->
val toBeWritten = buffer.convertTo<Value.Bytes>().value
val toBeWritten = buffer.bytesValue()
val offset = if (off == Value.Null) 0 else off.intValue()
val length = if (len == Value.Null) toBeWritten.size else len.intValue()
translateIoError { self.asObj<OutputStream>().write(toBeWritten, offset, length) }
Expand Down Expand Up @@ -70,7 +70,7 @@ internal val inStreamMetatable = buildTable { table ->
self.asObj<InputStream>().read().toDouble().metisValue()
} else {
// Read into a buffer
val toBeRead = buffer.convertTo<Value.Bytes>().value
val toBeRead = buffer.bytesValue()
val read = self.asObj<InputStream>().read(toBeRead)
if (read == -1) {
Value.Null
Expand Down
Loading

0 comments on commit 996784a

Please sign in to comment.