Last active
June 5, 2017 10:41
-
-
Save DanCouper/3b8bb16185a5c358b8c3161e88562127 to your computer and use it in GitHub Desktop.
Not really functional initial spike for a calculator processor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Dunno how to phrase this as I'm at a very early stage of learning OCaml/Reason, | |
* but I'm writing a calculator as first semi-complex toy app, and not sure if what | |
* I'm trying to do is completely wrongheaded (& if I'm making some futile attempt | |
* to replicate Erlang tagged tuple conventions using the OCaml type system). | |
* Calculator takes a sequential stream of inputs, and I need to parse those as | |
* they come in. To facilitate pattern matching, I wanted to set it up so that each | |
* input is a tuple of `type * displayValue`, eg `(Digit One, "1")` or | |
* `(Number Int, "1234")` or `(Op Div, "÷")`, so I can do basically all | |
* processing using the type system, and configure how the display looks by | |
* just mapping over lists of those tuples. I can get this working in a | |
* single file, buuut I want to use that as kind of configuration, so I define a | |
* big set of types and values, then use that thoughout application - ie I pass a | |
* list of these tuples to the input component, and those are passed back to the | |
* processor one by one when a key is pressed, to be matched upon. | |
* | |
* TODO | |
* | |
* - [x] Set up basic structure using types | |
* - [ ] write input parser | |
* - [x] deal with numbers | |
* - [ ] write shunting yard | |
* - [ ] implement functions | |
* - [ ] implement memory | |
* - [ ] implement backspace | |
* - [ ] implement history | |
*/ | |
/** | |
* The base types are all explicitly defined. I _could_ | |
* simply use chars or floats or whatever, but as I wholly | |
* control what should be used in the calculator, it makes | |
* more sense to extend that control to the type design. | |
*/ | |
type calculatorDigit = | |
| Zero | |
| One | |
| Two | |
| Three | |
| Four | |
| Five | |
| Six | |
| Seven | |
| Eight | |
| Nine | |
| DecimalSeperator; | |
let mapDigitToString = | |
fun | |
| Zero => "0" | |
| One => "1" | |
| Two => "2" | |
| Three => "3" | |
| Four => "4" | |
| Five => "5" | |
| Six => "6" | |
| Seven => "7" | |
| Eight => "8" | |
| Nine => "9" | |
| DecimalSeperator => "."; | |
/* Distinction made between decimal/int so that I can | |
* easily ignore decimal seperator input after first, | |
* so I don't end up with like `1.2.3.4.5` */ | |
type calculatorNumber = | |
| Decimal | |
| Integer; | |
type calculatorOperator = | |
| Exp | |
| Mul | |
| Div | |
| Add | |
| Sub; | |
type calculatorOperatorAssoc = | |
| Left | |
| Right; | |
let assoc calculatorOperator => | |
switch calculatorOperator { | |
| Exp => Right | |
| Mul | |
| Div | |
| Add | |
| Sub => Left | |
}; | |
let precedence calculatorOperator => | |
switch calculatorOperator { | |
| Exp => 4 | |
| Mul | |
| Div => 3 | |
| Add | |
| Sub => 2 | |
| _ => (-1) | |
}; | |
type calculatorAction = | |
| Clear | |
| Execute | |
| SwitchSign; | |
type calculatorInput = | |
| Digit calculatorDigit | |
| Number calculatorNumber | |
| Action calculatorAction | |
| Operator calculatorOperator; | |
let mapInputTypeToString inputType => | |
switch inputType { | |
| Operator o => | |
switch o { | |
| Exp => "^" | |
| Mul => "\195\151" | |
| Div => "\195\183" | |
| Add => "+" | |
| Sub => "-" | |
} | |
} | |
}; | |
/** | |
* The tokenisedInput is modelled as a list of tuples | |
* of the form `(Type, stringRepresentation)`. | |
* This means that: | |
* | |
* - on execution, the list can be easily parsed | |
* by the shunting yard | |
* - on display, the strings can simply be joined | |
* to give a visual representation of the | |
* calculation. | |
*/ | |
type tokenisedInputs = list (calculatorInput, string); | |
/** | |
* At some point, the input has to be displayed, | |
* and that display can be a string for simplicity's sake: | |
*/ | |
type display = string; | |
/** | |
* Special case has to be made for digits, as they come in | |
* character-by-character. The Digit type is never used in | |
* the tokenised list: it is always a type `Number Decimal/Integer`, | |
* which is created using the following rule: | |
* | |
* - If the head of the tokenisedInputs is Number: | |
* - If Digit is DecimalSeperator, and Number | |
* is a Decimal, ignore. | |
* - Else if Digit is DecimalSeperator, concat to | |
* Number string and make Number a Decimal | |
* - Else concat the string representation of Digit | |
* to the Number string regardless of Number type. | |
* - If the head of tokenisedInputs is not Number: | |
* - If Digit is DecimalSeperator, create a new | |
* Number of type Decimal and use `"0" ^ DecimalSeperator` | |
* as the representation. | |
* - Else create a new Number of type Integer and | |
* use the string representation of the Digit type. | |
*/ | |
type handleDigitInput = (calculatorDigit, tokenisedInputs) => tokenisedInputs; | |
let handleDigitInput (digit, tokenisedInputs) => | |
switch (digit, tokenisedInputs) { | |
| (DecimalSeperator, [(Number Decimal, _), _]) => tokenisedInputs | |
| (DecimalSeperator, [(Number Integer, numStr), ...rest]) => [ | |
(Number Decimal, numStr ^ mapDigitToString DecimalSeperator), | |
...rest | |
] | |
| (d, [(Number n, numStr), ...rest]) => [(Number n, numStr ^ mapDigitToString d), ...rest] | |
| (DecimalSeperator, tokenisedInputs) => [ | |
(Number Decimal, mapDigitToString Zero ^ mapDigitToString DecimalSeperator), | |
...tokenisedInputs | |
] | |
| (d, tokenisedInputs) => [(Number Integer, mapDigitToString d), ...tokenisedInputs] | |
}; | |
/** | |
* Implementation of the shunting yard algorithm, | |
* used to process the input, taken verbatim ffom Wikipedia: | |
*/ | |
type updateInput = (calculatorInput, tokenisedInputs) => tokenisedInputs; | |
let updateInput (input, tokenisedInputs) => | |
switch (input, tokenisedInputs) { | |
| (Digit d, tokenisedInputs) => handleDigitInput (d, tokenisedInputs) | |
| (Operator o, [(Operator _, _), _]) => tokenisedInputs | |
| (Operator o, tokenisedInputs) => [(Operator o, ""), ...tokenisedInputs] | |
/* | (Action Execute, tokenisedInputs) => processTokenisedInputs tokenisedInputs */ | |
| (Action Clear, _) => [] | |
/* | (Action SwitchSign, [(Number n, numStr), ...rest]) => [(Number n, )] */ | |
}; | |
/** | |
* On an update to the tokenisedInput, | |
* just rebuild the display | |
*/ | |
type updateDisplayFromInput = tokenisedInputs => display; | |
let updateDisplayFromInput tokenisedInputs => { | |
let rec updateLoop inputs display => | |
switch inputs { | |
| [] => display | |
| [(_, str)] => updateLoop [] (str ^ " " ^ display) | |
| [(_, str), ...rest] => updateLoop rest (str ^ " " ^ display) | |
}; | |
updateLoop tokenisedInputs "" | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment