Code Examples for

Programming in Scala, Fourth 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 the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Main.scala package org.stairwaybook.scells import swing._ object Main extends SimpleSwingApplication { def top = new MainFrame { title = "ScalaSheet" contents = new Spreadsheet(100, 26) } }

    35.2 Disconnecting data entry and display


    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex2/Model.scala package org.stairwaybook.scells class Model(val height: Int, val width: Int) { case class Cell(row: Int, column: Int) val cells = Array.ofDim[Cell](height, width) for (i <- 0 until height; j <- 0 until width) cells(i)(j) = new Cell(i, j) }

    35.3 Formulas


    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/FormulaParsers.scala package org.stairwaybook.scells import scala.util.parsing.combinator._ object FormulaParsers extends RegexParsers {
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def ident: Parser[String] = """[a-zA-Z_]\w*""".r def decimal: Parser[String] = """-?\d+(\.\d*)?""".r
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/FormulaParsers.scala def range: Parser[Range] = cell~":"~cell ^^ { case c1~":"~c2 => Range(c1, c2) }
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def number: Parser[Number] = decimal ^^ (d => Number(d.toDouble))
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def application: Parser[Application] = ident~"("~repsep(expr, ",")~")" ^^ { case f~"("~ps~")" => Application(f, ps) }
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def expr: Parser[Formula] = range | cell | number | application
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def textual: Parser[Textual] = """[^=].*""".r ^^ Textual
    // In file the-scells-spreadsheet/ex5/FormulaParsers.scala def formula: Parser[Formula] = number | textual | "="~>expr
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex3/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 the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Evaluator.scala package org.stairwaybook.scells trait Evaluator { this: Model => ...
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Evaluator.scala type Op = List[Double] => Double val operations = new collection.mutable.HashMap[String, Op]
    // In file the-scells-spreadsheet/ex5/Evaluator.scala private def evalList(e: Formula): List[Double] = e match { case Range(_, _) => references(e) map (_.value) case _ => List(evaluate(e)) }
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Arithmetic.scala package org.stairwaybook.scells trait Arithmetic { this: Evaluator => operations ++= List( "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 => xs.foldLeft(0.0)(_ + _) }, "prod" -> { xs => xs.foldLeft(1.0)(_ * _) } ) }
    { case List(x, y) => x + y }
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Model.scala package org.stairwaybook.scells import swing._ class Model(val height: Int, val width: Int) extends Evaluator with Arithmetic {
    // In file the-scells-spreadsheet/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 the-scells-spreadsheet/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 the-scells-spreadsheet/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 the-scells-spreadsheet/ex5/Model.scala reactions += { case ValueChanged(_) => value = evaluate(formula) } } // end class Cell
    // In file the-scells-spreadsheet/ex5/Model.scala case class ValueChanged(cell: Cell) extends event.Event
    // In file the-scells-spreadsheet/ex5/Model.scala val cells = Array.ofDim[Cell](height, width) for (i <- 0 until height; j <- 0 until width) cells(i)(j) = new Cell(i, j) } // end class Model
    // In file the-scells-spreadsheet/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, Fourth Edition (the "Stairway Book"), please visit:

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

    and:

    http://booksites.artima.com/programming_in_scala_4ed

    Copyright © 2007-2019 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.