Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Scala library providing "source" metadata to your program, similar to Python's __name__, C++'s __LINE__ or Ruby's __FILE__.

License

NotificationsYou must be signed in to change notification settings

com-lihaoyi/sourcecode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sourcecode is a small Scala library 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-nameand line number of the current file, either through the() syntax or via animplicit:

valfile= sourcecode.File()assert(file.endsWith("/sourcecode/shared/src/test/scala/sourcecode/Tests.scala"))valline= implicitly[sourcecode.Line]assert(line==16)

This might not be something you want to use for "business logic", but is veryhelpful for things likedebugging,logging orproviding automatic diagnostics forDSLs.This information is also available via animplicit, letting you write functionsthat automatically pull it in.

Using SourceCode on code dealing with lots of anonymous functions or anonymousclasses can easily turn what you see in your debug printouts from this:

Before

To this:

After

By capturing source information you can use to give your objects and functionmeaningful names that tell you where they were defined, automatically withoutneeding you to manually assign a string-ID to every anonymous function oranonymous class you define all over your code base.

If you like using Sourcecode, you might also enjoy this book by the author whichteaches you Scala in a similarly simple and straightforward way:

Table of Contents

Overview

The kinds of compilation-time data thatsourcecode provides are:

  • sourcecode.File: full path of the current file where the call occurs
  • sourcecode.FileName: name of the current file where the call occurs; lessverbose thansourcecode.File but often enough for debugging purposes
  • sourcecode.Line: current line number
  • sourcecode.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 doesnot 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 alsowant to get the "source text" of that particular value. Note that ifyou have multiple statements in a{} block,sourcecode.Text will onlycapture the source code for the last expression that gets returned. Thisimplicit is slightly experimental; be sure to report any bugs you find!
  • sourcecode.Args: the arguments that were provided to the nearest enclosingmethod
  • sourcecode.Name.Machine,sourcecode.FullName.Machine andsourcecode.Enclosing.Machine which are similar tosourcecode.Name,sourcecode.FullName andsourcecode.Enclosing except they do not filterout 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.Filecan be summoned viasourcecode.File() orimplicitly[sourcecode.File].value.This also means you can define functions that pull in this informationautomatically:

deffoo(arg:String)(implicitfile: sourcecode.File)= {  ...do somethingwith arg ...  ...do somethingwith file.value ...}foo("hello")// the implicit sourcecode.File is filled in automatically

sourcecode does not rely on runtime reflection or stack inspection, andis done at compile-time using macros. This means that it is both orders ofmagnitude faster than e.g. getting file-name and line-numbers using stackinspection, and also works on Scala.js where reflection and stack inspectioncan't be used.

Download

Mill

sourcecode is published to Maven Central.

