I have to write some code that only uses Java libraries for testing purposes. I can’t remember the Java syntax anymore that well, so I would like to write it using Scala syntax. Is there a way to make sure none of the scala standard libraries get used? An sbt option perhaps? I can make sure not to use any in the imports, but it would be nice if I could make sure the compiler does some extra verification for me.
There are also some constructs that I have to avoid. I think case classes (as they use tuples, enums, … (what have I forgotten?)
I think if you add -Yimports:java.lang
to scalacOptions
then nothing from the scala namespace will be imported by default. That should help considerably.
Varargs use scala.Seq[String]
. Lambdas and eta-expanded methods will compile to instances of scala.FunctionN
, unless the expected type is a SAM type. By-name parameters are scala.Function0
.
1 Like
You can prevent sbt from adding the scala library as a dependency by setting autoScalaLibrary := false
(cf. sbt reference). Unfortunately the reference also reads:
In order to compile Scala sources, the Scala library needs to be on the classpath
There is a discussion about making a “scala kernel” independent of the standard library, but it’s not a priority (for now).
1 Like
If I do that, then I no longer seem to access Char
, or Int
and I get messages like the following.
/Volumes/Dev/Programming/Scala3/cql-play/java/src/main/java/com/conexus/rdf/NTripleNodeParser.scala:56:21: not found: type Char
[error] private def hex(c: Char) = digit( c ) || (‘A’ <= c && c <= ‘F’) || ( ‘a’ <= c && c <= ‘f’)
Yeah you need to use fully qualified names like scala.Char
or import those explicitly at the beginning.
The idea of using that or -Yno-imports
is that you have to be explicit about everything you use and that way you do not end up using something from Scala by mistake.
1 Like
Yes, if I add that flag then I can no longer compile.
Urgh. That looks like one of those things where one needs to be an sbt guru to work out how to get the compiler to work again. Is there a trick to get the code to compile? Do they mean that one has to add the classpath on running sbt?
Mhh, and there I was thinking it would be simple.
I would actually not mind if there were a very small jar dependency for things like Char, Int, and a few of the most useful constructs.
If this is possible in dotty, I can also switch to that.
Getting there. I added
import scala.{Char,Int,Array,Boolean}
and it compiles.
But now running the code in jjs
(don’t ask me why I have to do that! Sigh!), I get
jjs> var slo = Java.type('com.conexus.rdf.NTripleNodeParser')
jjs> slo.parseLiteral('"Hello World"@en')
Exception in thread "main" java.lang.NoClassDefFoundError: scala/runtime/BoxesRunTime
at com.conexus.rdf.NodeParser.parseLiteral(NTripleNodeParser.scala:142)
at com.conexus.rdf.NodeParser.parseLit(NTripleNodeParser.scala:128)
Right. I’m afraid that avoiding calls to scala.runtime
will be very hard, maybe even impossible. For starters you will have to avoid all automatic boxing. So e.g. using List[Integer]
instead of List[Int]
and explicitly creating the Integer
objects where necessary (i.e. new Integer(42)
).
Some array related stuff will probably also call into scala.runtime
.
2 Likes
I think I got it in the end. It seems to work without exceptions when I run the resulting jar purely on the jvm. It’s just not easy to tell if I have dependencies on scala-lib or not.
Here is the code for those interested.
import java.io.{Reader, StringReader}
import java.text.ParseException
import java.util
import java.lang.StringBuilder
import java.net.URI
import java.util.Objects
import java.lang.String
import scala.{Any, Array, Boolean, Char, Int, Unit}
import com.conexus.rdf.NTripleNodeParser.Lang
/**
* parse NTriple Nodes with no reliance on Scala libraries
*/
object NTripleNodeParser {
type Lang = String
def parseLiteral(nodeStr: String): Literal =
NodeParser(nodeStr.trim()).parseLit()
def main(args: Array[String]): Unit = {
System.out.println(parseLiteral(args(0)))
}
}
sealed trait Literal {
def literal: String
}
class TypedLiteral(val lit: String, val tpe: URI) extends Literal {
override def literal: String = lit
override def hashCode(): Int = Objects.hash(lit,tpe)
override def equals(obj: Any): Boolean =
obj match {
case tl: TypedLiteral => tl.lit == this.lit && tl.tpe.equals(this.tpe)
case _ => false
}
// this can be improved by a case on each of the types
override def toString: String = {
import TypedLiteral._
if (tpe == xsdString) '"' + literal + '"'
else if (tpe == xsdInt) literal
else '"' + literal + '"' + "^^<" + tpe.toString + ">"
}
}
object TypedLiteral {
def xsd(tp: String) = new URI("http://www.w3.org/2001/XMLSchema#"+tp)
lazy val xsdString = xsd("string")
lazy val xsdInt = xsd("int")
def apply(lit: String) = new TypedLiteral(lit,xsdString)
def apply(lit: String, tpe: URI) =
if (tpe == xsdString) new TypedLiteral(lit,xsdString)
else new TypedLiteral(lit,tpe)
}
class LangLiteral(val lit: String, val lang: Lang) extends Literal {
override def literal: String = lit
override def equals(obj: Any): Boolean =
obj match {
case tl: LangLiteral => tl.lit == this.lit && tl.lang.equals(this.lang)
case _ => false
}
override def hashCode(): Int = Objects.hash(lit.hashCode(),lang.hashCode())
override def toString: String = '"'+lit+'"'+ '@'+lang
}
object LangLiteral{
def apply(lit: String, lang: Lang ) = new LangLiteral(lit, lang)
}
object NodeParser {
def apply(node: String): NodeParser = new NodeParser(new StringReader(node))
private def digit(c: Char): Boolean = '0' <= c && c <= '9'
private def whitespace(c: Char): Boolean = c == ' ' || c == '\t'
private def alpha(c: Char): Boolean = ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
private def hex(c: Char): Boolean = digit(c) || ('A' <= c && c <= 'F') || ( 'a' <= c && c <= 'f')
private def alphaNum(c: Char): Boolean = alpha(c) || digit(c)
private def pn_chars_base(c: Char): Boolean = alpha(c) ||
('\u00C0' <= c && c <= '\u00D6') || ('\u00D8' <= c && c <='\u00F6') || ('\u00F8' <= c && c <= '\u02FF') ||
('\u0370' <= c && c <= '\u037D') || ('\u037F' <= c && c <= '\u1FFF') || ('\u200C' <= c && c <= '\u200D') ||
('\u2070' <= c && c <= '\u218F') || ('\u2C00' <= c && c <= '\u2FEF') || ('\u3001' <= c && c <= '\uD7FF') ||
('\uF900' <= c && c <= '\uFDCF') || ('\uFDF0' <= c && c <= '\uFFFD') || ('\u1000' <= c && c <= '\uEFFF')
private def pn_chars_ranges(c: Char) = digit(c) || ('\u3000' <= c && c <= '\u036F') || ('\u203F' <= c && c <= '\u2040')
private def not_IRI_char_range(c: Char) = ('\u0000' <= c && c <= '\u0020')
def IRI_char(ci: Int) = {
val c = ci.toChar
"""<>"{}|^`\""".indexOf(c) == -1 && !not_IRI_char_range(c)
}
def pn_chars(ci: Int) = {
val c = ci.toChar
c == '-' || c == '\u00B7' || pn_chars_base(c) || pn_chars_ranges(c)
}
def pn_chars_dot(ci: Int) = {
val c = ci.toChar
c == '.' || pn_chars(c)
}
def pn_chars_u(ci: Int) = {
val c = ci.toChar
c == '_' || c == ':' || pn_chars_base(c)
}
def blank_node_label_first_char(ci: Int): Boolean = {
val c = ci.toChar
digit(c) || pn_chars_u(c)
}
def whitespace(ci: Int) = {
val c = ci.toChar
c == ' ' || c == '\t'
}
def whitespaceEOL(ci: Int) = {
val c = ci.toChar
c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
}
class NodeParser(private val rd: Reader) {
import NodeParser._
def PE(msg: String) = new ParseException(msg,0)
def PE(c: Int, msg: Lang) = new ParseException(msg + ": '" + c.toChar +"'", 0)
protected
val rewind: util.Queue[Int] = new java.util.LinkedList[Int]()
protected
def read(): Int = {
if (rewind.isEmpty) rd.read()
else rewind.remove()
}
protected
def newBuilder = new java.lang.StringBuilder()
protected
def appendChar(c: Int,buf: java.lang.StringBuilder) = buf.append(c.toChar)
def parseLit(): Literal = {
read() match {
case '"' => parseLiteral()
//todo add uris and other starting chars
case _ => throw PE("literal does not start with a quote")
}
}
// we enter this function after having consumed the first quotation character
protected
def parseLiteral(): Literal = {
val lexicalForm = parsePlainLiteral()
read() match {
case -1 => TypedLiteral(lexicalForm) //node matches can end early
case '^' => TypedLiteral(lexicalForm, parseDataType())
case '@' => LangLiteral(lexicalForm, parseLang())
case x => {
rewind.add(x) // this character can be used for later parsing
TypedLiteral(lexicalForm)
}
}
}
protected final
def parsePlainLiteral(litBuf: java.lang.StringBuilder = newBuilder): String =
read() match {
case -1 => throw PE("end of string Literal before end of quotation")
case '"' => litBuf.toString() //closing quote
case '\\' => parsePlainLiteral(appendChar(parseQuotedChar(), litBuf))
case illegal if ( illegal == 0x22 || illegal == 0x5c
|| illegal == 0xA || illegal == 0xD) => {
throw PE(illegal, "illegal character in literal")
}
case c => {
parsePlainLiteral(appendChar(c, litBuf))
}
}
protected final
def parseDataType(): java.net.URI = {
read() match {
case '^' => {
val c = read()
if ( c == '<')
parseIRI()
else throw PE(c, "data type literal must be followed by ^^<$uri>. ")
}
case -1 => throw PE("unexpected end of stream while waiting for dataType for URI")
case c => throw PE(c, "expected ^^ followed by URI, found ^ ")
}
}
private def parseLang(): Lang = {
val buf = newBuilder
def lang(): Lang = {
read() match {
case -1 => buf.toString
case '-' => { appendChar('-',buf); subsequentParts() }
case c if alpha(c.toChar) => { appendChar(c,buf); lang()}
case other => { rewind.add(other); buf.toString() }
}
}
def subsequentParts(): String = {
read() match {
case -1 => buf.toString
case '-' => { appendChar('-',buf); subsequentParts() }
case c if alphaNum(c.toChar) => { appendChar(c,buf); subsequentParts() }
case other => { rewind.add(other); buf.toString() }
}
}
lang()
}
protected
def parseQuotedChar(): Char =
read() match {
case 't' => '\t'
case 'b' => '\b'
case 'n' => '\n'
case 'r' => '\r'
case 'f' => '\f'
case '"' => '"'
case '\'' => '\''
case '\\' => '\\'
case 'u' => parseShortHex()
case 'U' => parseLongHex()
case other => throw PE(other, "Illegal quoted char")
}
private def parseShortHex(): Char = hexVal(readN(4).toCharArray)
private def parseLongHex(): Char = hexVal(readN(8).toCharArray)
private def parseIRIQuotedChar(): Char =
read() match {
case 'u' => parseShortHex()
case 'U' => parseLongHex()
case other => throw PE(other, "Illegal character after escape '\\' char")
}
/**
* The initial '<' has already been read
*/
private final
def parseIRI(iribuf: java.lang.StringBuilder = newBuilder): URI = {
read() match {
case -1 => throw PE("unexpected end of stream reading URI starting with '" + iribuf.toString() + "'")
case '>' => new URI(iribuf.toString())
case '\\' => parseIRI(appendChar(parseIRIQuotedChar(),iribuf))
case c if IRI_char(c) => parseIRI(appendChar(c,iribuf))
case err => throw PE(err,"illegal character "+
"in IRI starting with >" + iribuf.toString() + "< ")
}
}
protected
def hexVal(chars: Array[Char]): Char = {
var position: Int = chars.length
var result: Int = 0
while (position > 0) {
val importance = chars.length - position
position = position-1
result = result | (Character.digit(chars(position), 16) << 4 * importance)
}
result.toChar
}
protected final
def readN(i: Int, buf: java.lang.StringBuilder = newBuilder): String = {
if (i <= 0) buf.toString
else read() match {
case -1 => throw PE("reached end of stream while trying to readN chars")
case c => readN(i - 1, appendChar(c,buf))
}
}
}