Skip to content

Instantly share code, notes, and snippets.

@DanCouper
Last active June 5, 2017 10:41
Show Gist options
  • Save DanCouper/3b8bb16185a5c358b8c3161e88562127 to your computer and use it in GitHub Desktop.
Save DanCouper/3b8bb16185a5c358b8c3161e88562127 to your computer and use it in GitHub Desktop.
Not really functional initial spike for a calculator processor
/**
* 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