defivyDeps=Agg(ivy"com.lihaoyi::sourcecode:0.4.2",// Scala-JVMivy"com.lihaoyi::sourcecode::0.4.2"// Scala.js / Scala Native)

sbt

"com.lihaoyi"%%"sourcecode"%"0.4.2"// Scala-JVM"com.lihaoyi"%%%"sourcecode"%"0.4.2"// Scala.js / Scala Native

Examples

Here are a few examples ofsourcecode's core functions being used in avariety of contexts. Hopefully they will give you an idea of how the variousimplicits behave:

packagesourcecodeobjectImplicits {defimplicitRun()= {valname= implicitly[sourcecode.Name]    assert(name.value=="name")valfullName= implicitly[sourcecode.FullName]    assert(fullName.value=="sourcecode.Implicits.fullName")valenclosing= implicitly[sourcecode.Enclosing]    assert(enclosing.value=="sourcecode.Implicits.implicitRun enclosing")valpkg= implicitly[sourcecode.Pkg]    assert(pkg.value=="sourcecode")valfile= implicitly[sourcecode.File]    assert(file.value.endsWith("/sourcecode/Implicits.scala"))valfileName= implicitly[sourcecode.FileName]    assert(fileName.value=="Implicits.scala")valline= implicitly[sourcecode.Line]    assert(line.value==23)lazyvalmyLazy= {traitBar{valname= implicitly[sourcecode.Name]        assert(name.value=="name")valfullName= implicitly[sourcecode.FullName]        assert(fullName.value=="sourcecode.Implicits.Bar.fullName")valfile= implicitly[sourcecode.File]        assert(file.value.endsWith("/sourcecode/Implicits.scala"))valfileName= implicitly[sourcecode.FileName]        assert(fileName.value=="Implicits.scala")valline= implicitly[sourcecode.Line]        assert(line.value==40)valenclosing= implicitly[sourcecode.Enclosing]        assert(          (enclosing.value=="sourcecode.Implicits.implicitRun myLazy$lzy Bar#enclosing")||          (enclosing.value=="sourcecode.Implicits.implicitRun myLazy Bar#enclosing")// encoding changed in Scala 2.12        )      }valb=newBar{}    }    myLazy  }}

Note that in "normal" usage you would not directly callimplicitly to summonupsourcecode values; rather, you would add implicit parameters of thesetypes to your functions. That would make these values automatically availableto your functions without needing to manually keep passing them in. Apart fromsummoning them via implicits, you can also use theapply method on each typeto pull them in using the() syntax:

packagesourcecodeobjectImplicits {defimplicitRun()= {valname= implicitly[sourcecode.Name]    assert(name.value=="name")valfullName= implicitly[sourcecode.FullName]    assert(fullName.value=="sourcecode.Implicits.fullName")valenclosing= implicitly[sourcecode.Enclosing]    assert(enclosing.value=="sourcecode.Implicits.implicitRun enclosing")valpkg= implicitly[sourcecode.Pkg]    assert(pkg.value=="sourcecode")valfile= implicitly[sourcecode.File]    assert(file.value.endsWith("/sourcecode/Implicits.scala"))valfileName= implicitly[sourcecode.FileName]    assert(fileName.value=="Implicits.scala")valline= implicitly[sourcecode.Line]    assert(line.value==23)lazyvalmyLazy= {traitBar{valname= implicitly[sourcecode.Name]        assert(name.value=="name")valfullName= implicitly[sourcecode.FullName]        assert(fullName.value=="sourcecode.Implicits.Bar.fullName")valfile= implicitly[sourcecode.File]        assert(file.value.endsWith("/sourcecode/Implicits.scala"))valfileName= implicitly[sourcecode.FileName]        assert(fileName.value=="Implicits.scala")valline= implicitly[sourcecode.Line]        assert(line.value==40)valenclosing= implicitly[sourcecode.Enclosing]        assert(          (enclosing.value=="sourcecode.Implicits.implicitRun myLazy$lzy Bar#enclosing")||          (enclosing.value=="sourcecode.Implicits.implicitRun myLazy Bar#enclosing")// encoding changed in Scala 2.12        )      }valb=newBar{}    }    myLazy  }}

By default, the various implicits all ignore any synthetic<init>,<local Foo> or$anonfun methods that might be present:

packagesourcecodeobjectNoSynthetic {defrun()= {classEnumValue(implicitname: sourcecode.Name){overridedeftoString= name.value    }objectFooextendsEnumValue    assert(Foo.toString=="Foo")objectBar{      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 versionsof each of these instead:

packagesourcecodeobjectSynthetic {defrun()= {classEnumValue(implicitname: sourcecode.Name.Machine){overridedeftoString= name.value    }objectFooextendsEnumValue    assert(Foo.toString=="<init>")objectBar{      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  }}

Hopefully this has given you a reasonable feel forwhat* sourcecode does. Youmay still be wonderingwhy we would want any of this: what could we possiblyuse these things for? Why would we want to write code that depends on ourpackage paths or variable names? The section below will provide use cases thatyou will hopefully be able to relate to.

Use Cases

At first it might seem strange to make use of these source-level details inyour program: shouldn't a program's meaning not change under re-formatting andre-factoring?

It turns out that there are a number of entirely valid use cases for this sortof information that is both extremely handy, and also would not be surprisingat all to a developer using your API. Here are a few example use cases:

Logging

You can usesourcecode.File andsourcecode.Line to definelog functionsthat automatically capture their line number and file-name

deflog(foo:String)(implicitline: sourcecode.Line,file: sourcecode.File)= {  println(s"${file.value}:${line.value}$foo")}log("Foooooo")// sourcecode/shared/src/test/scala/sourcecode/Tests.scala:86 Fooooo

This 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 magnitudefaster than getting this information by generating stack traces, and workson Scala.js where stack-inspection does not. Lastly, if you want additionalinformation such as method names, class names, or packages to be provided toyour logging function, you can easily do so by asking for thesourcecode.Nameorsourcecode.FullName orsourcecode.Pkg implicits.

Enums

You can usesourcecode.Name to define an enumeration-value factory functionthat automatically assigns names to the enum values based on the name of theval that it is assigned to

packagesourcecodeobjectEnumExample {defrun()= {caseclassEnumValue(name:String){overridedeftoString= name    }classEnum{defvalue(implicitname: sourcecode.Name)=EnumValue(name.value)    }objectMyEnumextendsEnum{valfirstItem= valuevalsecondItem= value    }    assert(MyEnum.firstItem.toString=="firstItem")    assert(MyEnum.secondItem.toString=="secondItem")  }}

This is very handy, and this functionality is used in a number of librariessuch asFastParse andScalatags to providea boilerplate-free experience while still providing good debuggabilityand convenience.

Sometimes you want to make sure that different enum values in differentlynamed enums (or even an enum of the same name in a different package!) aregiven unique names. In that case, you can usesourcecode.FullName orsourcecode.Enclosing to capture the full path e.g."com.mypkg.MyEnum.firstItem" and"com.mypkg.MyEnum.secondItem":

packagesourcecodeobjectEnumFull {defrun()= {caseclassEnumValue(name:String){overridedeftoString= name    }classEnum{defvalue(implicitname: sourcecode.FullName)=EnumValue(name.value)    }objectMyEnumextendsEnum{valfirstItem= valuevalsecondItem= value    }    assert(MyEnum.firstItem.toString=="sourcecode.EnumFull.MyEnum.firstItem")    assert(MyEnum.secondItem.toString=="sourcecode.EnumFull.MyEnum.secondItem")  }}

You can also usesourcecode.Name in an constructor, in which case it'll bepicked up during inheritance:

classEnumValue(implicitname: sourcecode.Name){overridedeftoString= name.value}objectFooextendsEnumValueprintln(Foo.toString)assert(Foo.toString=="Foo")

Debug Prints

How many times have you written tedious code like

objectBar{deffoo(arg:String)= {    println("Bar.foo:"+ arg)  }}

Where you have to prefix every print statement with the name of the enclosingclasses, objects or functions to make sure you can find your print output2-3 minutes later? Withsource.Enclosing, you can get this for free:

defdebug[V](value: sourcecode.Text[V])(implicitenclosing: sourcecode.Enclosing)= {  println(enclosing.value+" ["+ value.source+"]:"+ value.value)}classFoo(arg:Int){  debug(arg)// sourcecode.DebugRun.main Foo [arg]: 123defbar(param:String)= {    debug(arg-> param)  }}newFoo(123).bar("lol")// sourcecode.DebugRun.main Foo#bar [arg -> param]: (123,lol)

You can easily vary the amount of verbosity, e.g. by swapping thesourcecode.Enclosing for asourcecode.Name if you think it's too verbose:

defdebug[V](value: sourcecode.Text[V])(implicitname: sourcecode.Name)= {  println(name.value+" ["+ value.source+"]:"+ value.value)}classFoo(arg:Int){  debug(arg)// Foo [arg]: 123defbar(param:String)= {    debug(param-> arg)  }}newFoo(123).bar("lol")// bar [param]: lol

Or leaving it out entirely:

defdebug[V](value: sourcecode.Text[V])= {  println("["+ value.source+"]:"+ value.value)}classFoo(arg:Int){  debug(arg)// [arg]: 123defbar(param:String)= {    debug(param-> arg)  }}newFoo(123).bar("lol")// [param]: lol

Thus you can easily configure how much information yourdebug helper methodneeds, at its definition, without having to hunt all over your codebase for thevariousdebug call-sites you left lying around and manually tweaking theverbosity of each one. Furthermore, if you want additional information likesourcecode.Line orsourcecode.File, that's all just one implicit away.

ThePPrintlibrary provides apprint.log method that does exactly this: prints out thevalue provided (in this case pretty-printing it with colors and nice formatting& indentation) together with the enclosing context and line number, so youcan easily distinguish your individual prints later:

scala>classFoo{|defbar(grid:Seq[Seq[Int]])= {|// automatically capture and print out source context|     pprint.log(grid, tag="grid")|   }| }definedclassFooscala>newFoo().bar(Seq(0 until10,10 until20,20 until30))pkg.Foo#bar"grid":12List(Range(0,1,2,3,4,5,6,7,8,9),Range(10,11,12,13,14,15,16,17,18,19),Range(20,21,22,23,24,25,26,27,28,29))

pprint.log is itself defined as

deflog[T:PPrint](value:T,tag:String="")                  (implicitcfg:Config=Config.Colors.PPrintConfig,path: sourcecode.Enclosing,line: sourcecode.Line)= ...

Usingsourcecode.Enclosing andsourcecode.Line to provide the context tobe printed. You can, or course, define your ownlog method in the same way,customizing it to print or not-print exactly what you want to see via theimplicits thatsourcecode provides!

sourcecode.Args can be used to access all parameters that where providedto a method:

defdebug(implicitname: sourcecode.Name,args: sourcecode.Args):Unit= {  println(name.value+ args.value.map(_.map(a=> a.source+"="+ a.value).mkString("(",",",")")).mkString(""))}deffoo(bar:String,baz:Int)(p:Boolean):Unit= {  debug}foo("baz",42)(true)// foo(bar=baz, baz=42)(p=true)

Embedding Domain-Specific Languages

Scala is a popular language in which to embed domain-specific languages:that means that you start with some external language, e.g. thisMathProg example

param m;param n;param l;setI:=1 .. m;setJ:=1 .. n;setK:=1 .. l;param c{J};param d{K};param a{I,J};varx{J} integer,>=0;vary{K}>=0;

The linked slides has more detail about what exactly this language does (itdescribes mathematical optimization problems). For a variety of reasons, youmay prefer to write this as part of a Scala program instead: for example youmay want Scala's IDE support, or its ability to define functions that helpreduce boilerplate, or maybe you like the way the compiler provides type errorswhen you do the wrong thing.

A first attempt at converting this to Scala may look like this:

valm= param("m")valn= param("n")vall= param("l")valI= set("I"):=1 to mvalJ= set("J"):=1 to mvalK= set("K"):=1 to mvalc= param("c",J)vald= param("d",K)vala= param("a",I,J)valx= xvar("x",J).integer>=0valy= xvar("y",K)>=0

There's a bunch of duplication around the names of thevals: eachvalhas its name repeated in a string that gets passed to the expression on theright. This is for the program to use the name of theval later: for examplewhen printing error messages, or the results of the computation, you want tosee whichvals are involved! Thus you end up duplicating the names over andover and over.

With sourcecode, you can easily defineparamset andxvar as takingimplicitsourcecode.Names, thus eliminating all the boilerplate involved induplicating names:

valm= paramvaln= paramvall= paramvalI= set:=1 to mvalJ= set:=1 to mvalK= set:=1 to mvalc= param(J)vald= param(K)vala= param(I,J)valx= xvar(J).integer>=0valy= xvar(K)>=0

The popularFastParse parser-combinatorlibrary uses sourcecode for exactly this use case

importfastparse.all._valA=P("aa" )valB=P("bb" )valC=P( (A|B).rep(1) )C.parse("aabb")// Success((), 4)C.parse("X")// Failure((A | B):1:1 ..."X")

As you can see, the names of the rulesA andB are embedded in the errormessages for parse failures. This makes debugging parsers far easier, whilesaving you the effort of duplicating the name of the parser in possiblyhundreds of rules in a large parser. In this case, it is theP(...) functionwhich takes an implicitsourcecode.Name that does this work:

defP[T](p:=>Parser[T])(implicitname: sourcecode.Name):Parser[T]=    parsers.Combinators.Rule(name.value, ()=> p)

And forwards the name on to the actualRule object, which can make use of itin its.toString method.

Version History

0.4.2

  • Fix NullPointerException when using sourcecode.File in Scala 3 repl (#161)
  • Correctly handle backticked macro keyword in Scala 3 (#163)

0.4.1

  • Fix NullPointerException when using fileName under Scala 3 repl (#153)

0.4.0

  • Support for Scala-Native 0.5.0
  • Minimum version of Scala 2 raised to 2.12.x
  • Minimum version of Scala 3 raised to 3.3.1

0.3.1

  • Reference method values when buildingArgs in Scala 3#126

0.3.0

  • Drop support for Scala.js 0.6
  • Bump Scala.js to 1.10 (minimum version supported is 1.8)
  • Bump Scala versions to latest (2.12.16, 2.13.8, 3.1.3)

0.2.7

  • Support Scala 3.0.0

0.2.6

  • Support Scala 3.0.0-RC3

0.2.5

  • Support Scala 3.0.0-RC2

0.2.2

  • Support for Scala-Native 0.4.0

0.1.9

  • $anonfun segments are now ignored bysourcecode.Enclosing

0.1.8

  • Addsourceco.FileName implicit

0.1.7

  • Support for Scala 2.13.0 final

0.1.5

  • Upgrade Scala, ScalaJS, Scala Native versions (a21c11a)

0.1.4

  • Add Scala Native support#34
  • Add 2.13.0-M1 support,#30
  • Add OSGi header to jar manifests,#32

0.1.3

0.1.2

  • Addsourcecode.Args implicit, which can be used to capture debugging informationabout the nearest enclosing function call for logging/debugging, thanks toBenjamin Hagemeister

  • Attempted fix for#17 and#13, thanks toSimeon H.K. Fitch

0.1.1

  • Ignore<local foo> and<init> symbols when determiningsourcecode.Name,sourcecode.FullName orsourcecode.Enclosing. If you want these, use thesourcecode.Name.Machine/sourcecode.FullName.Machine/sourcecode.Enclosing.Machineimplicits instead.

  • Addsourcecode.Text implicit to capture source code of an expression

  • Add implicit conversions tosourcecode.*, so you can pass in aStringto manually satisfy and implicit wanting asourcecode.Name orsourcecode.FullName orsourcecode.File, anInt to satisfy an implicitasking forsourcecode.Line

  • sourcecode.Enclosing has been simplified to take a singleString ratherthan the previousVector[Chunk].

  • Added thesourcecode.Pkg implicit, which provides the currentenclosing package without any of theclasss/objects/defs/etc.. Can besubtracted fromsourcecode.Enclosing if youonly want theclasss/objects/defs/etc.

0.1.0

  • First release

About

Scala library providing "source" metadata to your program, similar to Python's __name__, C++'s __LINE__ or Ruby's __FILE__.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp