Code Examples for

Programming in Scala, Second Edition

Return to chapter index

35 The SCells Spreadsheet

  • 35.1 The visual framework
  • 35.2 Disconnecting data entry and display
  • 35.3 Formulas
  • 35.4 Parsing formulas
  • 35.5 Evaluation
  • 35.6 Operation libraries
  • 35.7 Change propagation
  • 35.8 Conclusion
  • 35.1 The visual framework


    // In file scells/ex1/Spreadsheet.scala package org.stairwaybook.scells import swing._ class Spreadsheet(val height: Int, val width: Int) extends ScrollPane { val table = new Table(height, width) { rowHeight = 25 autoResizeMode = Table.AutoResizeMode.Off showGrid = true gridColor = new java.awt.Color(150, 150, 150) } val rowHeader = new ListView((0 until height) map (_.toString)) { fixedCellWidth = 30 fixedCellHeight = table.rowHeight } viewportView = table rowHeaderView = rowHeader }
    // In file scells/ex5/Main.scala package org.stairwaybook.scells import swing._ object Main extends SimpleGUIApplication { def top = new MainFrame { title = "ScalaSheet" contents = new Spreadsheet(100, 26) } }

    35.2 Disconnecting data entry and display


    // In file scells/ex2/Spreadsheet.scala package org.stairwaybook.scells import swing._ class Spreadsheet(val height: Int, val width: Int) extends ScrollPane { val cellModel = new Model(height, width) import cellModel._ val table = new Table(height, width) { // settings as before... override def rendererComponent(isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component = if (hasFocus) new TextField(userData(row, column)) else new Label(cells(row)(column).toString) { xAlignment = Alignment.Right } def userData(row: Int, column: Int): String = { val v = this(row, column) if (v == null) "" else v.toString } } // rest as before... }
    // In file scells/ex3/Model.scala package org.stairwaybook.scells class Model(val height: Int, val width: Int) { case class Cell(row: Int, column: Int) val cells = new Array[Array[Cell]](height, width) for (i <- 0 until height; j <- 0 until width) cells(i)(j) = new Cell(i, j) }

    35.3 Formulas


    // In file scells/ex5/Formulas.scala package org.stairwaybook.scells trait Formula case class Coord(row: Int, column: Int) extends Formula { override def toString = ('A' + column).toChar.toString + row } case class Range(c1: Coord, c2: Coord) extends Formula { override def toString = c1.toString +":"+ c2.toString } case class Number(value: Double) extends Formula { override def toString = value.toString } case class Textual(value: String) extends Formula { override def toString = value } case class Application(function: String, arguments: List[Formula]) extends Formula { override def toString = function + arguments.mkString("(", ",", ")") } object Empty extends Textual("")

    35.4 Parsing formulas


    // In file scells/ex5/FormulaParsers.scala package org.stairwaybook.scells import scala.util.parsing.combinator._ object FormulaParsers extends RegexParsers {
    // In file scells/ex5/FormulaParsers.scala def ident: Parser[String] = """[a-zA-Z_]\w*""".r def decimal: Parser[String] = """-?\d+(\.\d*)?""".r
    // In file scells/ex5/FormulaParsers.scala def cell: Parser[Coord] = """[A-Za-z]\d+""".r ^^ { s => val column = s.charAt(0).toUpper - 'A' val row = s.substring(1).toInt Coord(row, column) }
    // In file scells/ex5/FormulaParsers.scala def range: Parser[Range] = cell~":"~cell ^^ { case c1~":"~c2 => Range(c1, c2) }
    // In file scells/ex5/FormulaParsers.scala def number: Parser[Number] = decimal ^^ (d => Number(d.toDouble))
    // In file scells/ex5/FormulaParsers.scala def application: Parser[Application] = ident~"("~repsep(expr, ",")~")" ^^ { case f~"("~ps~")" => Application(f, ps) }
    // In file scells/ex5/FormulaParsers.scala def expr: Parser[Formula] = range | cell | number | application
    // In file scells/ex5/FormulaParsers.scala def textual: Parser[Textual] = """[^=].*""".r ^^ Textual
    // In file scells/ex5/FormulaParsers.scala def formula: Parser[Formula] = number | textual | "="~>expr
    // In file scells/ex5/FormulaParsers.scala def parse(input: String): Formula = parseAll(formula, input) match { case Success(e, _) => e case f: NoSuccess => Textual("["+ f.msg +"]") } } //end FormulaParsers
    // In file scells/ex4/Spreadsheet.scala package org.stairwaybook.scells import swing._ import event._ class Spreadsheet(val height: Int, val width: Int) ... { val table = new Table(height, width) { ... reactions += { case TableUpdated(table, rows, column) => for (row <- rows) cells(row)(column).formula = FormulaParsers.parse(userData(row, column)) } } }
    // In file scells/ex3/Model.scala case class Cell(row: Int, column: Int) { var formula: Formula = Empty override def toString = formula.toString }
    TableUpdated(table, rows, column)

    35.5 Evaluation


    // In file scells/ex5/Evaluator.scala package org.stairwaybook.scells trait Evaluator { this: Model => ...
    // In file scells/ex5/Evaluator.scala def evaluate(e: Formula): Double = try { e match { case Coord(row, column) => cells(row)(column).value case Number(v) => v case Textual(_) => 0 case Application(function, arguments) => val argvals = arguments flatMap evalList operations(function)(argvals) } } catch { case ex: Exception => Double.NaN }
    // In file scells/ex5/Evaluator.scala type Op = List[Double] => Double val operations = new collection.mutable.HashMap[String, Op]
    // In file scells/ex5/Evaluator.scala private def evalList(e: Formula): List[Double] = e match { case Range(_, _) => references(e) map (_.value) case _ => List(evaluate(e)) }
    // In file scells/ex5/Evaluator.scala def references(e: Formula): List[Cell] = e match { case Coord(row, column) => List(cells(row)(column)) case Range(Coord(r1, c1), Coord(r2, c2)) => for (row <- (r1 to r2).toList; column <- c1 to c2) yield cells(row)(column) case Application(function, arguments) => arguments flatMap references case _ => List() } } // end Evaluator

    35.6 Operation libraries


    // In file scells/ex5/Arithmetic.scala package org.stairwaybook.scells trait Arithmetic { this: Evaluator => operations += ( "add" -> { case List(x, y) => x + y }, "sub" -> { case List(x, y) => x - y }, "div" -> { case List(x, y) => x / y }, "mul" -> { case List(x, y) => x * y }, "mod" -> { case List(x, y) => x % y }, "sum" -> { xs => (0.0 /: xs)(_ + _) }, "prod" -> { xs => (1.0 /: xs)(_ * _) } ) }
    { case List(x, y) => x + y }
    // In file scells/ex4/Model.scala package org.stairwaybook.scells class Model(val height: Int, val width: Int) extends Evaluator with Arithmetic { case class Cell(row: Int, column: Int) { var formula: Formula = Empty def value = evaluate(formula) override def toString = formula match { case Textual(s) => s case _ => value.toString } } ... // rest as before }

    35.7 Change propagation


    // In file scells/ex5/Model.scala package org.stairwaybook.scells import swing._ class Model(val height: Int, val width: Int) extends Evaluator with Arithmetic {
    // In file scells/ex5/Model.scala case class Cell(row: Int, column: Int) extends Publisher { override def toString = formula match { case Textual(s) => s case _ => value.toString }
    // In file scells/ex5/Model.scala private var v: Double = 0 def value: Double = v def value_=(w: Double) { if (!(v == w || v.isNaN && w.isNaN)) { v = w publish(ValueChanged(this)) } }
    // In file scells/ex5/Model.scala private var f: Formula = Empty def formula: Formula = f def formula_=(f: Formula) { for (c <- references(formula)) deafTo(c) this.f = f for (c <- references(formula)) listenTo(c) value = evaluate(f) }
    // In file scells/ex5/Model.scala reactions += { case ValueChanged(_) => value = evaluate(formula) } } // end class Cell
    // In file scells/ex5/Model.scala case class ValueChanged(cell: Cell) extends event.Event
    // In file scells/ex5/Model.scala val cells = new Array[Array[Cell]](height, width) for (i <- 0 until height; j <- 0 until width) cells(i)(j) = new Cell(i, j) } // end class Model
    // In file scells/ex5/Spreadsheet.scala package org.stairwaybook.scells import swing._, event._ class Spreadsheet(val height: Int, val width: Int) extends ScrollPane { val cellModel = new Model(height, width) import cellModel._ val table = new Table(height, width) { ... // settings as in Listing X override def rendererComponent( isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int) = ... // as in Listing X def userData(row: Int, column: Int): String = ... // as in Listing X reactions += { case TableUpdated(table, rows, column) => for (row <- rows) cells(row)(column).formula = FormulaParsers.parse(userData(row, column)) case ValueChanged(cell) => updateCell(cell.row, cell.column) } for (row <- cells; cell <- row) listenTo(cell) } val rowHeader = new ListView(0 until height) { fixedCellWidth = 30 fixedCellHeight = table.rowHeight } viewportView = table rowHeaderView = rowHeader }

    35.8 Conclusion

    For more information about Programming in Scala, Second Edition (the "Stairway Book"), please visit:

    http://www.artima.com/shop/programming_in_scala_2ed

    and:

    http://booksites.artima.com/programming_in_scala_2ed

    Copyright © 2007-2010 Artima, Inc. All rights reserved.

    Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.