Author Archives: Trond

A simple non-static logger for Scala

Overview

Here’s an idea for a logger that doesn’t rely on it being shared statically. Initialization is done using implicits, which results in less typing when using the actual classes that mixin the Loggable trait. Any object created within such classes will also get access to these implicits. This lets the implicit arguments propagate forward to other loggable objects when instanciated inside a Loggable class.

The Loggable trait

The trait defines several logging methods such as info(), warn(), error(), fail(), and debug(), where error() will return false and fail() will result in an AssertionError exception being thrown. Each method specify who triggered the logging method, where in the sourcecode it happend, the message that was sent, and a list of zero or more application-defined tags. The tags can be used for example to do filtering or add extra logging categories in the implementation of the logging methods.

Three members controls the behaviour of the Loggable trait. The getLogCompanion specify the companion object who contains the object’s debug tags, the logger.checker checks that debug logging is enabled using the companion’s debug tags, and the logger.writer outputs the logged messages.

The whole process is bootstrapped by constructing an object that implements the Logger trait and marking it as implicit. This could be done for example in an application object or in the main() method.

import java.io.{PrintWriter,StringWriter}
import java.util.regex.{Pattern,Matcher}
 
trait Loggable[+T <: Loggable[T]] {
  this: T => // type this trait with the subclass that uses it
 
  import Loggable._
 
  // attributes required by this trait
  protected val logger: Logger
 
  // methods required by this trait
  protected def getLogCompanion(): LogCompanion[T]
 
  // predefined methods
  protected final def error(msg: String, tags: String*): Boolean = {
    val trace = (new Exception).getStackTrace()(2)
    val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
    val where = trace.getFileName + ":" + trace.getLineNumber
    logger.writer.error(msg,who,where,tags:_*)
  }
 
  protected final def error(exp: Throwable, tags: String*): Boolean = {
    val trace = (new Exception).getStackTrace()(2)
    val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
    val where = trace.getFileName + ":" + trace.getLineNumber
    logger.writer.error(expToString(exp),who,where,tags:_*)
  }
 
  protected final def fail(msg: String, tags: String*): Nothing = {
    val trace = (new Exception).getStackTrace()(2)
    val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
    val where = trace.getFileName + ":" + trace.getLineNumber
    logger.writer.fail(msg,who,where,tags:_*)
  }
 
  protected final def debug(msg: String, tags: String*) {
    // the tag "all" controls all debug logging
    if (logger.checker("all") &&
        getLogCompanion.DebugTags.exists(tag => logger.checker(tag))) {
      val trace = (new Exception).getStackTrace()(2)
      val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
      val where = trace.getFileName + ":" + trace.getLineNumber
      logger.writer.debug(msg,who,where,tags:_*)
    }
  }
 
  protected final def info(msg: String, tags: String*) {
    val trace = (new Exception).getStackTrace()(2)
    val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
    val where = trace.getFileName + ":" + trace.getLineNumber
    logger.writer.info(msg,who,where,tags:_*)
  }
 
  protected final def warn(msg: String, tags: String*) {
    val trace = (new Exception).getStackTrace()(2)
    val who   = toDot(trace.getClassName) + "." + toDot(trace.getMethodName)
    val where = trace.getFileName + ":" + trace.getLineNumber
    logger.writer.warn(msg,who,where,tags:_*)
  }
}
 
object Loggable {
 
  // traits required by Loggable
  trait LogCompanion[+T <: Loggable[_]] {
    val DebugTags: Set[String]
  }
 
  trait Logger {
    val checker: LogChecker
    val writer: LogWriter
  }
 
  trait LogChecker {
    def update(tag: String, isEnabled: Boolean): Unit
    def apply(tag: String): Boolean
  }
 
  trait LogWriter {
      def debug(msg: String, who: String, where: String,tags: String*): Unit
      def error(msg: String, who: String, where: String, tags: String*): Boolean
      def fail(msg: String, who: String, where: String, tags: String*): Nothing
      def info(msg: String, who: String, where: String, tags: String*): Unit
      def warn(msg: String, who: String, where: String, tags: String*): Unit
  }
 
  // utility function
  private val SlashPattern = Pattern.compile("\\\\")
 
  private def toDot(str: String): String = {
    val matcher = SlashPattern.matcher(str)
    matcher.replaceAll(".")
  }
 
  private def expToString(exp: Throwable): String = {
    val writer = new StringWriter
    exp.printStackTrace(new PrintWriter(writer))
    writer.toString
  }
}

An example of how the trait could be used

import Loggable._
 
// common trait for tunes that Jukebox can use
trait Playable {
  this: Loggable[_] =>
 
  def play(): Unit = info("Started playing " + this)
  def stop(): Unit = info("Stopped playing " + this)
}
 
// the secondary implicit list of arguments are resolved at compile-time by
// the compiler, which will replace these arguments with any available implicitly
// defined objects that satisfy the type. Multiple objects are resolved by using
// the most specific one. There are also rules for how the compiler does search.
class Jukebox()(implicit protected val logger: Logger)
  extends Loggable[Jukebox] {
 
  import scala.collection.immutable.Queue
 
  protected var tunes = Queue[Playable]()
 
  debug("Created " + this)
 
  override def toString(): String = "(Jukebox tunes: " + tunes.mkString("[",",","]") + ")"
 
  protected def getLogCompanion(): LogCompanion[Jukebox] = Jukebox
 
  def queue(tune: Playable) {
    tunes = tunes.enqueue(tune)
    debug("Queued " + tune + " on " + this)
  }
 
  def kick(): Unit = fail("Crashed " + this)
 
  def play() {
    if (tunes.isEmpty)
      return warn("No more tunes to play on " + this)
 
    val (tune,rest) = tunes.dequeue
    tunes = rest
    tune.play()
    tune.stop()
  }
}
 
object Jukebox extends LogCompanion[Jukebox] {
  val DebugTags = Set("jukebox")
}
 
class Song(val name: String)(implicit protected val logger: Logger)
  extends Playable with Loggable[Song] {
 
  debug("Created " + this)
  override def toString(): String = "(Song name: \"" + name + "\")"
  protected def getLogCompanion(): LogCompanion[Song] = Song
}
 
object Song extends LogCompanion[Song] {
  val DebugTags = Set("song")
}
 
// override the logCompanion of Song
class Jingle(name0: String)(implicit logger: Logger)
  extends Song(name0) {
  debug("Created " + this)
  override def toString(): String = "(Jingle name: \"" + name + "\")"
  override protected def getLogCompanion(): LogCompanion[Song] = Jingle
}
 
object Jingle extends LogCompanion[Jingle] {
  val DebugTags = Set("jingle") ++ Song.DebugTags
}

Running the example code with some objects

// instantiating the logging  checker and writer
implicit val logger = new Logger {
  var enabledTags = Set("all","song","jukebox","jingle")
  var disabledTags = Set()
 
  val checker = new LogChecker {
    def update(tag: String, isEnabled: Boolean) {
      enabledTags = if (isEnabled) enabledTags + tag else enabledTags - tag
    }
    def apply(tag: String): Boolean = enabledTags contains tag
  }
 
  val writer = new LogWriter {
    def debug(msg: String, who: String, where: String, tags: String*) {
      println("\n--debug " + who + " " + where + "\n" + msg)
    }
    def error(msg: String, who: String, where: String, tags: String*): Boolean = {
      println("--error " + who + " " + where + "\n" + msg)
      false
    }
    def fail(msg: String, who: String, where: String, tags: String*): Nothing = {
      println("\n--fail " + who + " " + where + "\n" + msg)
      throw new java.lang.AssertionError(
        "msg: " + msg + " who: " + who + " where: " + where
      )
    }
    def info(msg: String, who: String, where: String, tags: String*) {
      println("\n--info " + who + " " + where + "\n" + msg)
    }
    def warn(msg: String, who: String, where: String, tags: String*) {
      println("\n--warn " + who + " " + where + "\n" + msg)
    }
  }
}
 
// creating some test objects
val jukebox = new Jukebox
jukebox.queue(new Song("that ol' tune"))
jukebox.queue(new Song("dum di dum"))
jukebox.queue(new Jingle("snap! snap!"))
jukebox.play()
jukebox.play()
jukebox.play()
jukebox.play()
jukebox.kick()

The resulting output in the console

--debug Main$$anon$4$Jukebox.<init> Debug.scala:123
Created (Jukebox tunes: [])
 
--debug Main$$anon$4$Song.<init> Debug.scala:153
Created (Song name: "that ol' tune")
 
--debug Main$$anon$4$Jukebox.queue Debug.scala:129
Queued (Song name: "that ol' tune") on (Jukebox tunes: [(Song name: "that ol' tune")])
 
--debug Main$$anon$4$Song.<init> Debug.scala:153
Created (Song name: "dum di dum")
 
--debug Main$$anon$4$Jukebox.queue Debug.scala:129
Queued (Song name: "dum di dum") on (Jukebox tunes: [(Song name: "that ol' tune"),(Song name: "dum di dum")])
 
--debug Main$$anon$4$Song.<init> Debug.scala:153
Created (Jingle name: "snap! snap!")
 
--debug Main$$anon$4$Jingle.<init> Debug.scala:166
Created (Jingle name: "snap! snap!")
 
--debug Main$$anon$4$Jukebox.queue Debug.scala:129
Queued (Jingle name: "snap! snap!") on (Jukebox tunes: [(Song name: "that ol' tune"),(Song name: "dum di dum"),(Jingle name: "snap! snap!")])
 
--info Main$$anon$4$Playable$class.play Debug.scala:107
Started playing (Song name: "that ol' tune")
 
--info Main$$anon$4$Playable$class.stop Debug.scala:108
Stopped playing (Song name: "that ol' tune")
 
--info Main$$anon$4$Playable$class.play Debug.scala:107
Started playing (Song name: "dum di dum")
 
--info Main$$anon$4$Playable$class.stop Debug.scala:108
Stopped playing (Song name: "dum di dum")
 
--info Main$$anon$4$Playable$class.play Debug.scala:107
Started playing (Jingle name: "snap! snap!")
 
--info Main$$anon$4$Playable$class.stop Debug.scala:108
Stopped playing (Jingle name: "snap! snap!")
 
--warn Main$$anon$4$Jukebox.play Debug.scala:136
No more tunes to play on (Jukebox tunes: [])
 
