Feeds:
Posts
Comments

Lego Robot

I participated in a Lego Robot Competition recently, and our team of 5 persons won the first prize. Our robot successfully accomplished its mission: picking up a treasure, a Lego block in red color, and putting it in the dropping area inside a maze, exiting the maze, and hunting for the next treasure.

Components of the robot

robot001

Figure 1. The Lego Robot

The robot consists of a Samsung Galaxy S4 Android phone, a Rockwell MicroLogix 830 controller, 3 Lego motors, a Xiaomi power bank, and several infra-red distance sensors. The phone connects to the MicroLogix controller via an USB OTG cable, an USB to serial converter, and a serial cable. The communication protocol between the phone and the controller is MODBUS over serial line.

The Android app is the brain of the robot. It captures pictures periodically to detect treasures and the 3 mazes (in blue, green and purple color respectively) and reads the values of the sensors to determine what to do next. It sends commands to the controller to make a move, a turn, pick up or put down a treasure. The controller is not entirely passive: it can stop the robot when it detects that it is too close to a wall.

The PLC (Programmable Logical Controller) logic running in the controller uses PID to control the motors to make accurate moving or turning.

The Android app

The Android app is built on top of 2 open source libraries: OpenCV (http://opencv.org/) and the jamod Modbus library
(http://jamod.sourceforge.net/). The OpenCV library is used to capture pictures and detect objects from the pictures; while the jamod library is used to send commands and read sensor values to and from the MicroLogix controller. (I’ve made some modification to the jamod library to make it working with Android serial driver.)

At the heart of the Android app is the Robot class. A Robot object has a RobotCommander for sending commands to the MicroLogix controller, a RobotSensors object for reading sensor values, several ObjectDetectors for detecting treasures, mazes, outer walls, and the dropping area.

robot002

Figure 2. The class diagram of the Robot class.

State machines

The behavior of the Robot is controlled by the state machines. The top level state machine has 5 states: Initial, Initialized, Running, Paused, and Stopped. After the Robot is initialized it can be put into the Running state. When in the Running state, it can be Stopped, it can also be Paused and then resumed to the Running state.

robot003

Figure 3. The top level state machine

The Running state itself is a state machine that has 9 sub-states, as shown in Figure 4: FindingTreasure, ApproachingTreasure, PickingTreasure, FindingMaze, ApproachingMaze, FindingMazeEntrance, FindingDropArea, DroppingTreasure, and ExitingMaze.

The Robot first searches for a treasure. Once a treasure is found, it moves towards the treasure, and picks it up. It them searches for a maze, approaches it, and finds the entrance. Once it is in a maze, it looks for the drop area, drops the treasure, exits the maze, and starts over again.

robot004

Figure 4. The sub-state machine of the Running state

Each sub-state corresponds to a state machine, which we call a Sequence. Figure 5 shows the state diagram of the Finding Treasure state.

robot005

Figure 5. Finding Treasure sequence

The implementation of state machines

Two interfaces, IStateMachine and IState, as shown in Figure 6, are defined for implementing the state machines. IState represents a state. Upon entering or exiting a state, the enter() or exit() method is called respectively. The move() method will be called one or more times when the state is active to allow the Robot to make a move. IStateMachine represents a state machine. It can change the current active state and have the current state to make a move.

robot006

Figure 6. IStateMachine and IState

Figure 7 shows all classes that implement the IStateMachine interface. Class RobotStateMachine implements the top-level state machine as shown in Figure 3 and class RobotRunningStateMachine implements the sub-state machine as shown in Figure 4. Class RobotRunningSequence is the base class of sequences. Each sub-class of RobotRunningSequence corresponds to a state of the sub-state machine.

robot007

Figure 7. State machines

When the Robot object is in the Running state, the continueMission() method is called periodically, which calls move() on the RobotStateMachine object, which calls move() on the current state, Running, which calls move() on the RobutRunningStateMachine object, which calls move() on the current sequence. If the m_nextState member variable is not null, it calls changeState(), which calls exit() on the current state, makes m_nextState the current state, and calls enter() on it. It finally calls move() on the current state, which would most likely calls sendCommand on the Robot object to send a command to the controller.

robot008

Figure 8. State machines sequence diagram

The states of a state machine are implemented using a Java enum, with each enum field representing a state. The following code shows how the top-level states are defined.

public enum RobotState implements IState {
    Initial {
        @Override
        public void enter(IStateMachine machine) {
        }
        @Override
        public void move(IStateMachine machine) {
            machine.robot().initialize();
            machine.changeState(Initialized);
        }
        @Override
        public void exit(IStateMachine machine) {
        }
     },
     Initialized {
        @Override
        public void enter(IStateMachine machine) {
            machine.robot().ensureArmAtHome();
        }
        @Override
        public void move(IStateMachine machine) {
            // wait
        }
        @Override
        public void exit(IStateMachine machine) {
        }
 
    },
    Running {
        @Override
        public void enter(IStateMachine machine) {
            m_runningMachine.afterResume();
            machine.robot().log("Enter Running state", 2);
        }
        @Override
        public void move(IStateMachine machine) {
            m_runningMachine.move();
        }
        @Override
        public void exit(IStateMachine machine) {
            // do nothing
        }
 
    }, 
    Paused {
        @Override
        public void enter(IStateMachine machine) {
            m_runningMachine.beforePause();
        }
        @Override
        public void move(IStateMachine machine) {
            machine.waitForResume();
        }
        @Override
        public void exit(IStateMachine machine) {
        }
 
    },
    Stopped {
        @Override
        public void enter(IStateMachine machine) {
            machine.robot().sendCommand(RobotAction.Stop);
        }
        @Override
        public void move(IStateMachine machine) {
            machine.robot().sleep(200);
        }
        @Override
        public void exit(IStateMachine machine) {
        }
    }
}

ThreadingEvery enum field is in fact a class. This makes the code more concise by not having to explicitly define a Java class for each state.

In an Android app, if the UI thread is blocked for more than 5 seconds the app will be forced to be stopped. To avoid running into this scenario, the entire state machine is running on a worker thread. When a Robot object is created, a work thread is created with a Runnable object whose run() method periodically calls the continueMission() method, which calls the move() method on the RobotStateMachine object.

In the TestSequenceActivity, a RobotTask class, inheriting from AsyncTask, is defined to test individual sequences using a worker thread.

In order to avoid the changeState() and move() methods of RobotStateMachine to be called by the UI thread, the methods throw an exception if the calling thread is the main thread.

public void move() {
    if (Looper.myLooper() == Looper.getMainLooper()) {
    throw new RuntimeException("move() cannot be called by the main thread");
}
…
}

Debugging

When the Android app is running, we cannot use Eclipse to debug the app as the USB port is used to connect to the controller. We chose to use log to help us understand what is going on. We create some Android Activities specially for debugging, e.g. an Activity for testing Modbus communication, an Activity for testing sending commands to the controller, and an Activity for testing individual sequences. The log of the debugging Activities provides much detailed information, while the log of the run mode Activity, RobotActivity, provides only important information.

An interface named IRobotLogger is defined for the Activities to implement to display logging information. When the log() method is called, if the logging level is equal to or higher than the lowest  logging level it is interested in, it appends the string passed in to the TextView.

Since the state machines are all running on a worker thread, the log() method cannot directly call the append() method on the TextView, instead it should send a request to the UI thread to do so by calling the runOnUiThread() on the Activity.

 

public void log(final String msg) {
    TestSequenceActivity.this.runOnUiThread(new Runnable() {
        public void run() {
            TextView logDisplay = (TextView) findViewById(R.id.text_log);
            logDisplay.append(msg + "\n");
        }
    });
}

Points of Interest

Adopting the State design pattern makes our code much easier to understand and debug. Even though only myself was working on the Java code debugging, the well understood behavior of each state machine plus the detailed log allow other team member to help me understand what was going on, and thus greatly shorten our debugging time.

When we designed the classes of the model, we had unit-testability in mind, although we hadn’t written much unit tests, as we were implementing the App in a rush. We defined some interfaces, such as IRobotCommander, IRobotSensors, to make them mockable.

Debuggability is the key for us to develop the app in such a short time with relatively good quality. The activities developed specially for debugging help us troubleshoot the issues we encountered quickly.

Using the Modbus protocol for the communication between the phone and the controller removes a lot of burden in both the Android app and the PLC code compare with using serial port communication directly.

If I had a chance to develop a similar app, I would consider developing a DSL (Domain Specific Language) for defining the sequences: defining how the states should be changed depending on the sensor values and the object detection result. This would allow non-Java developers to define and debug the sequences. I would also enhance the object detection by utilizing more OpenCV functionality.

 

 

Simple Made Easy

We often use ‘simple’ and ‘easy’ interchangeably. This presentation given by Rich Hickey makes it clear about the difference between the two words. Simple is objective. Easy is relative. Some thing may be very easy to someone, but very hard to others. When we say something is easy, it might be because we are familiar with it. It doesn’t necessarily means it is simple.

http://www.infoq.com/presentations/Simple-Made-Easy

 

 

BarModel Script

I’ve written an article introducing the syntax, the semantic model and the graphical model of the BarModel script I’ve designed for “drawing” bar models by writing scripts.

https://wuxuesong.files.wordpress.com/2015/04/barmodel-script.pdf

 

 

成語縱橫網絡版

我剛剛發布了成語縱橫的網絡版,可切換繁體簡體:

http://chengyuzongheng.azurewebsites.net.

歡迎使用,並提寶貴意見。

謝謝

New books

Received my books ordered from Amazon:

Implementing domain-driven design, by Vaughn Vernon,
ng-book, by Ari Lerner

It took my quite some time to install the latest PhoneGap (2.9.1 as for now) for Android on Windows 8. I’ve found this Blog particularly useful.

If your target Android version is 4.4.2 (API 19), make sure you have installed “Android SDK Build-tools 19.1″. Otherwise you may get an error something like

The SDK Build Tools revision (19.0.3) is too low for project 'HellowPhoneGap'.
Minimum required is 19.1.0

 

I’ve written a few compilers, the most visible one being the PowerBuilder .NET compiler that can compile a PowerBuilder application into either a .NET Windows Forms application or an ASP.NET application. We used ANTLR 2 as the parser generator, which works great for us except for one thing: the context sensitive keywords. PowerScript, the programming language provided by PowerBuilder, allows some keywords, e.g. the keyword “update” of embedded SQL, to be used as identifiers outside of embedded SQL statements. We had to introduce a member variable in the lexer class to specify the current context that parser is in in order for so the lexer to do the right thing in different contexts.

Later I discovered Irony from irony.codeplex.com, that allows you to write grammar in C# code directly, which eliminates the step to generate a lexer and a parser classes from a grammar specification. Irony also has a Grammar Explorer with which you can debug and test your grammar. However, since the tool is still in beta stage, I haven’t invested a lot of time in it.

ANTLR and Irony may look very different, they have at least one thing in common: they both have two distinct phases: scanning and parsing, which means that, by default, the scanner doesn’t know what context the parser is in, thus making it harder to have the lexer to different things in different contexts.

I kept searching for other solutions until I found FParsec, a parser combinator library for F#, a functional programming language from Microsoft. A parser takes a string as the input and generates some form of output, e.g. an AST. By definition, a parser is a function. And therefore it is very natural to write parsers using functional programming languages. FParsec provides some basic parsers, which can be combined to parse higher language constructs of a particular language, until the highest level parser is defined to parse the entire compilation unit. There isn’t a distinct between lexing and parsing. You define parsers to parse both terminals and non-terminals. At any point in parsing, you can decide exactly which parser to use to parse the next token, making it very easy to support context sensitive keywords.

The following F# code is what I’ve written to parse BarModel script, whose grammar is appended at the end.
 
Ast.fs defines the AST nodes
module Ast
 
type Position = Above | Below
type Side = Left | Right
type StrokeStyle = Solid | Dashed | Dotted
 
type Operator = Add | Subtract | Ratio
 
type Size =
    | Symbol of string
    | Value of double
 
type ShapeSpecifier =
    | BarAlias of string
    | BarIndex of int
    | BarRange of int * int
    | BoxAlias of string
    | Alias of string
    | EntireBar of int
    | BoxIndex of int * int
    | BoxRange of int * int * int
 
type Property =
    | TitleProperty of string
    | SizeProperty of Size
    | FillProperty of string
    | StrokeProperty of string
    | StrokeStyleProperty of StrokeStyle
    | PositionProperty of Position
 
type Expression =
    | Literal of double
    | Id of string
    | Box of ShapeSpecifier
    | Arithmetic of Expression * Operator * Expression
 
type Statement =
    | AddBar of Property * string option
    | AddBox of Property list * ShapeSpecifier * string option * Property list option
    | AddLabel of Property list * ShapeSpecifier
    | CutInto of ShapeSpecifier * int list
    | CutFrom of ShapeSpecifier * Side * Size
    | UpdateBox of ShapeSpecifier * Property list
    | ShiftBox of ShapeSpecifier * ShapeSpecifier
    | RatioConstraint of ShapeSpecifier list * int list
    | AdditiveConstraint of Expression * Expression
 
 
 
Parser.fs defines the parser
 
module Parser
 
open FParsec
open Ast
 
type Line = Blank | Statement of Statement
 
type BarModelParser () =
 
    let ws : Parser< unit, unit> = skipMany (pchar ‘ ‘ <|> pchar ‘\t’)
 
    let str_ws c = pstring c .>> ws
    let strCI_ws c = pstringCI c .>> ws
 
    // Tokens
    let addToken : Parser< string, unit> = strCI_ws “add”
    let cutToken : Parser< string, unit> = strCI_ws “cut”
    let shiftToken : Parser< string, unit> = strCI_ws “shift”
    let updateToken : Parser< string, unit> = strCI_ws “update”
 
    let barToken : Parser< string, unit> = strCI_ws “bar”
    let boxToken : Parser< string, unit> = strCI_ws “box”
    let labelToken : Parser< string, unit> = strCI_ws “label”
 
    let titleToken : Parser< string, unit> = strCI_ws “title”
    let sizeToken : Parser< string, unit> = strCI_ws “size”
    let fillToken : Parser< string, unit> = strCI_ws “fill”
    let strokeToken : Parser< string, unit> = strCI_ws “stroke”
    let strokeStyleToken : Parser< string, unit> = strCI_ws “stroke-style”
    let positionToken : Parser< string, unit> = strCI_ws “position”
 
    let toToken : Parser< string, unit> = strCI_ws “to”
    let asToken : Parser< string, unit> = strCI_ws “as”
    let byToken : Parser< string, unit> = strCI_ws “by”
    let intoToken : Parser< string, unit> = strCI_ws “into”
    let fromToken : Parser< string, unit> = strCI_ws “from”
    let withToken : Parser< string, unit> = strCI_ws “with”
 
    let leftToken : Parser< string, unit> = strCI_ws “left”
    let rightToken : Parser< string, unit> = strCI_ws “right”
 
    let aboveToken : Parser< string, unit> = strCI_ws “above”
    let belowToken : Parser< string, unit> = strCI_ws “below”
 
    // Identifier
    let identifier =
        let isValidFirstChar c = isLetter c || c = ‘_’
        let isValidChar c = isLetter c || isDigit c || c = ‘_’
        many1Satisfy2L isValidFirstChar isValidChar “identifier”
 
    // Literals
    let floatNumber = pfloat .>> ws
    let intNumber = pint32 .>> ws
 
    let number : Parser< double, unit> = floatNumber <|> (intNumber |>> fun i -> (float i))
 
    let numberString : Parser< string, unit> =
        (floatNumber |>> fun f -> (string f)) <|>
        (intNumber |>> fun i -> (string i))
 
    let doubleQuoteStringLiteral = between (pstring “\””) (pstring “\”” ) (manySatisfy ((<>) ‘\”‘ ))
    let singleQuoteStringLiteral = between (pstring “\'”) (pstring “\'” ) (manySatisfy ((<>) ‘\” ))
   
    let stringLiteral = doubleQuoteStringLiteral <|> singleQuoteStringLiteral
 
    let hexColor = pstring “#” >>. many1Satisfy isHex |>> fun c -> (“#” + c)
    let color = identifier <|> hexColor <|> stringLiteral
 
    let strokeStyle =
        (strCI_ws “solid” >>% Solid) <|>
        (strCI_ws “dotted” >>% Dotted) <|>
        (strCI_ws “dashed” >>% Dashed)
 
    let position = (aboveToken >>% Above) <|> (belowToken >>% Below)
 
    // Properties
    let titleProperty =
        titleToken >>. str_ws “=” >>. (stringLiteral <|> identifier <|> numberString) .>> ws
        |>> fun s -> TitleProperty(s)
 
    let sizeValue =
        (identifier |>> fun i -> Symbol(i)) <|>
        (stringLiteral |>> fun s -> Symbol(s)) <|>
        (number |>> fun n -> Value(n))
 
    let sizeProperty = sizeToken >>. str_ws “=” >>. sizeValue .>> ws |>> fun s -> SizeProperty(s)
 
    let fillProperty = fillToken >>. str_ws “=” >>. color .>> ws |>> fun c -> FillProperty(c)
    let strokeProperty = strokeToken >>. str_ws “=” >>. color .>> ws |>> fun c -> StrokeProperty(c)
 
    let strokeStyleProperty = strokeStyleToken >>. str_ws “=” >>. strokeStyle .>> ws |>> fun s -> StrokeStyleProperty(s)
 
    let boxProperty = sizeProperty <|> fillProperty <|> strokeProperty <|> strokeStyleProperty
    let boxProperties = many boxProperty
    let boxProperties1 = many1 boxProperty
 
    let positionProperty = positionToken >>. str_ws “=” >>. position .>> ws |>> fun p -> PositionProperty(p)
 
    let labelProperties = many1 (titleProperty <|> positionProperty)
 
    // Bar Specifier
    let barAlias = identifier |>> fun i -> BarAlias(i)
 
    let singleBarIndex =
        intNumber |>> fun i -> BarIndex(i)
 
    let rangeBarIndex =
        (pipe3 intNumber toToken intNumber ( fun i _ j -> BarRange(i,j)))
 
    let barIndex =
        barToken >>.
        between (str_ws “[“) (str_ws “]” ) (attempt rangeBarIndex <|> singleBarIndex)
 
    let barSpec = (attempt barIndex <|> barAlias) .>> ws
 
    // Box Specifier
    let entireBar =
        intNumber |>> fun i -> EntireBar(i)
   
    let singleBoxIndex =
        pipe3 intNumber (str_ws “,”) intNumber (fun i _ j -> BoxIndex(i, j))
 
    let rangeBoxIndex = 
        pipe5 intNumber (str_ws “,”) intNumber toToken intNumber (fun i _ j _ k -> BoxRange(i,j,k))
 
    let boxAlias = (identifier .>> ws) |>> fun i -> BoxAlias(i)
 
    let boxIndex =
        boxToken >>.
        between (str_ws “[“) (str_ws “]” )
            (
                attempt rangeBoxIndex <|>
                attempt singleBoxIndex <|>
                entireBar
            )
 
    let boxSpec = (attempt boxIndex <|> boxAlias) .>> ws
 
    let toBar = toToken >>. barSpec
    let toBox = toToken >>. boxSpec
 
    let toBarOrBox =
        toToken >>.
        (
            attempt boxIndex <|>
            attempt barIndex <|>
            boxAlias
        )
 
    let asBarAlias = asToken >>. identifier
    let asBoxAlias = asToken >>. identifier
 
    // Statements
    let addBar = pipe3 barToken titleProperty (opt asBarAlias) ( fun _ title alias -> AddBar(title, alias))
 
    let withLabelKeywords = withToken .>>. labelToken
    let withLabel = withLabelKeywords >>. labelProperties
 
    let addBox = pipe5 boxToken boxProperties toBar (opt asBoxAlias) (opt withLabel) (fun _ props bar alias label -> AddBox(props, bar, alias, label))
 
    let addLabel =
        pipe3 labelToken labelProperties toBarOrBox ( fun _ props shape -> AddLabel(props, shape))
 
    let addStatement =
        addToken >>.
        (
            addBar <|>
            addBox <|>
            addLabel
        )
 
    let ratios = sepBy1 intNumber (str_ws “:”)
    let cutBoxIntoRatios = pipe3 boxSpec intoToken ratios ( fun box _ r -> CutInto(box, r))
 
    let side = (leftToken >>% Left) <|> (rightToken >>% Right)
    let cutBoxFrom = pipe5 boxSpec fromToken side byToken sizeValue ( fun box _ side _ size -> CutFrom(box, side, size))
 
    let cutBox =
        cutToken >>.
        (
            attempt cutBoxIntoRatios  <|>
            cutBoxFrom
        )
 
    let shiftBox = pipe4 shiftToken boxSpec toToken barSpec ( fun _ box _ bar -> ShiftBox(box, bar))
 
    let updateBox = pipe3 updateToken boxSpec boxProperties1 ( fun _ box props -> UpdateBox(box, props))
 
    let primitive =
        (boxSpec |>> fun b -> Box(b)) <|>
        (floatNumber |>> fun f -> Literal(f)) <|>
        (identifier |>> fun id -> Id(id))
 
    let boxes = sepBy1 boxSpec (str_ws “:”)
    let ratioConstraint = pipe3 boxes (str_ws “=”) ratios (fun b _ r -> RatioConstraint(b, r))
 
    let oppa = new OperatorPrecedenceParser< Expression, unit , unit >()
    let arithmetic = oppa.ExpressionParser
    let terma = primitive .>> ws
    do oppa.TermParser <- terma
    do oppa.AddOperator( InfixOperator(“+” , ws, 1, Associativity .Left, fun x y ->Arithmetic(x, Add, y)))
    do oppa.AddOperator( InfixOperator(“-“ , ws, 1, Associativity .Left, fun x y ->Arithmetic(x, Subtract, y)))
 
    let additiveConstraint = pipe3 arithmetic (str_ws “=”) arithmetic (fun left _ right -> AdditiveConstraint(left, right))
 
    let statement =
        addStatement <|>
        cutBox <|>
        shiftBox <|>
        updateBox <|>
        attempt ratioConstraint <|>
        attempt additiveConstraint
 
    let pcomment = pchar ‘#’ >>. skipManySatisfy ( fun c -> c <> ‘\n’) >>. pchar ‘\n’
    let peol = pcomment <|> (pchar ‘\n’)
 
    let pline = spaces >>. statement .>> peol |>> ( fun i -> Statement i)
    let pblank = spaces >>. peol |>> ( fun _ -> Blank)
    let plines = many (pline <|> pblank) .>> eof
 
    member this.parse (program: string) =   
        match run plines program with
        | Success(result, s, p) ->
            let r = result |> List.choose ( function Statement i -> Some i | Blank -> None)
            Success(r, s, p)
        | Failure(errorMsg, e, s) -> Failure(errorMsg, e, s)
 
 
 
 
ParserTester.fs defines the unit tests for the Parser
 
module ParserTester
 
open FParsec
open Ast
open Parser
open FsUnit
open FsUnit.MsTest
open Microsoft.VisualStudio.TestTools.UnitTesting
 
[<TestClass>]
type “Add bar“ () =
   
    [<TestMethod>]
    member x.“Add a bar with a title.“() =
        let parser = new BarModelParser()
        parser.parse “add bar title=apple\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddBar(TitleProperty(title), _)] -> title |> should equal “apple”
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(e, _, _) -> e |> should throw typeof<System. Exception>
   
    [<TestMethod>]
    member x.“Add a bar as alias“() =
        let parser = new BarModelParser()
        parser.parse “add bar title=orange as bar1\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddBar(TitleProperty(title), Some(alias))] ->
                title |> should equal “orange”
                alias |> should equal “bar1″
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(e, _, _) -> e |> should throw typeof<System. Exception>
   
    [<TestMethod>]
    member x.“Add a box“() =
        let parser = new BarModelParser()
        parser.parse “add box to bar[1]\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddBox(props, BarIndex(i), alias, label)] ->
                props |> List.length |> should equal 0
                i |> should equal 1
                alias.IsNone |> should equal true
                label.IsNone |> should equal true
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(e, _, _) -> e |> should throw typeof<System. Exception>
   
    [<TestMethod>]
    member x.“Add a box with size“() =
        let parser = new BarModelParser()
        parser.parse “add box size=100 to bar1\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddBox(props, BarAlias(a), alias, label)] ->
                props |> List.length |> should equal 1
 
                match props.Head with
                | SizeProperty(Value(size)) -> size |> should equal 100.0
                | _ -> “size property” |> should throw typeof<System.Exception >
 
                a |> should equal “bar1″
                alias.IsNone |> should equal true
                label.IsNone |> should equal true
 
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(e, _, _) -> e |> should throw typeof<System. Exception>
   
    [<TestMethod>]
    member x.“Add a box with size fill stroke“() =
        let parser = new BarModelParser()
        parser.parse “add box size = 100 fill= red stroke =#ababab to bar1\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddBox(props, BarAlias(a), alias, label)] ->
                props |> List.length |> should equal 3
 
                match props.Head with
                | SizeProperty(Value(size)) -> size |> should equal 100.0
                | _ -> “size property” |> should throw typeof<System.Exception >
 
                match props.Tail.Head with
                | FillProperty(color) -> color |> should equal “red”
                | _ -> “fill property” |> should throw typeof<System.Exception >
 
                match props.Tail.Tail.Head with
                | StrokeProperty(rgb) -> rgb |> should equal “#ababab”
                | _ -> “stroke property” |> should throw typeof<System.Exception >
 
                a |> should equal “bar1″
                alias.IsNone |> should equal true
                label.IsNone |> should equal true
 
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(e, _, _) -> e |> should throw typeof<System. Exception>
   
    [<TestMethod>]
    member x.“Add label to a box“() =
        let parser = new BarModelParser()
        parser.parse “add label title=\”tree\” position=above to box[2, 1 to 6]\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddLabel(props, box)] ->
                props |> List.length |> should equal 2
 
                match props.Head with
                | TitleProperty(title) -> title |> should equal “tree”
                | _ -> “title” |> should throw typeof<System.Exception >
 
                match props.Tail.Head with
                | PositionProperty(pos) -> pos |> should equal Above
                | _ -> “position” |> should throw typeof<System.Exception >
 
            | _ -> “properties” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Add label to bars“() =
        let parser = new BarModelParser()
        parser.parse “add label title=300 to bar[1 to 6]\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AddLabel(props, bar)] ->
                props |> List.length |> should equal 1
 
                match props.Head with
                | TitleProperty(title) -> title |> should equal “300”
                | _ -> “title” |> should throw typeof<System.Exception >
 
            | _ -> “properties” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Cut box into equal units“() =
        let parser = new BarModelParser()
        parser.parse “cut box[1, 10] into 9 \n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [CutInto(BoxIndex(bar, box), ratios)] ->
                bar |> should equal 1
                box |> should equal 10
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Cut box into ratios“() =
        let parser = new BarModelParser()
        parser.parse “cut box[1, 10] into 2 : 3 : 4 \n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [CutInto(BoxIndex(bar, box), ratios)] ->
                bar |> should equal 1
                box |> should equal 10
 
                List.length ratios |> should equal 3
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Cut box from left“() =
        let parser = new BarModelParser()
        parser.parse “cut box[1, 10] from left by 300\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [CutFrom(BoxIndex(bar, box), side, Value(sz))] ->
                bar |> should equal 1
                box |> should equal 10
                side |> should equal Left
                sz |> should equal 300.0
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Cut box from right“() =
        let parser = new BarModelParser()
        parser.parse “cut box[5] from right by 300\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [CutFrom(EntireBar(bar), side, Value(size))] ->
                bar |> should equal 5
                side |> should equal Right
                size |> should equal 300.0
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Shift box“() =
        let parser = new BarModelParser()
        parser.parse “shift box[1, 1] to bar[4]\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [ShiftBox(BoxIndex(bar, box), BarIndex(bar2))] ->
                bar |> should equal 1
                box |> should equal 1
                bar2 |> should equal 4
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Update box“() =
        let parser = new BarModelParser()
        parser.parse “update box[1, 1] fill=blue\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [UpdateBox(BoxIndex(bar, box), props)] ->
                bar |> should equal 1
                box |> should equal 1
                props.Length |> should equal 1
                match props.Head with
                | FillProperty(c) -> c |> should equal “blue”
                | _ -> “error” |> should throw typeof<System.Exception >
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Additive constraint“() =
        let parser = new BarModelParser()
        parser.parse “box[1, 1]+box[2] – box[3, 2 to 7] = 210\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [AdditiveConstraint(left, right)] ->
                match left with
                | Arithmetic(Arithmetic(Box(BoxIndex(bar1, box1)), op1, Box(EntireBar(bar2))), op2, Box(BoxRange(bar3, box31, box32))) ->
                    bar1 |> should equal 1
                    box1 |> should equal 1
                    op1 |> should equal Add
                    bar2 |> should equal 2
                    bar3 |> should equal 3
                    box31 |> should equal 2
                    box32 |> should equal 7
                | _ -> “error” |> should throw typeof<System.Exception >
 
                match right with
                | Literal(value) -> value |> should equal 210.0
                | _ -> “error” |> should throw typeof<System.Exception >
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
 
    [<TestMethod>]
    member x.“Ratio constraint“() =
        let parser = new BarModelParser()
        parser.parse “box[1, 1]:box[2] : box[3, 2 to 7] = 5 :4: 3\n” |> function
        | Success(s, _, _) ->
            s |> List.length |> should equal 1
            match s with
            | [RatioConstraint(boxes, ratios)] ->
                List.length boxes |> should equal 3
                List.length ratios |> should equal 3
            | _ -> “error” |> should throw typeof<System.Exception >
        | Failure(err, _, _) -> err |> should throw typeof<System. Exception>
