"com.lihaoyi" %% "sourcecode" % "0.1.0" // Scala-JVM
"com.lihaoyi" %%% "sourcecode" % "0.1.0" // Scala.jssourcecode is a small Scala library for that provides common "source code"
context to your program at runtime, similar to Python's __name__, C++'s
__LINE__ or Ruby's __FILE__. For example, you can ask for the file-name
and line number of the current file:
val file = sourcecode.File()
assert(file.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Tests.scala"))
val line = sourcecode.Line()
assert(line == 16)This might not be something you want to use for "business logic", but is very
helpful for debugging, logging and providing automatic diagnostics. This
information is also available via an implicit, letting you write functions
that automatically pull it in.
The kinds of compilation-time data that sourcecode provides are:
sourcecode.File: full path of the current file where the call occurssourcecode.Line: current line numbersourcecode.Name: the name of the nearest enclosing definition:val,class, whatever.sourcecode.FullName: the name of the nearest enclosing definition:val,class, whatever, prefixed by the names of all enclosingclasss,traits,objects orpackages. Note that this does not include other enclosingdefs,vals,vars orlazy vals`sourcecode.Enclosing: the name of the nearest enclosing definition:val,class, whatever, prefixed by the names of all enclosingclasss,traits,objects orpackages,defs,vals,vars orlazy vals`sourcecode.Text[T]: when you want to take a value of typeT, but also want to get the "source text" of that particular value. Note that this implicit requires the-Yrangeposcompiler flag to work, and will fail to compile otherwise. Also, if you have multiple statements in a{}block,sourcecode.Textwill only capture the source code for the last expression that gets returned.sourcecode.Name.Machine,sourcecode.FullName.Machineandsourcecode.Enclosing.Machinewhich are similar tosourcecode.Name,sourcecode.FullNameandsourcecode.Enclosingexcept they do not filter out synthetic method names; e.g. if you want to see the<init>names or<local foo>names as part of the path, use these instead.
All these are available both via () and as implicits, e.g. sourcecode.File
can be summoned via sourcecode.File() or implicitly[sourcecode.File].value.
This also means you can define functions that pull in this information
automatically:
def foo(arg: String)(implicit file: sourcecode.File) = {
... do something with arg ...
... do something with file.value ...
}
foo("hello") // the implicit sourcecode.File is filled in automaticallysourcecode does not rely on runtime reflection or stack inspection, and
is done at compile-time using macros. This means that it is both orders of
magnitude faster than e.g. getting file-name and line-numbers using stack
inspection, and also works on Scala.js where reflection and stack inspection
can't be used.
Here are a few examples of sourcecode's core functions being used in a
variety of contexts. Hopefully they will give you an idea of how the various
implicits behave:
package sourcecode
object Implicits {
def implicitRun() = {
val name = implicitly[sourcecode.Name]
assert(name.value == "name")
val fullName = implicitly[sourcecode.FullName]
assert(fullName.value == "sourcecode.Implicits.fullName")
val enclosing = implicitly[sourcecode.Enclosing]
assert(enclosing.value == "sourcecode.Implicits.implicitRun enclosing")
val pkg = implicitly[sourcecode.Pkg]
assert(pkg.value == "sourcecode")
val file = implicitly[sourcecode.File]
assert(file.value.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Implicits.scala"))
val line = implicitly[sourcecode.Line]
assert(line.value == 20)
lazy val myLazy = {
trait Bar{
val name = implicitly[sourcecode.Name]
assert(name.value == "name")
val fullName = implicitly[sourcecode.FullName]
assert(fullName.value == "sourcecode.Implicits.Bar.fullName")
val file = implicitly[sourcecode.File]
assert(file.value.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Implicits.scala"))
val line = implicitly[sourcecode.Line]
assert(line.value == 34)
val enclosing = implicitly[sourcecode.Enclosing]
assert(enclosing.value == "sourcecode.Implicits.implicitRun myLazy$lzy Bar#enclosing")
}
val b = new Bar{}
}
myLazy
}
}Note that in "normal" usage you would not directly call implicitly to summon
up sourcecode values; rather, you would add implicit parameters of these
types to your functions. That would make these values automatically available
to your functions without needing to manually keep passing them in. Apart from
summoning them via implicits, you can also use the apply method on each type
to pull them in using the () syntax:
package sourcecode
object Apply {
def applyRun() = {
val name = sourcecode.Name()
assert(name == "name")
val fullName = sourcecode.FullName()
assert(fullName == "sourcecode.Apply.fullName")
val enclosing = sourcecode.Enclosing()
assert(enclosing == "sourcecode.Apply.applyRun enclosing")
val pkg = sourcecode.Pkg()
assert(pkg == "sourcecode")
val file = sourcecode.File()
assert(file.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Apply.scala"))
val line = sourcecode.Line()
assert(line == 20)
lazy val myLazy = {
trait Bar{
val name = sourcecode.Name()
assert(name == "name")
val fullName = sourcecode.FullName()
assert(fullName == "sourcecode.Apply.Bar.fullName")
val file = sourcecode.File()
assert(file.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Apply.scala"))
val line = sourcecode.Line()
assert(line == 34)
val enclosing = sourcecode.Enclosing()
assert(enclosing == "sourcecode.Apply.applyRun myLazy$lzy Bar#enclosing")
}
val b = new Bar{}
}
myLazy
}
}By default, the various implicits all ignore any synthetic <init> or
<local Foo> methods that might be present:
package sourcecode
object NoSynthetic {
def run() = {
class EnumValue(implicit name: sourcecode.Name){
override def toString = name.value
}
object Foo extends EnumValue
assert(Foo.toString == "Foo")
object Bar{
assert(sourcecode.Name() == "Bar")
assert(sourcecode.FullName() == "sourcecode.NoSynthetic.Bar")
assert(sourcecode.Enclosing() == "sourcecode.NoSynthetic.run Bar")
}
Bar
}
}If you want these synthetic methods to be shown, use the .Machine versions
of each of these instead:
package sourcecode
object Synthetic {
def run() = {
class EnumValue(implicit name: sourcecode.Name.Machine){
override def toString = name.value
}
object Foo extends EnumValue
assert(Foo.toString == "<init>")
object Bar{
assert(sourcecode.Name.Machine() == "<local Bar>", sourcecode.Name())
assert(sourcecode.FullName.Machine() == "sourcecode.Synthetic.Bar.<local Bar>")
assert(sourcecode.Enclosing.Machine() == "sourcecode.Synthetic.run Bar.<local Bar>")
}
Bar
}
}At first it might seem strange to make use of these source-level details in your program: shouldn't a program's meaning not change under re-formatting and re-factoring?
It turns out that there are a number of entirely valid use cases for this sort of information that is both extremely handy, and also would not be surprising at all to a developer using your API. Here are a few example use cases:
You can use sourcecode.File and sourcecode.Line to define log functions
that automatically capture their line number and file-name
def log(foo: String)(implicit line: sourcecode.Line, file: sourcecode.File) = {
println(s"${file.value}:${line.value} $foo")
}
log("Foooooo") // sourcecode/shared/src/test/scala/sourcecode/Tests.scala:86 FoooooThis can be handy for letting you see where the log lines are coming from, without tediously tagging every log statement with a unique prefix. Furthermore, this happens at compile time, and is thus orders of magnitude faster than getting this information by generating stack traces, and works on Scala.js where stack-inspection does not.
You can use sourcecode.Name to define an enumeration-value factory function
that automatically assigns names to the enum values based on the name of the
val that it is assigned to
case class EnumValue(name: String){
override def toString = name
}
class Enum{
def value(implicit name: sourcecode.Name) = EnumValue(name.value)
}
object MyEnum extends Enum{
val firstItem = value // No need to pass in "firstItem" as a string!
val secondItem = value
}
assert(MyEnum.firstItem.toString == "firstItem")
assert(MyEnum.secondItem.toString == "secondItem")This is very handy, and this functionality is used in a number of libraries such as FastParse and Scalatags to provide a boilerplate-free experience while still providing good debuggability and convenience.
Sometimes you want to make sure that different enum values in differently
named enums (or even an enum of the same name in a different package!) are
given unique names. In that case, you can use sourcecode.FullName or
sourcecode.Enclosing to capture the full path e.g.
"com.mypkg.MyEnum.firstItem" and "com.mypkg.MyEnum.secondItem". You can
also use sourcecode.Name in an constructor, in which case it'll be picked
up during inheritance:
class EnumValue(implicit name: sourcecode.Name){
override def toString = name.value
}
object Foo extends EnumValue
println(Foo.toString)
assert(Foo.toString == "Foo")How many times have you written tedious code like
object Bar{
def foo(arg: String) = {
println("Bar.foo: " + arg)
}
}Where you have to prefix every print statement with the name of the enclosing
classes, objects or functions to make sure you can find your print output
2-3 minutes later? With source.Enclosing, you can get this for free:
def debug[V](value: sourcecode.Text[V])(implicit enclosing: sourcecode.Enclosing) = {
println(enclosing.value + " [" + value.source + "]: " + value.value)
}
class Foo(arg: Int){
debug(arg) // sourcecode.DebugRun.main Foo [arg]: 123
def bar(param: String) = {
debug(arg -> param)
}
}
new Foo(123).bar("lol") // sourcecode.DebugRun.main Foo#bar [arg -> param]: (123,lol)The Scala programming is a popular choice to embed domain-specific languages: that means that you start with some external language, e.g. this MathProg example
param m;
param n;
param l;
set I := 1 .. m;
set J := 1 .. n;
set K := 1 .. l;
param c{J};
param d{K};
param a{I, J};
var x{J} integer, >= 0;
var y{K} >= 0;The linked slides has more detail about what exactly this language does (it describes mathematical optimization problems). For a variety of reasons, you may prefer to write this as part of a Scala program instead: for example you may want Scala's IDE support, or its ability to define functions that help reduce boilerplate, or maybe you like the way the compiler provides type errors when you do the wrong thing.
A first attempt at converting this to Scala may look like this:
val m = param("m")
val n = param("n")
val l = param("l")
val I = set("I") := 1 to m
val J = set("J") := 1 to m
val K = set("K") := 1 to m
val c = param("c", J)
val d = param("d", K)
val a = param("a", I, J)
val x = xvar("x", J).integer >= 0
val y = xvar("y", K) >= 0There's a bunch of duplication around the names of the vals: each val
has its name repeated in a string that gets passed to the expression on the
right. This is for the program to use the name of the val later: for example
when printing error messages, or the results of the computation, you want to
see which vals are involved! Thus you end up duplicating the names over and
over and over.
With sourcecode, you can easily define param set and xvar as taking
sourcecode.Name, thus eliminating all the boilerplate involved in duplicating
names:
val m = param
val n = param
val l = param
val I = set := 1 to m
val J = set := 1 to m
val K = set := 1 to m
val c = param(J)
val d = param(K)
val a = param(I, J)
val x = xvar(J).integer >= 0
val y = xvar(K) >= 0-
Ignore
<local foo>and<init>symbols when determiningsourcecode.Name,sourcecode.FullNameorsourcecode.Enclosing. If you want these, use thesourcecode.Name.Machine/sourcecode.FullName.Machine/sourcecode.Enclosing.Machineimplicits instead. -
Add
sourcecode.Textimplicit to capture source code of an expression -
Add implicit conversions to
sourcecode.*, so you can pass in aStringto manually satisfy and implicit wanting asourcecode.Nameorsourcecode.FullNameorsourcecode.File, anIntto satisfy an implicit asking forsourcecode.Line -
sourcecode.Enclosinghas been simplified to take a singleStringrather than the previousVector[Chunk]. -
Added the
sourcecode.Pkgimplicit, which provides the current enclosing package without any of theclasss/objects/defs/etc.. Can be subtracted fromsourcecode.Enclosingif you only want theclasss/objects/defs/etc.
- First release