--fail Main$$anon$4$Jukebox.kick Debug.scala:133
Crashed (Jukebox tunes: [])
java.lang.AssertionError: msg: Crashed (Jukebox tunes: []) who: Main$$anon$4$Jukebox.kick where: Debug.scala:133
        at Main$$anon$4$$anon$1$$anon$3.fail(Debug.scala:197)
        at Main$$anon$4$Loggable$class.fail(Debug.scala:33)
        at Main$$anon$4$Jukebox.fail(Debug.scala:116)
        at Main$$anon$4$Jukebox.kick(Debug.scala:133)
        at Main$$anon$4.<init>(Debug.scala:219)
        at Main$.main(Debug.scala:1)
        at Main.main(Debug.scala)
        ...

Some notes on the logger solution

I didn’t find any good solution on handling inherited DebugTags in the LogCompanion objects. These must instead be copied manually in the code of the companion object.

I considered using call-by-name on the debug argument but it wasn’t worth it. Per info from scala mailing list, it would result in an anonymous inner class and object instantiation at each place a debug call would be done, which clearly defeats the purpose of using call-by-name to only calculate the message when needed.

References

Preparing pdf-documents for the ebook reader

Briss

Getting the best resolution for pdf-documents on the an ebook reader like kindle isn’t always easy, but there’s a great program available for “trimming” your pdfs.

With the Java-based open-source software Briss, you can remove unnecessary margins by cropping the even and odd pages of a pdf-document. The end-result is a pdf-document that better utilize the screenspace of the kindle, and hence bigger fonts to read. Results may vary though as it seems the kindle software does a little bit of optimization itself.

Note: There’s no direct installer or execution but you can create a shortcut yourself for running the program using ‘java -jar briss-x.y.z.jar’, where x.y.z refer to your downloaded version. It’s also assumed that Java Runtime Environment (JRE) is installed on your system.

PDF Split and Merge

Jumping around pdf-documents on the Kindle can be a bit cumbersome. With PDF-SaM you can split up large documents into chapters etc. for quicker browsing. This tool is also based on Java and thus requires the Java JRE.

Links

Setting up an Ant build script for your Scala project

Introduction

This is a small writeup which gives one way of setting up a build system for your Scala project. Here Apache Ant is used for managing this process, where the whole configuration is contained within a single build.xml file placed in the project’s root directory. Execution is done from command line or within your IDE environment.

Prerequisites

Install and configure Scala:

  • Define and set the SCALA_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include SCALA_HOME\bin directory

Install and configure Ant:

  • Define and set the ANT_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include ANT_HOME\bin directory
  • Optional: If you encounter memory problems, define and set ANT_OPTS=-Xmx500M with JVM memory configuration in the OS environment

Install and configure Java:

  • Define and set the JAVA_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include JAVA_HOME\bin directory

Find a suitable file structure for the project:

project-name/
  build/
  lib/
  src/
  test/
  build.xml

The Ant build script

Order of Ant targets:

  • build <- package <- doc <- compile <- init
  • clean <- init

Configure project-name/build.xml:

<?xml version="1.0" encoding="UTF-8"?>
 
<project name="ProjectName" default="build" basedir=".">
  <description>Project Build Script</description>
 
  <!-- targets -->
  <target name="build" depends="package" description="Build whole project"/>
 
  <target name="clean" depends="init" description="Remove previous build files">
    <delete dir="${build.dir}" includeemptydirs="true" quiet="true"/>
  </target>
 
  <target name="init">
    <property environment="env"/>
 
    <!-- check for required tools -->
    <fail message="Missing SCALA_HOME variable in OS environment">
      <condition><isset property="${env.SCALA_HOME}"/></condition>
    </fail>
    <fail message="Missing JAVA_HOME variable in OS environment">
      <condition><isset property="${env.JAVA_HOME}"/></condition>
    </fail>
 
    <!-- variables for paths and files -->
    <property name="src.dir" location="${basedir}/src"/>
    <property name="lib.dir" location="${basedir}/lib"/>
    <property name="build.dir" location="${basedir}/build"/>
    <property name="build-classes.dir" location="${build.dir}/classes"/>
    <property name="build-lib.dir" location="${build.dir}/lib"/>
    <property name="build-doc.dir" location="${build.dir}/doc"/>
    <property name="java.dir" location="${env.JAVA_HOME}"/>
    <property name="scala.dir" location="${env.SCALA_HOME}"/>
    <property name="scala-library.jar" location="${scala.dir}/lib/scala-library.jar"/>
    <property name="scala-reflect.jar" location="${scala.dir}/lib/scala-reflect.jar"/>
    <property name="scala-compiler.jar" location="${scala.dir}/lib/scala-compiler.jar"/>
 
    <path id="project.classpath">
      <pathelement location="${scala-library.jar}"/>
      <pathelement location="${build-classes.dir}"/> <!-- used during recompilation -->
    </path>
 
    <path id="scala.classpath">
      <pathelement location="${scala-compiler.jar}"/>
      <pathelement location="${scala-reflect.jar}"/>
      <pathelement location="${scala-library.jar}"/>
    </path>	
 
    <!-- load scala's ant tasks -->
    <taskdef resource="scala/tools/ant/antlib.xml" classpathref="scala.classpath"/>
 
    <!-- print where this project will get scala and java from -->
    <echo message="Init project"/>
    <echo message=" with scala.dir = ${scala.dir}"/>
    <echo message=" with java.dir = ${java.dir}"/>
 
    <!-- check if any files has been modified since last build -->
    <uptodate property="build.uptodate" targetfile="${build.dir}/build.done">
      <srcfiles dir= "${src.dir}" includes="**"/>
      <srcfiles dir= "${lib.dir}" includes="**"/>
    </uptodate>
  </target>
 
  <target name="compile" depends="init" unless="build.uptodate">
    <mkdir dir="${build-classes.dir}"/>
    <scalac
      destdir="${build-classes.dir}"
      classpathref="project.classpath">
      <include name="**/*.scala"/>
      <src><pathelement location="${src.dir}"/></src>
    </scalac>
  </target>
 
  <target name="doc" depends="compile" unless="build.uptodate">
    <mkdir dir="${build-doc.dir}"/>
    <scaladoc
      srcdir="${src.dir}"
      destdir="${build-doc.dir}"
      doctitle="Project API documentation"
      classpathref="project.classpath">
      <include name="**/*.scala"/>
    </scaladoc>
  </target>
 
  <target name="package" depends="doc" unless="build.uptodate">
    <mkdir dir="${build-lib.dir}"/>
    <jar destfile="${build-lib.dir}/project.jar">
      <fileset dir="${build-classes.dir}"/>
    </jar>
    <jar destfile="${build-lib.dir}/project-src.jar">
      <fileset dir="${src.dir}" includes="**/*.scala"/>
    </jar>
    <jar destfile="${build-lib.dir}/project-doc.jar">
      <fileset dir="${build-doc.dir}"/>
    </jar>
    <touch file="${build.dir}/build.done"/> <!-- mark build as up-to-date -->
  </target>
