Phase 0: strip JetBrains template, register Jai language and file type

- delete demo tool window + message bundle, rewrite plugin.xml metadata
- add JaiLanguage, JaiFileType, file icon, JaiTokenTypes (from the compiler's
  own lexer: modules/Jai_Lexer/module.jai)
- add ./jaitest: runs tests and reports the JUnit XML, not just the exit code
- ignore .kotlin
This commit is contained in:
hgranthorner
2026-08-04 10:19:11 -04:00
parent cb5a1d9555
commit 711334885b
11 changed files with 510 additions and 81 deletions

View File

@@ -1,22 +0,0 @@
package dev.hgh
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.PropertyKey
import java.util.function.Supplier
private const val BUNDLE = "messages.MyMessageBundle"
internal object MyMessageBundle {
private val instance = DynamicBundle(MyMessageBundle::class.java, BUNDLE)
@JvmStatic
fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any?): @Nls String {
return instance.getMessage(key, *params)
}
@JvmStatic
fun lazyMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any?): Supplier<@Nls String> {
return instance.getLazyMessage(key, *params)
}
}

View File

@@ -1,37 +0,0 @@
package dev.hgh
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.ui.content.ContentFactory
import javax.swing.JButton
import kotlin.random.Random
class MyToolWindowFactory : ToolWindowFactory {
override fun shouldBeAvailable(project: Project) = true
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val myToolWindow = MyToolWindow()
val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false)
toolWindow.contentManager.addContent(content)
}
class MyToolWindow {
private val content = JBPanel<JBPanel<*>>().apply {
val label = JBLabel(MyMessageBundle.message("toolwindow.MyToolWindow.number.label", "?"))
add(label)
add(JButton(MyMessageBundle.message("toolwindow.MyToolWindow.shuffle.button")).apply {
addActionListener {
label.text = MyMessageBundle.message(
"toolwindow.MyToolWindow.number.label", Random(System.currentTimeMillis()).nextInt(1000)
)
}
})
}
fun getContent(): JBPanel<JBPanel<*>> = content
}
}

View File

@@ -0,0 +1,20 @@
package dev.hgh.jai
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.util.IconLoader
import javax.swing.Icon
object JaiIcons {
@JvmField
val FILE: Icon = IconLoader.getIcon("/icons/jai.svg", JaiIcons::class.java)
}
object JaiFileType : LanguageFileType(JaiLanguage) {
override fun getName(): String = "Jai"
override fun getDescription(): String = "Jai source file"
override fun getDefaultExtension(): String = "jai"
override fun getIcon(): Icon = JaiIcons.FILE
}

View File

@@ -0,0 +1,11 @@
package dev.hgh.jai
import com.intellij.lang.Language
object JaiLanguage : Language("Jai") {
private fun readResolve(): Any = JaiLanguage
override fun getDisplayName(): String = "Jai"
override fun isCaseSensitive(): Boolean = true
}

View File

@@ -0,0 +1,390 @@
package dev.hgh.jai.lexer
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
import dev.hgh.jai.JaiLanguage
class JaiTokenType(
debugName: String,
) : IElementType(debugName, JaiLanguage) {
override fun toString(): String = "Jai:" + super.toString()
}
/**
* The Jai token set.
*
* Derived from the compiler's own lexer, `~/.local/jai/modules/Jai_Lexer/module.jai`
* (`Token_Type` at :17, `check_for_keyword` at :758, `compose_new_token` at :1503).
* See `docs/JAI_LANGUAGE_REFERENCE.md` §3/§4.
*
* Deliberate divergences from the compiler lexer, all in favour of the IDE:
* - `#ident` is a single [DIRECTIVE] token (the compiler emits `#` then an ident).
* - `#string ID ... ID` is a single [HERE_STRING] token, so the lexer never has to
* carry the terminator across a token boundary and can restart anywhere.
* - `::` and `:=` are single tokens (the compiler emits separate `:` `:` / `:` `=`).
* - The backtick of a backticked identifier is its own [BACKTICK] token (the compiler
* folds it into the identifier token as a flag).
*/
object JaiTokenTypes {
// --- trivia -----------------------------------------------------------
@JvmField val LINE_COMMENT = JaiTokenType("LINE_COMMENT")
@JvmField val BLOCK_COMMENT = JaiTokenType("BLOCK_COMMENT")
// --- literals and names -----------------------------------------------
@JvmField val IDENT = JaiTokenType("IDENT")
@JvmField val NUMBER = JaiTokenType("NUMBER")
@JvmField val STRING = JaiTokenType("STRING")
@JvmField val HERE_STRING = JaiTokenType("HERE_STRING")
@JvmField val DIRECTIVE = JaiTokenType("DIRECTIVE")
@JvmField val NOTE = JaiTokenType("NOTE")
// --- keywords ---------------------------------------------------------
@JvmField val KW_IF = JaiTokenType("if")
@JvmField val KW_XX = JaiTokenType("xx")
@JvmField val KW_IFX = JaiTokenType("ifx")
@JvmField val KW_FOR = JaiTokenType("for")
@JvmField val KW_THEN = JaiTokenType("then")
@JvmField val KW_ELSE = JaiTokenType("else")
@JvmField val KW_NULL = JaiTokenType("null")
@JvmField val KW_CASE = JaiTokenType("case")
@JvmField val KW_ENUM = JaiTokenType("enum")
@JvmField val KW_TRUE = JaiTokenType("true")
@JvmField val KW_CAST = JaiTokenType("cast")
@JvmField val KW_WHILE = JaiTokenType("while")
@JvmField val KW_BREAK = JaiTokenType("break")
@JvmField val KW_USING = JaiTokenType("using")
@JvmField val KW_DEFER = JaiTokenType("defer")
@JvmField val KW_FALSE = JaiTokenType("false")
@JvmField val KW_UNION = JaiTokenType("union")
@JvmField val KW_RETURN = JaiTokenType("return")
@JvmField val KW_STRUCT = JaiTokenType("struct")
@JvmField val KW_REMOVE = JaiTokenType("remove")
@JvmField val KW_INLINE = JaiTokenType("inline")
@JvmField val KW_SIZE_OF = JaiTokenType("size_of")
@JvmField val KW_TYPE_OF = JaiTokenType("type_of")
@JvmField val KW_CODE_OF = JaiTokenType("code_of")
@JvmField val KW_CONTEXT = JaiTokenType("context")
@JvmField val KW_CONTINUE = JaiTokenType("continue")
@JvmField val KW_OPERATOR = JaiTokenType("operator")
@JvmField val KW_TYPE_INFO = JaiTokenType("type_info")
@JvmField val KW_NO_INLINE = JaiTokenType("no_inline")
@JvmField val KW_INTERFACE = JaiTokenType("interface")
@JvmField val KW_ENUM_FLAGS = JaiTokenType("enum_flags")
@JvmField val KW_IS_CONSTANT = JaiTokenType("is_constant")
@JvmField val KW_PUSH_CONTEXT = JaiTokenType("push_context")
@JvmField val KW_INITIALIZER_OF = JaiTokenType("initializer_of")
/** `name` -> keyword token. Exactly `check_for_keyword`, nothing more. */
@JvmField
val KEYWORD_MAP: Map<String, JaiTokenType> =
mapOf(
"if" to KW_IF,
"xx" to KW_XX,
"ifx" to KW_IFX,
"for" to KW_FOR,
"then" to KW_THEN,
"else" to KW_ELSE,
"null" to KW_NULL,
"case" to KW_CASE,
"enum" to KW_ENUM,
"true" to KW_TRUE,
"cast" to KW_CAST,
"while" to KW_WHILE,
"break" to KW_BREAK,
"using" to KW_USING,
"defer" to KW_DEFER,
"false" to KW_FALSE,
"union" to KW_UNION,
"return" to KW_RETURN,
"struct" to KW_STRUCT,
"remove" to KW_REMOVE,
"inline" to KW_INLINE,
"size_of" to KW_SIZE_OF,
"type_of" to KW_TYPE_OF,
"code_of" to KW_CODE_OF,
"context" to KW_CONTEXT,
"continue" to KW_CONTINUE,
"operator" to KW_OPERATOR,
"type_info" to KW_TYPE_INFO,
"no_inline" to KW_NO_INLINE,
"interface" to KW_INTERFACE,
"enum_flags" to KW_ENUM_FLAGS,
"is_constant" to KW_IS_CONSTANT,
"push_context" to KW_PUSH_CONTEXT,
"initializer_of" to KW_INITIALIZER_OF,
)
/**
* Built-in type names. These are ordinary identifiers to the compiler — they are
* *not* reserved — but they get their own highlight. Kept as a name set, not a
* token type, so the parser can never depend on them being keywords.
* See `docs/JAI_LANGUAGE_REFERENCE.md` §3.
*/
@JvmField
val BUILTIN_TYPE_NAMES: Set<String> =
setOf(
"s8",
"s16",
"s32",
"s64",
"u8",
"u16",
"u32",
"u64",
"int",
"float",
"float32",
"float64",
"bool",
"string",
"void",
"Type",
"Any",
"Code",
)
/** Implicit `for` loop variables. Ordinary identifiers, highlighted specially. */
@JvmField
val LOOP_VARIABLE_NAMES: Set<String> = setOf("it", "it_index")
// --- operators and punctuation ----------------------------------------
@JvmField val PLUS = JaiTokenType("+")
@JvmField val MINUS = JaiTokenType("-")
@JvmField val STAR = JaiTokenType("*")
@JvmField val SLASH = JaiTokenType("/")
@JvmField val PERCENT = JaiTokenType("%")
@JvmField val PLUS_EQ = JaiTokenType("+=")
@JvmField val MINUS_EQ = JaiTokenType("-=")
@JvmField val STAR_EQ = JaiTokenType("*=")
@JvmField val SLASH_EQ = JaiTokenType("/=")
@JvmField val PERCENT_EQ = JaiTokenType("%=")
@JvmField val EQ = JaiTokenType("=")
@JvmField val EQ_EQ = JaiTokenType("==")
@JvmField val EQ_EQ_EQ = JaiTokenType("===")
@JvmField val NOT_EQ = JaiTokenType("!=")
@JvmField val NOT = JaiTokenType("!")
@JvmField val LT = JaiTokenType("<")
@JvmField val GT = JaiTokenType(">")
@JvmField val LT_EQ = JaiTokenType("<=")
@JvmField val GT_EQ = JaiTokenType(">=")
@JvmField val AND_AND = JaiTokenType("&&")
@JvmField val OR_OR = JaiTokenType("||")
@JvmField val AND_AND_EQ = JaiTokenType("&&=")
@JvmField val OR_OR_EQ = JaiTokenType("||=")
@JvmField val AND = JaiTokenType("&")
@JvmField val OR = JaiTokenType("|")
@JvmField val XOR = JaiTokenType("^")
@JvmField val TILDE = JaiTokenType("~")
@JvmField val AND_EQ = JaiTokenType("&=")
@JvmField val OR_EQ = JaiTokenType("|=")
@JvmField val XOR_EQ = JaiTokenType("^=")
@JvmField val SHL = JaiTokenType("<<")
@JvmField val SHR = JaiTokenType(">>")
@JvmField val SHL_EQ = JaiTokenType("<<=")
@JvmField val SHR_EQ = JaiTokenType(">>=")
@JvmField val ROL = JaiTokenType("<<<")
@JvmField val ROR = JaiTokenType(">>>")
@JvmField val ROL_EQ = JaiTokenType("<<<=")
@JvmField val ROR_EQ = JaiTokenType(">>>=")
@JvmField val RIGHT_ARROW = JaiTokenType("->")
@JvmField val QUICK_LAMBDA = JaiTokenType("=>")
@JvmField val DOUBLE_DOT = JaiTokenType("..")
@JvmField val TRIPLE_MINUS = JaiTokenType("---")
@JvmField val DOUBLE_MINUS = JaiTokenType("--")
@JvmField val DOUBLE_COMMA = JaiTokenType(",,")
@JvmField val BEGIN_STRUCT_LITERAL = JaiTokenType(".{")
@JvmField val BEGIN_ARRAY_LITERAL = JaiTokenType(".[")
@JvmField val POSTFIX_DEREFERENCE = JaiTokenType(".*")
@JvmField val DOLLAR = JaiTokenType("$")
@JvmField val DOUBLE_DOLLAR = JaiTokenType("$$")
@JvmField val COLON = JaiTokenType(":")
@JvmField val COLON_COLON = JaiTokenType("::")
@JvmField val COLON_EQ = JaiTokenType(":=")
@JvmField val SEMICOLON = JaiTokenType(";")
@JvmField val COMMA = JaiTokenType(",")
@JvmField val DOT = JaiTokenType(".")
@JvmField val QUESTION = JaiTokenType("?")
@JvmField val BACKTICK = JaiTokenType("`")
@JvmField val BACKSLASH = JaiTokenType("\\")
@JvmField val HASH = JaiTokenType("#")
@JvmField val AT = JaiTokenType("@")
@JvmField val LPAREN = JaiTokenType("(")
@JvmField val RPAREN = JaiTokenType(")")
@JvmField val LBRACKET = JaiTokenType("[")
@JvmField val RBRACKET = JaiTokenType("]")
@JvmField val LBRACE = JaiTokenType("{")
@JvmField val RBRACE = JaiTokenType("}")
// --- token sets -------------------------------------------------------
@JvmField val COMMENTS: TokenSet = TokenSet.create(LINE_COMMENT, BLOCK_COMMENT)
@JvmField val STRINGS: TokenSet = TokenSet.create(STRING, HERE_STRING)
@JvmField val KEYWORDS: TokenSet = TokenSet.create(*KEYWORD_MAP.values.toTypedArray())
@JvmField
val OPERATORS: TokenSet =
TokenSet.create(
PLUS,
MINUS,
STAR,
SLASH,
PERCENT,
PLUS_EQ,
MINUS_EQ,
STAR_EQ,
SLASH_EQ,
PERCENT_EQ,
EQ,
EQ_EQ,
EQ_EQ_EQ,
NOT_EQ,
NOT,
LT,
GT,
LT_EQ,
GT_EQ,
AND_AND,
OR_OR,
AND_AND_EQ,
OR_OR_EQ,
AND,
OR,
XOR,
TILDE,
AND_EQ,
OR_EQ,
XOR_EQ,
SHL,
SHR,
SHL_EQ,
SHR_EQ,
ROL,
ROR,
ROL_EQ,
ROR_EQ,
RIGHT_ARROW,
QUICK_LAMBDA,
DOUBLE_DOT,
DOUBLE_MINUS,
BEGIN_STRUCT_LITERAL,
BEGIN_ARRAY_LITERAL,
POSTFIX_DEREFERENCE,
DOLLAR,
DOUBLE_DOLLAR,
COLON,
COLON_COLON,
COLON_EQ,
DOUBLE_COMMA,
DOT,
QUESTION,
BACKTICK,
BACKSLASH,
HASH,
AT,
)
}