mirror of https://github.com/sbt/sbt.git
Datatype generating subproject, to be used to represent public API of sources
This commit is contained in:
parent
c4d039a594
commit
74202668c6
|
|
@ -0,0 +1,102 @@
|
|||
Type
|
||||
SimpleType
|
||||
Projection
|
||||
prefix : SimpleType
|
||||
id : String
|
||||
Singleton
|
||||
path: Path
|
||||
Parameterized
|
||||
baseType : SimpleType
|
||||
typeArguments: SimpleType
|
||||
Annotated
|
||||
baseType : SimpleType
|
||||
annotations : Annotation*
|
||||
Structure
|
||||
parents : Annotated*
|
||||
declarations: Definition*
|
||||
Existential
|
||||
baseType : Type
|
||||
clause: TypeParameter*
|
||||
|
||||
Source
|
||||
packages : Package*
|
||||
definitions: Definition*
|
||||
|
||||
Package
|
||||
components: String*
|
||||
|
||||
Definition
|
||||
access: Access
|
||||
modifiers: Modifiers
|
||||
FieldLike
|
||||
tpe : Type
|
||||
Val
|
||||
Var
|
||||
ParameterizedDefinition
|
||||
typeParameters: TypeParameter*
|
||||
Def
|
||||
valueParameters: ParameterList*
|
||||
returnType: Type
|
||||
ClassLike
|
||||
selfType: Type
|
||||
structure: Structure
|
||||
TypeMember
|
||||
name: String
|
||||
TypeAlias
|
||||
tpe: Type
|
||||
TypeDeclaration
|
||||
upperBound: Type
|
||||
lowerBound: Type
|
||||
|
||||
Access
|
||||
Public
|
||||
Qualified
|
||||
qualifier: Qualifier
|
||||
Protected
|
||||
Private
|
||||
|
||||
Qualifier
|
||||
Unqualified
|
||||
ThisQualifier
|
||||
IdQualifier
|
||||
value: String
|
||||
|
||||
Modifiers
|
||||
isDeferred: Boolean
|
||||
isOverride: Boolean
|
||||
isFinal: Boolean
|
||||
isSealed: Boolean
|
||||
isImplicit: Boolean
|
||||
isLazy: Boolean
|
||||
|
||||
ParameterList
|
||||
parameters: MethodParameter*
|
||||
isImplicit: Boolean
|
||||
MethodParameter
|
||||
name: String
|
||||
tpe: Type
|
||||
hasDefault: Boolean
|
||||
modifier: ParameterModifier
|
||||
|
||||
TypeParameter
|
||||
typeParameters : TypeParameter*
|
||||
variance: Variance
|
||||
upperBound: Type
|
||||
lowerBound: Type
|
||||
|
||||
Annotation
|
||||
base: SimpleType
|
||||
arguments: String*
|
||||
|
||||
enum Variance : Contravariant, Covariant, Invariant
|
||||
enum ParameterModifier : Vararg, Plain, ByName
|
||||
|
||||
Path
|
||||
components: PathComponent*
|
||||
|
||||
PathComponent
|
||||
Super
|
||||
qualifier: Path
|
||||
This
|
||||
Id
|
||||
id: String
|
||||
|
|
@ -18,9 +18,11 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
|
||||
val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub)
|
||||
val logSub = baseProject(utilPath / "log", "Logging", interfaceSub)
|
||||
val datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub)
|
||||
|
||||
val testSub = project("scripted", "Test", new TestProject(_), ioSub)
|
||||
|
||||
val compileAPISub = project(compilePath / "api", "Source API", new CompilerAPIProject(_), datatypeSub)
|
||||
val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub)
|
||||
|
||||
val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub)
|
||||
|
|
@ -141,6 +143,24 @@ class XSbt(info: ProjectInfo) extends ParentProject(info)
|
|||
{
|
||||
val process = "org.scala-tools.sbt" % "process" % "0.1"
|
||||
}
|
||||
/** This subproject generates a hierarchy of Java interfaces and Scala implementations according to a basic format.*/
|
||||
class CompilerAPIProject(info: ProjectInfo) extends Base(info)
|
||||
{
|
||||
override def watchPaths = super.watchPaths +++ apiDefinitionPaths
|
||||
override def mainSourceRoots = srcManagedPath
|
||||
def srcManagedPath = ("src_managed" ##)
|
||||
def generatedBasePath = srcManagedPath / "main" / "java"
|
||||
/** Files that define the datatypes.*/
|
||||
def apiDefinitionPaths: PathFinder = "definition"
|
||||
def apiDefinitions = apiDefinitionPaths.get.toList.map(_.absolutePath)
|
||||
/** Delete up the generated sources*/
|
||||
lazy val cleanManagedSrc = cleanTask(srcManagedPath)
|
||||
/** Runs the generator compiled by 'compile', putting the classes in src_managed and processing the definitions 'apiDefinitions'. */
|
||||
lazy val generateSource = generateSourceAction dependsOn(cleanManagedSrc)
|
||||
def generateSourceAction = runTask(datatypeSub.getMainClass(true), datatypeSub.runClasspath, "xsbti.api" :: generatedBasePath.absolutePath :: apiDefinitions)
|
||||
/** compiles the generated sources */
|
||||
override def compileAction = super.compileAction dependsOn(generateSource)
|
||||
}
|
||||
class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO with TestWithLog
|
||||
{
|
||||
def xTestClasspath = projectClasspath(Configurations.Test)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package xsbt.api
|
||||
|
||||
import java.io.File
|
||||
import xsbt.FileUtilities
|
||||
import Function.tupled
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class DatatypeParser extends NotNull
|
||||
{
|
||||
val WhitespacePattern = Pattern compile """\s*"""//(?>\#(.*))?"""
|
||||
val EnumPattern = Pattern compile """enum\s+(\S+)\s*:\s*(.+)"""
|
||||
val ClassPattern = Pattern compile """(\t*)(\S+)\s*"""
|
||||
val MemberPattern = Pattern compile """(\t*)(\S+)\s*:\s*([^\s*]+)([*]?)"""
|
||||
|
||||
def processWhitespaceLine(l: Array[String], line: Int) = new WhitespaceLine(l.mkString, line)
|
||||
def processEnumLine(l: Array[String], line: Int) = new EnumLine(l(0), l(1).split(",").map(_.trim), line)
|
||||
def processClassLine(l: Array[String], line: Int) = new ClassLine(l(1), l(0).length, line)
|
||||
def processMemberLine(l: Array[String], line: Int) = new MemberLine(l(1), l(2), l(3).isEmpty, l(0).length, line)
|
||||
|
||||
def error(l: Line, msg: String): Nothing = error(l.line, msg)
|
||||
def error(line: Int, msg: String): Nothing = throw new RuntimeException("{line " + line + "} " + msg)
|
||||
|
||||
def parseFile(file: File): Seq[Definition] =
|
||||
{
|
||||
val (open, closed) = ( (Array[ClassDef](), List[Definition]()) /: parseLines(file) ) {
|
||||
case ((open, defs), line) => processLine(open, defs, line)
|
||||
}
|
||||
open ++ closed
|
||||
}
|
||||
def parseLines(file: File): Seq[Line] = getLines(FileUtilities.read(file)).toList.zipWithIndex.map(tupled(parseLine))
|
||||
def getLines(content: String): Seq[String] = content split "(?m)$(?s:.)(?!$)*(^|\\Z)"
|
||||
def parseLine(line: String, lineNumber: Int): Line =
|
||||
matchPattern(WhitespacePattern -> processWhitespaceLine _, EnumPattern -> processEnumLine _,
|
||||
ClassPattern -> processClassLine _, MemberPattern -> processMemberLine _)(line, lineNumber)
|
||||
type Handler = (Array[String], Int) => Line
|
||||
def matchPattern(patterns: (Pattern, Handler)*)(line: String, lineNumber: Int): Line =
|
||||
patterns.flatMap { case (pattern, f) => matchPattern(pattern, f)(line, lineNumber) }.headOption.getOrElse {
|
||||
error(lineNumber, "Invalid line, expected enum, class, or member definition")
|
||||
}
|
||||
def matchPattern(pattern: Pattern, f: Handler)(line: String, lineNumber: Int): Option[Line] =
|
||||
{
|
||||
val matcher = pattern.matcher(line)
|
||||
if(matcher.matches)
|
||||
{
|
||||
val count = matcher.groupCount
|
||||
val groups = (for(i <- 1 to count) yield matcher.group(i)).toArray[String]
|
||||
Some( f(groups, lineNumber) )
|
||||
}
|
||||
else
|
||||
None
|
||||
}
|
||||
|
||||
def processLine(open: Array[ClassDef], definitions: List[Definition], line: Line): (Array[ClassDef], List[Definition]) =
|
||||
{
|
||||
line match
|
||||
{
|
||||
case w: WhitespaceLine => (open, definitions)
|
||||
case e: EnumLine => (Array(), new EnumDef(e.name, e.members) :: open.toList ::: definitions)
|
||||
case m: MemberLine =>
|
||||
if(m.level == 0 || m.level > open.length) error(m, "Member must be declared in a class definition")
|
||||
else withCurrent(open, definitions, m.level) { c => List( c + m) }
|
||||
case c: ClassLine =>
|
||||
if(c.level == 0) (Array( new ClassDef(c.name, None, Nil) ), open.toList ::: definitions)
|
||||
else if(c.level > open.length) error(c, "Class must be declared as top level or as a subclass")
|
||||
else withCurrent(open, definitions, c.level) { p => p :: new ClassDef(c.name, Some(p), Nil) :: Nil}
|
||||
}
|
||||
}
|
||||
private def withCurrent(open: Array[ClassDef], definitions: List[Definition], level: Int)(onCurrent: ClassDef => Seq[ClassDef]): (Array[ClassDef], List[Definition]) =
|
||||
{
|
||||
require(0 < level && level <= open.length)
|
||||
val closed = open.drop(level).toList
|
||||
val newOpen = open.take(level - 1) ++ onCurrent(open(level - 1))
|
||||
( newOpen.toArray, closed ::: definitions )
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package xsbt.api
|
||||
|
||||
sealed trait Definition extends NotNull
|
||||
{
|
||||
val name: String
|
||||
}
|
||||
final class ClassDef(val name: String, val parent: Option[ClassDef], val members: Seq[MemberDef]) extends Definition
|
||||
{
|
||||
def allMembers = members ++ inheritedMembers
|
||||
def inheritedMembers: Seq[MemberDef] = parent.toList.flatMap(_.allMembers)
|
||||
def + (m: MemberLine) = new ClassDef(name, parent, members ++ Seq(new MemberDef(m.name, m.tpe, m.single)) )
|
||||
}
|
||||
final class EnumDef(val name: String, val members: Seq[String]) extends Definition
|
||||
|
||||
final class MemberDef(val name: String, val tpe: String, val single: Boolean) extends NotNull
|
||||
{
|
||||
def javaType = tpe + (if(single) "" else "[]")
|
||||
def scalaType = if(single) tpe else "Array[" + tpe + "]"
|
||||
def asScalaDeclaration = name + ": " + scalaType
|
||||
def asJavaDeclaration = javaType + " " + name
|
||||
def mapType(f: String => String) = new MemberDef(name, f(tpe), single)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package xsbt.api
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** Generates a datatype hierarchy from a definition file.*/
|
||||
object GenerateDatatypes
|
||||
{
|
||||
/** Arguments: <base package name> <base directory> <input file>+*/
|
||||
def main(args: Array[String])
|
||||
{
|
||||
if(args.length < 3)
|
||||
{
|
||||
System.err.println("Invalid number of arguments, expected package, base directory, and files to process")
|
||||
System.exit(1)
|
||||
}
|
||||
else
|
||||
{
|
||||
val packageName = args(0).trim
|
||||
require(!packageName.isEmpty)
|
||||
|
||||
val baseDirectory = new File(args(1))
|
||||
baseDirectory.mkdirs
|
||||
|
||||
val files = args.drop(2).map(new File(_))
|
||||
// parse the files, getting all interface and enums
|
||||
val parser = new DatatypeParser
|
||||
val definitions = files.flatMap(parser.parseFile)
|
||||
|
||||
// create the interfaces, enums, and class implementations
|
||||
val generator = new Generator(packageName, baseDirectory)
|
||||
generator.writeDefinitions(definitions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package xsbt.api
|
||||
|
||||
import java.io.File
|
||||
import xsbt.FileUtilities
|
||||
|
||||
class Generator(pkgName: String, baseDirectory: File)
|
||||
{
|
||||
def writeDefinitions(ds: Iterable[Definition]) =
|
||||
{
|
||||
val (nameSet, duplicates) =
|
||||
( (Set[String](), Set[String]()) /: ds.map(_.name)) {
|
||||
case ((nameSet, duplicates), name) =>
|
||||
if(nameSet.contains(name)) (nameSet, duplicates + name) else (nameSet + name, duplicates)
|
||||
}
|
||||
if(duplicates.isEmpty)
|
||||
ds.foreach(writeDefinition)
|
||||
else
|
||||
error("Duplicate names:\n\t" + duplicates.mkString("\n\t"))
|
||||
}
|
||||
def writeDefinition(d: Definition) = d match { case e: EnumDef => write(e); case c: ClassDef => write(c) }
|
||||
def write(e: EnumDef)
|
||||
{
|
||||
val content =
|
||||
"public enum " + e.name + " {" +
|
||||
e.members.mkString("\n\t", ",\n\t", "\n") +
|
||||
"}"
|
||||
writeSource(e.name, content)
|
||||
}
|
||||
def write(c: ClassDef): Unit = writeSource(c.name + ".java", classContent(c))
|
||||
def classContent(c: ClassDef): String =
|
||||
{
|
||||
val normalizedMembers = c.members.map(normalize)
|
||||
val fields = normalizedMembers.map(m => "private final " + m.asJavaDeclaration + ";")
|
||||
val accessors = normalizedMembers.map(m => "public final " + m.asJavaDeclaration + "() \n\t{\n\t\treturn " + m.name + ";\n\t}")
|
||||
val parameters = c.allMembers.map(_.asJavaDeclaration)
|
||||
val assignments = normalizedMembers.map(m => "this." + m.name + " = " + m.name + ";")
|
||||
val superConstructor =
|
||||
{
|
||||
val inherited = c.inheritedMembers
|
||||
if(inherited.isEmpty) "" else "super(" + inherited.map(_.name).mkString(", ") + ");"
|
||||
}
|
||||
|
||||
val constructor = "public " + c.name + "(" + parameters.mkString(", ") + ")\n\t" +
|
||||
"{\n\t\t" +
|
||||
superConstructor + "\n\t\t" +
|
||||
assignments.mkString("\n\t\t") + "\n\t" +
|
||||
"}"
|
||||
"public class " + c.name + c.parent.map(" extends " + _.name + " ").getOrElse("") + "\n" +
|
||||
"{\n\t" +
|
||||
constructor + "\n\t" +
|
||||
(fields ++ accessors).mkString("\n\t") + "\n" +
|
||||
"}"
|
||||
}
|
||||
def normalize(m: MemberDef): MemberDef =
|
||||
m.mapType(tpe => if(primitives(tpe.toLowerCase)) tpe.toLowerCase else tpe)
|
||||
|
||||
def writeSource(name: String, content: String)
|
||||
{
|
||||
import Paths._
|
||||
val file =baseDirectory / packagePath / name
|
||||
file.getParentFile.mkdirs()
|
||||
FileUtilities.write(file, "package " + pkgName + ";\n\n" + content)
|
||||
}
|
||||
private def packagePath = pkgName.replace('.', File.separatorChar)
|
||||
private val primitives = Set("int", "boolean", "float", "long", "short", "byte", "char", "double")
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package xsbt.api
|
||||
|
||||
sealed trait Line extends NotNull { val line: Int }
|
||||
final class ClassLine(val name: String, val level: Int, val line: Int) extends Line
|
||||
final class EnumLine(val name: String, val members: Seq[String], val line: Int) extends Line
|
||||
final class MemberLine(val name: String, val tpe: String, val single: Boolean, val level: Int, val line: Int) extends Line
|
||||
final class WhitespaceLine(val comment: String, val line: Int) extends Line
|
||||
Loading…
Reference in New Issue