</project>

References

@tuple – experimenting with lisp-like syntax as XML replacement

Motivation

The following post describes an experimental format which has a lisp-like syntax, with hints of JSON, for storing tree-structured data as text. The main motivation was a need to have a less verbose alternative to XML, but still be readable and editable, support pattern matching and transformation, include common binary datatypes, and enable structural type checking.

The current specification might contain errors and is not yet completed. Rule-based transformations through pattern matching is still in early stage.

Current specification

(@text
  (#
 
  Quick overview
    @tuple is an experimental typed data format using s-expression-like syntax
    for storing tree-structured data. In addition to handling text there is also
    support for common binary datatypes. The data format also allows transformations
    with rules that pattern match on the tree structure.
 
    A tuple is given by (<type> <exp1> <exp2> ... <expn> <type>) or its shorthand
    form (<type> <exp1> <exp2> ... <expn>). The sequence of expressions allowed
    is dependent on the type.
 
    A typed tuple may be a @int, @float, @bool, @bytes, @rule, @text, @note or
    @tuple. A named or untyped tuple is implicitly typed as @tuple. A @note
    is used for documentation or ignoring embedded tuples, and may be
    discarded during parsing.
 
    Binary operators are defined for
      :  key-value pair in @tuple or @note
      == require rule in @rule
      -> replace rule in @rule
      => merge rule in @rule
 
    A @tuple may contain key-value pairs, @note, @rule, untyped and typed @text, and
    untyped and typed @tuple.
 
    The value in a key-value pair may contain untyped or typed @text, @int, @float,
    @bool, @bytes, @tuple. It may also contain a pattern matching variable.
 
    A document must start with a named @tuple or typed tuple, but cannot start with
    a @note.
 
  Syntax
    ()    encloses a named, unnamed or typed tuple
 
    (#    begins a text block (nested text blocks are allowed but
          unbalanced text blocks must use double-quotation instead)
    #)    ends a text block
    ""    encloses a text-string of escaped characters
    @text gives one or more text-blocks and text-strings
 
    :  a pair where left side is key and right side the value
    => rule that matches the left side and then merges it with the right side
    -> rule that matches the left side and then replace it with the right side
    == rule that matches left side and then require that the right side is present
    '' encloses a pattern matching variable that specify type or named reference
 
    @tuple  gives a sequence of typed or untyped @tuple, @text, @rule, or @note
    ()      gives a sequence of typed or untyped @tuple, @text, @rule, or @note
    (Name)  gives a sequence of typed or untyped @tuple, @text, @rule, or @note
    @note   gives a sequence of typed or untyped @tuple, @text, @rule, or @note
    @rule   gives a sequence of rules
    @bytes  gives a sequence of base64-encoded text containing unsigned bytes
    @int    gives a sequence of signed integers in :32 bit or :64 bit
    @float  gives a sequence of floating point numbers in :32 bit or :64 bit
    @value  gives a sequence of unbounded numeric values
    @bool   gives a sequence of true and false values
 
  Pattern matching
    * Application of pattern matching is handled in application code
    * Distinction between shallow and deep pattern matching
      (@note "a @rule:shallow pattern matches on child nodes of @tuple"
        (@rule:shallow (value: '@int') => (Data))
      )
      (@note "a @rule:deep pattern matches recursively on child nodes of @tuple and pairs"
        (@rule:deep (value: '@int') => (Data))
      )
      (@note "a @rule is implicitly typed as @rule:shallow"
        (@rule (value: '@int') => (Data))
      )
    * Pattern matching variables can be used to
      (@note "assign variable to the value of a pair"
        (Data value: 'pi')
      )
      (@note "replace a variable with an expression"
        (@rule:deep 'pi' -> (@float 3.14))
      )
      (@note "require the structure of a tuple"
        (@rule (Data) == (size: '@int'))
      )
 
  Examples
    (@note "named tuples"
       (Document title: "just another format" Document)
       (Dir name: "root" files: (
           (File name: "readme" data: (@bytes (#MTIz#)))
         )
       )
    )
    (@note "unnamed tuples"
       (title: "just another format")
       (name: "root" files: (
           (name: "readme" data: (@bytes (#MTIz#)))
         )
       )
    )
    (@note "typed tuples in key-value pairs"
       (Cell name: "a" value: (@int:32 42))
       (Screen fullscreen: (@bool false))
    )
    (@note "untyped tuples in key-value pairs"
       (Cell name: "b" value: 42)
       (Screen fullscreen: false)
    )
 
  Parsing syntax
    start            ::= tuple-named | tuple-typed | rule-typed | primitives-typed
 
    tuple            ::= tuple-named | tuple-untyped | tuple-typed
    tuple-typed      ::= typed<tuple-type,tuple-seq>
    tuple-untyped    ::= unnamed<tuple-seq>
    tuple-type       ::= '@tuple'
    tuple-named      ::= named<name,tuple-seq>
    tuple-seq        ::= {rule | note | text | pair | tuple, pad}
 
    unnamed<body>    ::= '(' body ')'
    named<tag,body>  ::= '(' tag (pad body)? (pad tag)? ')'
    typed<tag,body>  ::= '(' tag (pad body)? (pad tag)? ')'
    name             ::= [a-zA-Z][-A-Za-z0-9]*
    pair             ::= name ':' pad pair-value
    pair-value       ::= values | variable | tuple
    values           ::= values-untyped | values-typed
    values-untyped   ::= value-untyped | bool-untyped | text-untyped
    values-typed     ::= bytes-typed | float-typed | int-typed
                       | bool-typed | text-typed | tuple
 
    rule             ::= rule-typed
    rule-typed       ::= typed<rule-type,rule-seq>
    rule-type        ::= '@rule' (':shallow' | ':deep')?
    rule-seq         ::= {rule-merge | rule-replace | rule-require | note, pad}
    rule-merge       ::= tuple pad '=>' pad tuple
    rule-replace     ::= (tuple pad '->' pad tuple)
                       | variable pad '->' pad (tuple | primitives)
    rule-require     ::= tuple pad '==' pad tuple
 
    variable         ::= ''' variable-type '''
    variable-type    ::= name | int-type | float-type | bool-type | bytes-type 
                       | text-type | tuple-type | note-type
 
    note             ::= note-typed
    note-typed       ::= typed<note-type,tuple-seq>
    note-type        ::= '@note'
 
    bytes            ::= bytes-typed
    bytes-typed      ::= typed<bytes-type,bytes-untyped>
    bytes-untyped    ::= '(#' `all validated base64 text with whitespace trimmed` '#)'
    bytes-type       ::= '@bytes'
 
    value-untyped    ::= int-untyped | float-untyped
 
    int              ::= int-untyped | int-typed
    int-typed        ::= typed<int-type,int-seq>
    int-untyped      ::= `characters giving an integer of any size`
    int-type         ::= '@int' (':32' | ':64')?
    int-seq          ::= {int-untyped, pad}
 
    float            ::= float-untyped | float-typed
    float-typed      ::= typed<float-type,float-seq>
    float-untyped    ::= `characters giving a floating point of any size`
    float-type       ::= '@float' (':32' | ':64')?
    float-seq        ::= {float-untyped, pad}
 
    bool             ::= bool-untyped | bool-typed
    bool-typed       ::= typed<bool-type,bool-seq>
    bool-untyped     ::= 'true' | 'false'
    bool-type        ::= '@bool'
    bool-seq         ::= {bool-untyped, pad}
 
    text             ::= text-untyped | text-typed
    text-typed       ::= typed<text-type,text-seq>
    text-untyped     ::= text-string | text-block
    text-type        ::= '@text'
    text-seq         ::= {text-string | text-block, pad}
    text-string      ::= '"' `all validated text until unescaped double-quote` '"'
    text-block       ::= '(#' `all validated text until text-block is balanced` '#)'
 
    pad              ::= [/s]+
 
  How to handle text
    * Text-strings are escaped by replacing '\' with '\\' then '"' with '\"', and
      unescaped by replacing '\"' with '"' then '\\' with '\'.
    * Text-blocks are not escaped, but verified that they are balanced. If not they
      become text-strings. A text-block is balanced if all embedded '(#' is matched
      by a corresponding '#)'. A text-block cannot end with the '(' character.
 
  Examples of invalid expressions
    (@note "duplicate keys"
      (Data name: "A" name: "B")
    )
    (@note "named tuple not enclosed with parenthesis"
      Data
    )
    (@note "type not enclosed with parenthesis"
      @tuple
    )
    (@note "variable does not contain a valid type"
      value: '@byte'
    )
 
  Examples of valid expressions
    (@note "untyped @tuple"
      (label: "Kyrre")
    )
    (@note "untyped @text"
      "Some characters!"
    )
    (@note "boolean value is implicit typed as @bool"
      value: true
    )
    (@note "numeric value implicit typed as @value"
      width: 1024
    )
    (@note "integer value implicit typed as @int:64"
      value: (@int 4)
    )
    (@note "float value implicit typed as @float:64"
      value: (@float 3.14)
    )
  #)
@text)

An example

Data with redundant information stored in rules:

(@tuple
 
  (@note "Rules for naming all unnamed tuples and discarding notes and rules")
  (@rule:deep
    (name: '@text' type: '@text') => (Node)
    (from: '@tuple' to: '@tuple') => (Link)
    (inputs: (
        (@rule (name: '@text' type: '@text') => (Socket))
      )
    ) => ()
    (outputs: (
        (@rule (name: '@text' type: '@text') => (Socket))
      )
    ) => ()
    '@note' -> ()
    '@rule' -> ()
  )
 
  (@note "Rules for replacing variables with data")
  (@rule:deep
    'binary-data' -> (@bytes (#QUI9PQ==#))
    'instructions' -> "some data in text format"
  )
 
  (@note "The data to be transformed")
  (name: "Group1" type: "Group"
    nodes: (
      (name: "Source" type: "Value" value: 'binary-data'
        inputs: ((name: "In" type: "bytedata"))
        outputs: ((name: "Out" type: "bytedata"))
      )
      (name: "Transform" type: "Process" data: 'instructions'
        inputs: ((name: "In" type: "bytedata"))
        outputs: ((name: "Out" type: "bytedata"))
      )
      (name: "Target" type: "Value" value: ""
        inputs: ((name: "In" type: "bytedata"))
        outputs: ((name: "Out" type: "bytedata"))
      )
    )
    links: (
      (from: (node: "Source" socket: "Out") to: (node: "Transform" socket: "In"))
      (from: (node: "Transform" socket: "Out") to: (node: "Target" socket: "In"))
    )
  )
 
@tuple)

The resulting data after the rules of the tuple has been applied to itself:

(@tuple
  (Node name: "Group1" type: "Group"
    nodes: (
      (Node name: "Source" type: "Value" value: (@bytes (#QUI9PQ==#))
        inputs: ((Socket name: "In" type: "bytedata"))
        outputs: ((Socket name: "Out" type: "bytedata"))
      )
      (Node name: "Transform" type: "Process" data: "some data in text format"
        inputs: ((Socket name: "In" type: "bytedata"))
        outputs: ((Socket name: "Out" type: "bytedata"))
      )
      (Node name: "Target" type: "Value" value: "output"
        inputs: ((Socket name: "In" type: "bytedata"))
        outputs: ((Socket name: "Out" type: "bytedata"))
      )
    )
 
    links: (
      (Link from: (node: "Source" socket: "Out")
        to: (node: "Transform" socket: "In")
      )
      (Link from: (node: "Transform" socket: "Out")
        to: (node: "Target" socket: "In")
      )
    )
  )
@tuple)

Implementation

A basic implementation in Scala is available at bitbucket.org/trondolsen/tuples and also an api documentation.

Ideas for extensions

  • Chaining together separate files
  • Nesting separate files with pattern matching variables – like referencing large bytedata and instantiating tuples
  • Use separate files for validation, update and typing of data
  • Pattern matching bits, will maybe complicate the format too much

Trait-like CSS on HTML

Defining some CSS types

One way of structuring the CSS is to define some types over the CSS classes. What these types might be may be is open, but one instance might be Behaviour, Layout and Roles. The types are not used explicitly in the CSS, but are there to help organize and abstract the CSS classes.

On these types the following CSS classes can be defined:

  • Roles – sidebar, menu, toolbar, group, item, title, input, post, search, button
  • Behaviour – inactive, hidden, selectable, grabable, highlighted
  • Layout – flow{L,R}, break{,L,R}, padded{,L,R,T,B,V,H}, spaced{,L,R,T,B,V,H}

    where {} mean “choose from” and letters are abbreviations for Left, Right, Top, Bottom, Vertically, Horizontally

For Roles it is important to add classes that can be combined or nested. For instance, toolbar and menu might both contain item and group. Furthermore, a sidebar might contain a toolbar or menu. Experimenting and identifying such classes as Roles (or any types) can help simplify or find reoccuring structures in the CSS.

Tagging the HTML elements with CSS classes

Depending on the design of the HTML page, HTML elements may be tagged as you see fit with CSS classes. The variations of Roles is a result of how the HTML elements are nested. These elements can further be tagged with Behaviour and Layout classes.

Example:

<div class="menu">
  <div class="title">title</div>
  <div class="spacedH flowL selectable item">item 1</div>
  <div class="spacedH flowL selectable item">item 2</div>
  <div class="spacedH flowL selectable item">item 3</div>
<div>

Setting the appearance of composed CSS classes

The visual appearance of the HTML elements is specified by a set of selectors, each having a pattern to match and the corresponing CSS attributes to set.

Guidelines for writing the selectors:

  • More specific compositions of CSS classes override less specific
  • Selector A B specifies node B when nested inside A
  • Selector AB specifies node with A and B
  • Selector A>B specifies node B when child of A
  • Selector A+B specifies node B if after A
  • Selector A B, C+D specifies two selectors using the same attributes

Example:

.break { clear: both; }
.breakL { clear: left; }
.breakR { clear: right; }
 
.spaced { margin: 4px; }
.spacedL { margin-left: 4px; }
.spacedV { margin-top: 4px; margin-bottom: 4px; }
.spacedH { margin-left: 4px; margin-right: 4px; }
 
.menu {
  background-color: #404040;
}
 
.menu .title {
  color: #a0a0a0;
  font-weight: bold;
}
 
.menu .item {
  color: #a0a0a0;
}
 
/* Here .item.selectable is node with both */
.menu .item.selectable#hover {
  color: #f0f0f0;
}

References

Reminders on Scala syntax

Reminders

The companion object (both for own code and Scala’s API)

// The class definition with primary constructor
class Node protected (val a: String, private var b: String) {
  // imports definitions in companion object
  import Node._ 
 
  // attributes and primary constructor code
  var busy = false
 
  // methods
  override def toString() = "A"
}
 
// Companion object comes after the class/trait definition
object Node {
  // apply and unapply methods for object pattern matching (like case classes)
  // and object construction
  apply(arg: String): A = new A("a", arg)
 
  // static methods and variables
}
 
val node = Node("test","me")

Setter and getter functions are defined implicitly for val, var and referenced arguments in primary constructor. To set these manually

class A {
  private[this] var v: Int = 0 // avoids implicit setters and getters
  def value(): Int = v
  def value_=(t: Int): Unit = { v = t } // setters returns Unit
}

Adding indexing operators

// Here T give the type parameter for the class
class A[T] (size: Int) {
  private val array = new Array[T](size)
  def apply(index: Int): T = { /** get index value */ }
  def update(index: Int, value: T): Unit = { /** set index value*/ }
}
 
val a = new A[Int](100)
a(0) = 1
val n = a(0)

Binding a variable during pattern matching

str match {
  case bound @ "A match!" => println(bound)
  case _ => // no match
}

Defining an extractor for pattern matching (example lacks validation)

object Email {
  def apply(user: String, domain: String): String =
    user + "@" + domain
 
  def unapply(str: String): Option[(String,String)] = {
    str.split("@").toList match {
      case user :: domain :: Nil => Some(user,domain)
      case _ => None
    }
  }
}
 
"me@host.com" match {
  case Email(name,host) => println("Username is " + name)
  case _ => println("Invalid email address")
}

Imports can be grouped

import scala.collection.mutable.{Map,Stack,Queue}

Types can be aliased

type QMap = Map[Int,Queue[Int]]

Upper A <: T (A is a subtype of T) and lower A >: T (A is a supertype of T) bounded type parameters

class List[+A](elem: A, next: Option[List[A]]) {
  def prepend[B >: A](newElem: B): List[B] = new List(newElem, Some(this))
}
// scala> val l1 = new List(1.0, None)
// l1: List[Double] = List@1f49969
// scala> val l2 = new List(10,Some(l1))
// l2: List[AnyVal] = List@19bf996
 
def sort[T <: Ordered[T]](list: List[T]) { /** ... */ }

Writing () is optional with no arguments, and . is optional for methods with one argument

val b = new B
List(1,2,3) map (_ * 2)

The return keyword is optional (and curly braces on a single expression too)

def test() = "test"

The return type Unit is implicit when functions are declared without =

def printBig(str: String): Unit = {
  println(str.toUpperCase)
  () /** explicitly end the expression with the unit value */
}
def printBig(str: String) { println(str.toUpperCase) } /** same as previous */

References

Simple serialization with XML strings in Javascript

Introduction

Included in this post are some quick and simple functions for doing XML serialization in Javascript through strings. Reserved XML characters < > & ' " are escaped from input and if any invalid input are found an exception will be thrown.

List of XML utility functions:

  • toXmlHeader() – Creates the XML header.
  • toXmlElem(name: string, attributes: object) – Creates and closes an XML element.
  • toXmlElemOpen(name: string, attributes: object) – Creates and opens an XML element.
  • toXmlElemClose(name: string) – Closes an XML element.
  • toXmlText(text: string) – Creates XML text.

A serialization example

var xml = toXmlHeader();
xml += toXmlElemOpen("Library"); // No attributes given.
 
xml += toXmlElemOpen("Authors", {country: "Norway"});
xml += toXmlElem("Author", {name:"Petter Dass"});
xml += toXmlElemClose("Authors");
 
xml += toXmlElemOpen("Books"); // No attributes given.
xml += toXmlElem("Book", {title:"book1", author:"author1"});
xml += toXmlElemOpen("Book", {title:"book2", author:"author1"});
xml += toXmlText("Ach, So?");
xml += toXmlElemClose("Book");
xml += toXmlElem("Book", {title:"book3", author:"author2"});
xml += toXmlElem("Book"); // No attributes given.
xml += toXmlElemClose("Books");
 
xml += toXmlElemClose("Library");
alert(xml); // the complete xml is now contained inside the string.
<?xml version="1.0" encoding="UTF-8"?>
<Library>
<Authors country="Norway">
<Author name="Petter Dass"/></Authors>
<Books>
<Book title="book1" author="author1"/>
<Book title="book2" author="author1">Ach, So?</Book>
<Book title="book3" author="author2"/>
<Book/></Books></Library>

Sourcecode for the XML utility functions

function toXmlValid(str) {
  if (str === undefined || str === null || str.match === undefined || str.match(/<|>|&|'|"/) !== null) {
    throw("invalid string given");
  }
 
  return str;
}
 
function escapeXmlText(str) {
  if (str === undefined || str === null || str.replace === undefined) {
    throw("invalid string given");
  }
 
  // The order of replace is important because the & character is used in escaping.
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;");
}
 
function unescapeXmlText(str) {
  if (str === undefined || str === null || str.replace === undefined) {
    throw("invalid string given");
  }
 
  // The order of replace is important because the & character is used in unescaping.
  return str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&apos;/g, "'").replace(/&quot;/g, "\"").replace(/&amp;/g, "&");
}
 
function toXmlHeader() {
  return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
}
 
function toXmlAttr(name, value) {
  return " " + toXmlValid(name) + "=\"" + escapeXmlText(value) + "\"";
}
 
function toXmlElem(tagName, attributes) {
  var str = "\n<" + toXmlValid(tagName);
 
  if (attributes !== undefined) {	
    for (var key in attributes) {
      if (attributes.hasOwnProperty(key) === true) {
        str += toXmlAttr(key, attributes[key]);
      }
    }
  }
 
  return str + "/>";
}
 
function toXmlElemOpen(tagName, attributes) {
  var str = "\n<" + toXmlValid(tagName);
 
  if (attributes !== undefined) {
    for (var key in attributes) {
      if (attributes.hasOwnProperty(key) === true) {
        str += toXmlAttr(key, attributes[key]);
      }
    }
  }
 
  return str + ">";
}
 
function toXmlElemClose(tagName) {
  return "</" + toXmlValid(tagName) + ">";
}
 
function toXmlText(text) {
  return escapeXmlText(text);
}

References

Comparison against null and undefined in Javascript

Values in Javascript can come from

  • Object – object reference
  • String – immutable, 16-bit USC-2
  • Boolean – true, false
  • Number – only 64-bit floating point, IEEE 754 (double)
  • null – empty object reference
  • undefined – unassigned variables and function arguments, missing object properties

Boolean evaluation of values using == and != operators

Values that are interpreted as false:

  • false
  • null
  • undefined
  • "" – the empty string
  • 0 – the number 0
  • NaN – not a number, result of undefined math operation

All other values and objects are interpreted as true.

Working with comparison operators

The == and != operators will do implicit type conversion of it’s arguments to match their type.┬áSince the null and undefined values are both equal with these operators, errors can occur when a missing object property is used in a null-comparison.

By using the === and !== operators, no implicit type conversion will be done. The values null and undefined are not equal using these operators. These operators can be used to check against missing properties and unassigned arguments among other.

Notice: Be careful if updating your old javascript code with === and !== for null-comparison tests. It might be that an undefined-comparison should have been used instead (or both). Failure to recognise these cases can introduce hard-to-catch errors.

References

Deep Pocket Layout – tree-based UI layout

Overall idea

The Deep Pocket Layout is a specialized tree data-structure for use in non-overlapping layout of widgets. This data-structure can be used for partitioning screen-space, like the quadtree or kd-tree data-structures, and uses the naming conventions of border layout. In usage, it functions as a tree of either vertically or horizontally linked partitions. Much like a combined border/horizontal/vertical layout.

This data-structure will be explained by example as a container widget, which is part of an inheritance hierarchy of widgets.

Introduction by example

The figure above shows the named “pockets” of the data structure. The outmost pockets are used for linking the Pocket Layout Widget (PW) together with other PWs. The center pocket can contain a PW (thus forming a tree) or a different kind of Widget (leaf node). The center pocket specifies the screen-space, while the border pockets acts as internal links to other PWs. When the left or right pockets are linked, they become part of a horizontal layout (top and bottom pockets must always be empty). Conversely, when the top or bottom pockets are linked, they become part of a vertical layout (left and right pockets must always be empty).

The first example below shows how a widget (of any kind) can be inserted by using the center pocket of the PW.

PWs can be arranged into either vertical and horizontal layouts. The figure below shows how a horizontal layout is started. Since P1 it the root of the tree, a new PW P3 is created, replacing the center pocket content of P1. Then P2 is linked with P3, starting a horizontal layout.

The figure below shows how a vertical layout is started. This example is identical to the previous one, but starting a vertical layout instead. (The root constraint for P1 still applies.)

The final example shows how to split a PW in a horizontal layout into a vertical. Here P3 is already part of a horizontal layout. A new PW is created and replaced with P3s center pocket. The vertical layout is then started by linking P6 with P7.

The final deep pocket layout of this example is visualized in the following figure.

Summary

Some features:

  • Enables combination of tree-based vertical/horizontal layouts.
  • Allows the user to insert/remove any PWs in the hierarchy from any depth, thus controlling the complete layout with drag-and-drop.
  • User control over screen space that is delegated within the tree hierarchy.
  • PW linked in either a horizontal or vertical layout can adjust neighboring PWs or delegate request to parent PW.

Implementation extensions:

  • The example used “share equally”, but custom ratios of the linked PWs can be handled by the parent PW.
  • Overlay of drag-and-drop targets when dragging is in progress.

Code documentation for Javascript and Actionscript 3

Prerequisites for Actionscript documentation

Prerequisites for Javascript documentation

Actionscript 3 Code Documentation

Example documentation:

/**
* class-description
*/
class-declaration
/**
* method-description
*
* @see method-name
* @param name description
* @return description
*/
method-declaration.
/** attribute-description */
attribute-declaration

Extracting the documentation from the sourcecode:

asdoc.exe -source-path /source/directory -doc-sources /source/directory -output /output/directory -window-title "Project Title"

Javascript Code Documentation

Example documentation:

/**
 * class-description
 *
 * @author author
 * @extends class-name
 * @see class-name
 * @version version
 * @constructor
 */
class-constructor-declaration.
/**
 * method-description
 *
 * @see class-name#method-name
 * @param {data-type} name description
 * @return description
 * @type data-type-of-return
 */
method-declaration.

Extracting the documentation from the sourcecode:

perl jsdoc.pl /source/directory --directory /output/directory --project-name "Project Title"

References for this post