The grammar of BarModel script:
addBar := “add” “bar” titleProperty (“as” barAlias)?
addBox := “add” “box” boxProperty* (“as” boxAlias)?
addLabel := “add” “label” labelProperty* to (boxSpec | barSpec)
cutBoxInto := “cut” boxSpec “into” ratios
cutBoxFrom := “cut” boxSpec “from” (“left” | “right”) “by” size
shiftBox := “shift” boxSpec “to” barSpec
updateBox := “update” boxSpec boxProperty*
additiveConstraint := boxSpec (“+”|”-“ boxSpec)+ “=” number
ratioConstraint := boxSpec (“:” boxSpec)+ “=” ratios
ratios := number (“:” number)*
titleProperty := “title” “=” string
boxProperty :=
   (sizeProperty | fillProperty | strokeProperty |strokeStyleProperty)+
sizeProperty := “size” “=” (identifier | number)
fillProperty := “fill” “=” color
strokeProperty := “stroke” “=” color
strokeStyleProperty := “stroke-style” “=” (“solid” | “dashed” | “dotted”)
labelProperty := (titleProperty | positionProperty)+
positionProperty := “position” “=” (“above” | “below”)
barSpec := (“bar” “[“ intNumber (“,” intNumber)? “]”) | barAlias
barSpec :=
    (“box” “[“ intNumber (“,” intNumber (“to” intNumber)? )? ) | boxAlias
Follow

Get every new post delivered to your Inbox.

Join 118 other followers