Debug Settings

Alpaca provides compile-time debug settings that help you troubleshoot and understand macro expansion, lexer, and parser behavior during compilation.

Overview

Debug settings are configured using the Scala compiler's -Xmacro-settings flag. These settings control:

  • Debug output directory
  • Compilation timeout
  • Verbose naming
  • Log levels and outputs

Compile-time processing: Debug settings are read by the Alpaca macro during compilation via -Xmacro-settings. They control what the macro logs, where it writes output files, and how long it is allowed to run before timing out. These settings have no effect at runtime -- they only influence the compile-time macro expansion process.

Available Settings

Core Settings

Setting Type Default Description
debugDirectory String null Absolute directory path where debug output files will be written. For Mill, use $moduleDir/debug; for SBT, use an absolute path or ${baseDirectory.value}/debug
compilationTimeout Duration 90s Maximum time allowed for macro compilation before timeout
enableVerboseNames Boolean false Enable verbose naming in generated code for better debugging

Log Level Settings

Each log level can be configured with one of three output modes:

Log Level Default Output Description
trace disabled Most detailed logging for fine-grained debugging
debug disabled Debug-level messages during macro expansion
info disabled Informational messages
warn stdout Warning messages (printed to console by default)
error stdout Error messages (printed to console by default)

Output Modes:

  • stdout - Print to standard output (console)
  • file - Write to a log file in the debug directory
  • disabled - Suppress output for this level

Configuration

Mill

Add debug settings to your scalacOptions in build.mill:

import mill._
import mill.scalalib._

object myproject extends ScalaModule {
  def scalaVersion = "3.7.4"

  def mvnDeps = Seq(
    mvn"io.github.halotukozak::alpaca:0.0.2"
  )

  override def scalacOptions = Seq(
    s"-Xmacro-settings:debugDirectory=$moduleDir/debug",
    "-Xmacro-settings:enableVerboseNames=true",
    "-Xmacro-settings:compilationTimeout=120s",
    "-Xmacro-settings:trace=stdout",
    "-Xmacro-settings:debug=file",
  )
}

Tip: You can combine multiple settings in a single flag using commas:

override def scalacOptions = Seq(
  s"-Xmacro-settings:debugDirectory=$moduleDir/debug,enableVerboseNames=true,trace=stdout"
)

Note: debugDirectory must be an absolute path. In Mill, use $moduleDir to reference the module directory.

SBT

Add debug settings to your build.sbt:

scalaVersion := "3.7.4"

libraryDependencies += "io.github.halotukozak" %% "alpaca" % "0.0.2"

scalacOptions ++= Seq(
  s"-Xmacro-settings:debugDirectory=${baseDirectory.value}/debug",
  "-Xmacro-settings:enableVerboseNames=true",
  "-Xmacro-settings:compilationTimeout=120s",
  "-Xmacro-settings:trace=stdout",
  "-Xmacro-settings:debug=file"
)

Or combine them:

scalacOptions += s"-Xmacro-settings:debugDirectory=${baseDirectory.value}/debug,enableVerboseNames=true,trace=stdout"

Note: debugDirectory must be an absolute path. In SBT, use ${baseDirectory.value} to reference the project directory.

Scala CLI

Use the --scala-opt flag to pass debug settings:

scala-cli run MyLexer.scala \
  --scala 3.7.4 \
  --dep "io.github.halotukozak::alpaca:0.0.2" \
  --scala-opt "-Xmacro-settings:debugDirectory=/absolute/path/to/debug" \
  --scala-opt "-Xmacro-settings:enableVerboseNames=true"

Or add directives in your Scala file:

//> using scala "3.7.4"
//> using dep "io.github.halotukozak::alpaca:0.0.2"
//> using options "-Xmacro-settings:debugDirectory=/absolute/path/to/debug"
//> using options "-Xmacro-settings:enableVerboseNames=true"

import alpaca.*

// Your code here

Note: debugDirectory must be an absolute path. Use an absolute path like /Users/username/project/debug or /home/user/project/debug.

Example: Complete Debugging Setup

Here's a complete example for debugging a complex parser:

// build.mill
import mill._
import mill.scalalib._

object myparser extends ScalaModule {
  def scalaVersion = "3.7.4"

  def mvnDeps = Seq(
    mvn"io.github.halotukozak::alpaca:0.0.2"
  )

  override def scalacOptions = Seq(
    // Enable all debug features
    s"-Xmacro-settings:debugDirectory=$moduleDir/debug",
    "-Xmacro-settings:enableVerboseNames=true",
    "-Xmacro-settings:compilationTimeout=180s",

    // Log everything to files
    "-Xmacro-settings:trace=file",
    "-Xmacro-settings:debug=file",
    "-Xmacro-settings:info=file",

    // Keep warnings and errors on console
    "-Xmacro-settings:warn=stdout",
    "-Xmacro-settings:error=stdout"
  )
}

This configuration:

  • Creates a debug directory in your module directory for output
  • Enables verbose names in generated code
  • Sets a 3-minute compilation timeout
  • Logs detailed trace/debug/info to files
  • Displays warnings and errors on the console

Finding log files: When using file output mode, log files are created in the debugDirectory with names based on your source files (e.g., MyLexer.scala.log). The full path will be $moduleDir/debug/MyLexer.scala.log for Mill projects.

Compilation Timeout Errors

When macro expansion exceeds the compilationTimeout duration (default: 90 seconds), the compiler throws an AlpacaTimeoutException with the message:

Alpaca compilation timeout

This is a compile-time error -- it means the macro took too long to build the parse table or validate patterns. Complex grammars with many rules and conflict resolutions are the most common trigger.

How to fix:

  • Increase the timeout: Set compilationTimeout to a longer duration (e.g., 180s or 300s) in your -Xmacro-settings
  • Simplify the grammar: Reduce the number of rules
  • Check for ambiguity: Highly ambiguous grammars generate larger parse tables, which take longer to build

The timeout is enforced by a background thread that runs during macro expansion. Setting compilationTimeout to Inf disables the timeout entirely (not recommended for CI environments).

Notes

  • debugDirectory must be an absolute path. Use build tool variables like $moduleDir (Mill) or ${baseDirectory.value} (SBT) to construct the path
  • Debug settings only affect compile-time behavior; they have no impact on runtime performance
  • The debug directory is created automatically if it doesn't exist
  • Log files are buffered and flushed periodically (every 4 seconds)
  • All log files are closed automatically when the JVM shuts down
  • Debug output can be quite verbose; use file output for detailed logging
  • Log files are named after your source files (e.g., MyLexer.scala.log) and placed in the debugDirectory