Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature request: Kotlin support #427

Open
bqv opened this issue Nov 24, 2024 · 2 comments
Open

feature request: Kotlin support #427

bqv opened this issue Nov 24, 2024 · 2 comments
Assignees
Labels
enhancement New feature or request

Comments

@bqv
Copy link

bqv commented Nov 24, 2024

Language: Kotlin

package org.javacs.kt

import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
import java.util.logging.Formatter
import java.util.logging.LogRecord
import java.util.logging.Handler
import java.util.logging.Level
import java.time.Instant

internal val LOG = object : Logger() {
  override fun toString(): String = "logger"
}

protected class Demo<T> private constructor() : Collection<T> {
  init {
    LOG.toString()
  }

  private inline fun <reified S> Iterable<T>.something(callback: S.() -> Unit) {
    TODO()
    throw Exception("unreachable")
  }
}

enum class LogLevel(val value: Int) {
  NONE(100),
  ERROR(2),
  WARN(1),
  INFO(0),
  DEBUG(-1),
  TRACE(-2),
  DEEP_TRACE(-3),
  ALL(-100)
}

class Logger {
  class LogMessage(
    val level: LogLevel,
    val message: String
  ) {
    val formatted: String
      get() = "[$level] $message"
  }

  private var outBackend: ((LogMessage) -> Unit)? = null
  private var errBackend: ((LogMessage) -> Unit)? = null
  private val outQueue: Queue<LogMessage> = ArrayDeque()
  private val errQueue: Queue<LogMessage> = ArrayDeque()

  private val newline = System.lineSeparator()
  val logTime = false
  var level = LogLevel.INFO

  private fun outputError(msg: LogMessage) {
    if (errBackend == null) {
      errQueue.offer(msg)
    } else {
      errBackend?.invoke(msg)
    }
  }

  private fun output(msg: LogMessage) {
    if (outBackend == null) {
      outQueue.offer(msg)
    } else {
      outBackend?.invoke(msg)
    }
  }

  private fun log(msgLevel: LogLevel, msg: String, placeholders: Array<out Any?>) {
    if (level.value <= msgLevel.value) {
      output(LogMessage(msgLevel, format(insertPlaceholders(msg, placeholders))))
    }
  }

  suspend fun error(msg: String, vararg placeholders: Any?) = log(LogLevel.ERROR, msg, placeholders)

  suspend fun warn(msg: String, vararg placeholders: Any?) = log(LogLevel.WARN, msg, placeholders)

  suspend fun info(msg: String, vararg placeholders: Any?) = log(LogLevel.INFO, msg, placeholders)

  suspend fun debug(msg: String, vararg placeholders: Any?) = log(LogLevel.DEBUG, msg, placeholders)

  suspend fun trace(msg: String, vararg placeholders: Any?) = log(LogLevel.TRACE, msg, placeholders)

  suspend fun deepTrace(msg: String, vararg placeholders: Any?) = log(LogLevel.DEEP_TRACE, msg, placeholders)

  fun connectJULFrontend() {
    class JULRedirector(private val downstream: Logger): Handler() {
      override fun publish(record: LogRecord) {
        when (record.level) {
          Level.SEVERE -> downstream.error(record.message)
          Level.WARNING -> downstream.warn(record.message)
          Level.INFO -> downstream.info(record.message)
          Level.CONFIG -> downstream.debug(record.message)
          Level.FINE -> downstream.trace(record.message)
          else -> downstream.deepTrace(record.message)
        }
      }

      override fun flush() {}

      override fun close() {}
    }

    val rootLogger = java.util.logging.Logger.getLogger("").also {
      it.addHandler(JULRedirector(this))
    }
  }

  fun connectOutputBackend(outBackend: (LogMessage) -> Unit) {
    this.outBackend = outBackend
    flushOutQueue()
  }

  fun connectErrorBackend(errBackend: (LogMessage) -> Unit) {
    this.errBackend = errBackend
    flushErrQueue()
  }

  fun connectStdioBackend() {
    connectOutputBackend { println(it.formatted) }
    connectOutputBackend { System.err.println(it.formatted) }
  }

  private fun insertPlaceholders(msg: String, placeholders: Array<out Any?>): String {
    val msgLength = msg.length
    val lastIndex = msgLength - 1
    var charIndex = 0
    var placeholderIndex = 0
    var result = StringBuilder()

    while (charIndex < msgLength) {
      val currentChar = msg.get(charIndex)
      val nextChar = if (charIndex != lastIndex) msg.get(charIndex + 1) else '?'
      if ((currentChar == '{') && (nextChar == '}')) {
        if (placeholderIndex >= placeholders.size) {
          return "ERROR: Tried to log more '{}' placeholders than there are values"
        }
        result.append(placeholders[placeholderIndex] ?: "null")
        placeholderIndex += 1
        charIndex += 2
      } else {
        result.append(currentChar)
        charIndex += 1
      }
    }

    return result.toString()
  }

  private fun flushOutQueue() {
    while (outQueue.isNotEmpty()) {
      outBackend?.invoke(outQueue.poll())
    }
  }

  private fun flushErrQueue() {
    while (errQueue.isNotEmpty()) {
      errBackend?.invoke(errQueue.poll())
    }
  }

  private fun format(msg: String): String {
    val time = if (logTime) "${Instant.now()} " else ""
    var thread = Thread.currentThread().name

    return time + shortenOrPad(thread, 10) + msg
  }

  private fun shortenOrPad(str: String, length: Int): String =
    if (str.length <= length) {
      str.padEnd(length, ' ')
    } else {
      ".." + str.substring(str.length - length + 2)
    }
}
@bqv bqv added the enhancement New feature or request label Nov 24, 2024
@bqv
Copy link
Author

bqv commented Dec 1, 2024

My attempt:

(source_file
  (#set! "kind" "File")) @symbol

; Package
(package_header
  (identifier) @name
  (#set! "kind" "Package")) @symbol

; Enums
(class_declaration
  (type_identifier) @name
  (enum_class_body)
  (#set! "kind" "Enum")) @symbol
(enum_entry
  (simple_identifier) @name
(#set! "kind" "EnumMember")) @symbol

; Classes
(class_declaration
  (type_identifier) @name
  (#set! "kind" "Class")) @symbol

(object_declaration
  (type_identifier) @name
  (#set! "kind" "Object")) @symbol

; Functions
(function_declaration
  (simple_identifier) @name
  (#set! "kind" "Function")) @symbol
(anonymous_initializer
  (#offset! @name 0 0 0 4)
  (#set! "kind" "Constructor")) @symbol
(secondary_constructor
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Constructor")) @symbol

; Properties
(property_declaration
  (variable_declaration
  	(simple_identifier) @name
  )
  (#set! "kind" "Property")) @symbol
(getter
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Method")) @symbol
(setter
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Method")) @symbol

; Blocks
(call_expression
  (call_expression
    (simple_identifier) @name
  )
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol
(call_expression
  (simple_identifier) @name
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol
(call_expression
  (navigation_expression
    (navigation_suffix
      (simple_identifier) @name
    )
  )
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol

(boolean_literal
  (#set! "kind" "Boolean")) @symbol
(string_literal
  (#set! "kind" "String")) @symbol
(integer_literal
  (#set! "kind" "Number")) @symbol

@stevearc
Copy link
Owner

stevearc commented Dec 3, 2024

If you want to submit a PR, all you'll need to do is add the query file, add a test file, and run tests with make update_snapshots and confirm that the generated json file contains the expected symbol data. Take a look at #405 for reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants