Skip to content

Instantly share code, notes, and snippets.

@eczn
Last active November 18, 2019 08:21
Show Gist options
  • Save eczn/c65955d9ee4e14b1466c098c4122d335 to your computer and use it in GitHub Desktop.
Save eczn/c65955d9ee4e14b1466c098c4122d335 to your computer and use it in GitHub Desktop.
ts / hs 实现后缀计算式
-- by @W-46ec
-- a4.hs
-- Token data type
data Token = Num Double | Op String | Err String
deriving (Eq, Show)
-- Stack operations
-- 'dup' operator
dup :: [Token] -> [Token]
dup [] = [(Err "dup: empty stack")]
dup lst = lst ++ [last lst]
-- 'pop' operator
pop :: [Token] -> [Token]
pop [] = [(Err "pop: empty stack")]
pop (_ : xs) = xs
-- 'clear' operator
clear :: [a] -> [a]
clear lst = []
-- 'swap' operator
swap :: [Token] -> [Token]
swap [] = [(Err "swap: empty stack")]
swap [x] = [(Err "swap: not enough args")]
swap (a : (b : rest)) = (b : a : rest)
-- operators
-- 'inc' operator
inc :: [Token] -> [Token]
inc [] = [(Err "inc: empty stack")]
inc lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (x + 1))]
-- 'dec' operator
dec :: [Token] -> [Token]
dec [] = [(Err "dec: empty stack")]
dec lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (x - 1))]
-- 'sqrt' operator
sqroot :: [Token] -> [Token]
sqroot [] = [(Err "sqrt: empty stack")]
sqroot lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (sqrt x))]
-- 'sin' operator
sine :: [Token] -> [Token]
sine [] = [(Err "sin: empty stack")]
sine lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (sin x))]
-- 'cos' operator
cosine :: [Token] -> [Token]
cosine [] = [(Err "cos: empty stack")]
cosine lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (cos x))]
-- 'inv' operator
inv :: [Token] -> [Token]
inv [] = [(Err "inv: empty stack")]
inv lst = let (Num x) = last lst
in (exceptLast lst 1) ++ [(Num (1 / x))]
-- '+' operator
plus :: [Token] -> [Token]
plus [] = [(Err "+: empty stack")]
plus lst | (length lst) <= 1 = [(Err "+: not enough args")]
plus lst = let (Num y) = last lst
(Num x) = head (tail (reverse lst))
in (exceptLast lst 2) ++ [(Num (x + y))]
-- '-' operator
minus :: [Token] -> [Token]
minus [] = [(Err "-: empty stack")]
minus lst | (length lst) <= 1 = [(Err "-: not enough args")]
minus lst = let (Num y) = last lst
(Num x) = head (tail (reverse lst))
in (exceptLast lst 2) ++ [(Num (x - y))]
-- '*' operator
multiply :: [Token] -> [Token]
multiply [] = [(Err "*: empty stack")]
multiply lst | (length lst) <= 1 = [(Err "*: not enough args")]
multiply lst = let (Num y) = last lst
(Num x) = head (tail (reverse lst))
in (exceptLast lst 2) ++ [(Num (x * y))]
-- '/' operator
divide :: [Token] -> [Token]
divide [] = [(Err "/: empty stack")]
divide lst | (length lst) <= 1 = [(Err "/: not enough args")]
divide lst = let (Num y) = last lst
(Num x) = head (tail (reverse lst))
in (exceptLast lst 2) ++ [(Num (x / y))]
-- '+all' operator
plusAll :: [Token] -> [Token]
plusAll [] = [(Err "+all: empty stack")]
plusAll lst = [(Num (sum (map (\(Num x) -> x) lst)))]
-- '*all' operator
timesAll :: [Token] -> [Token]
timesAll [] = [(Err "*all: empty stack")]
timesAll lst = [(Num (product (map (\(Num x) -> x) lst)))]
-- Convert from strings to functions
interpretOp :: Token -> [Token] -> [Token]
-- Unary operators
interpretOp (Op "inc") = inc
interpretOp (Op "dec") = dec
interpretOp (Op "sqrt") = sqroot
interpretOp (Op "sin") = sine
interpretOp (Op "cos") = cosine
interpretOp (Op "inv") = inv
-- Binary operators
interpretOp (Op "+") = plus
interpretOp (Op "-") = minus
interpretOp (Op "*") = multiply
interpretOp (Op "/") = divide
interpretOp (Op "+all") = plusAll
interpretOp (Op "*all") = timesAll
-- Stack operation
interpretOp (Op "dup") = dup
interpretOp (Op "pop") = pop
interpretOp (Op "clear") = clear
interpretOp (Op "swap") = swap
-- Handling unknown operator
interpretOp (Op optr) = (\x -> [(Err ("Unknown operator: " ++ optr))])
-- Evaluate an expression
evalExpr :: [Token] -> Token -> [Token]
evalExpr lst (Err msg) = [(Err msg)]
evalExpr lst op = interpretOp op lst
-- Helper functions
-- Return true if the given character is digit, otherwise return false
isDigit :: Char -> Bool
isDigit c = foldl (||) False (map (\x -> x == c) ['0'..'9'])
-- Return true if the given string is a number
isNum :: String -> Bool
isNum num = foldl (&&) True (map (\x -> x == '.' || isDigit x) num)
-- Convert a Token to a string
token2str :: Token -> String
token2str (Num x) = show x
token2str (Op s) = s
-- Convert a string to a Token
str2token :: String -> Token
str2token str | isNum str = Num (read str :: Double)
| otherwise = Op str
-- Convert a string to a list of type Token
str2lst :: String -> [Token]
str2lst str = map str2token (words str)
-- Convert a list of type Token to a string
lst2str :: [Token] -> String
lst2str [] = ""
lst2str [(Num x)] = (show x)
lst2str [(Op str)] = str
lst2str [(Err msg)] = msg
lst2str ((Num x) : rest) = (show x) ++ " " ++ (lst2str rest)
lst2str ((Op str) : rest) = str ++ " " ++ (lst2str rest)
lst2str ((Err msg) : rest) = msg ++ " " ++ (lst2str rest)
-- Get the operator closest to the top of the stack
-- Empty list is being returned if no operator found
getFirstOp :: [Token] -> [Token]
getFirstOp [] = [(Err "empty stack")]
getFirstOp ((Op str) : _) = [(Op str)]
getFirstOp (_ : xs) = getFirstOp xs
-- Pop n elements from the stack
-- Do nothing if stack is empty
popn :: [a] -> Int -> [a]
popn lst n | n <= 0 = lst
| (length lst) == 0 = []
| otherwise = popn (tail lst) (n - 1)
-- Get all the numbers on top of the stack
getAllNums :: [Token] -> [Token]
getAllNums ((Num x) : xs) = (Num x) : (getAllNums xs)
getAllNums x = []
-- Return a list except the last n elements
-- Empty list is being returned if n >= the length of list
exceptLast :: [Token] -> Int -> [Token]
exceptLast lst n | (length lst) <= n = []
| otherwise = (head lst) : (exceptLast (tail lst) n)
-- error handler: Handle error (if any), proceed to next step if no error occur
errorHandler :: [Token] -> [Token]
errorHandler ((Err msg) : _) = [(Err msg)] -- An error occured
errorHandler lst = lst -- No error occur, continue
-- evaluate one expression from the stack
evalOne :: [Token] -> [Token]
evalOne lst | ((length lst) > 1) && ((length (getAllNums lst)) == (length lst))
= [(Err ("stack: " ++ (lst2str lst) ++ ": Cannot proceed anymore"))]
evalOne lst = errorHandler (evalExpr nums op) ++ rest
where nums = getAllNums lst
op = head (getFirstOp (popn lst (length nums)))
rest = popn lst ((length nums) + 1)
-- evaluate the whole stack as a list
evalStack :: [Token] -> [Token]
evalStack ((Err msg) : _) = [(Err msg)]
evalStack [(Num x)] = [(Num x)]
evalStack lst = evalStack (evalOne lst)
-- evaluate the whole stack as a string
calcStack :: String -> String
calcStack str = if (length result) == 0
then "empty stack"
else result
where result = lst2str (evalStack (str2lst str))
-- calc
calc :: String -> String
calc str = calcStack str
-- End of a4.hs
// https://www2.cs.sfu.ca/CourseCentral/383/tjd/a4.html
// 原题用 haskell 搞的
// 这里用 ts 玩一下
type Token = string | number;
function parse(str: string): Token[] {
return str.split(' ').filter(e => e).map(e => {
const _ = +e;
return isNaN(_) ? e : _;
});
}
/**
* resolve map types
*/
interface ResolveMap {
[op: string]: {
len: number,
calc: (...args: number[]) => number | number[]
}
}
/**
* resolve map
*/
const resolveMap: ResolveMap = {
'+': {
len: 2,
calc: (a: number, b: number) => a + b
},
'-': {
len: 2,
calc: (a: number, b: number) => a - b
},
'*': {
len: 2,
calc: (a: number, b: number) => a * b
},
'/': {
len: 2,
calc: (a: number, b: number) => a / b
},
'sqrt': {
len: 1,
calc: (a: number) => Math.sqrt(a)
},
'dup': {
len: 1,
calc: (a: number) => [a, a]
},
'+all': {
len: +Infinity,
calc: (...args: number[]) => args.reduce((a, b) => a + b)
},
'*all': {
len: +Infinity,
calc: (...args: number[]) => args.reduce((a, b) => a * b)
},
'clear': {
len: +Infinity,
calc: () => []
},
'pop': {
len: +Infinity,
calc: (...args: number[]) => args.slice(0, -1)
},
'inc': {
len: 1,
calc: (a: number) => a + 1
},
'dec': {
len: 1,
calc: (a: number) => a - 1
},
'sin': {
len: 1,
calc: (a: number) => Math.sin(a)
},
'cos': {
len: 1,
calc: (a: number) => Math.cos(a)
}
}
function calc(chars: Token[], ...args: number[]): number {
// console.log(chars, args);
if (chars.length === 0) {
if (args.length) {
return args[0]
} else {
throw new Error('empty stack');
}
} else {
const first = chars[0];
if (typeof first === 'number') {
return calc(chars.slice(1), ...args, first);
} else {
const op = resolveMap[first];
if (!op) {
throw new Error('Operator Not Found: ' + first);
}
// 倒数 n 个哦
const __ = args.slice(-op.len);
if ((__.length !== op.len) && op.len !== Infinity) {
throw new Error(`${first}: not enough args, received: [ ${__.join(', ')} ]`);
}
const val = op.calc(...__);
const restArgs = args.slice(0, -op.len);
if (typeof val === 'number') {
return calc(chars.slice(1), ...restArgs, val);
} else {
// ... en
return calc(chars.slice(1), ...restArgs, ...val);
}
}
}
}
/**
* 包一层
*/
const $ = (s: string) => {
try {
console.log('> calc "' + s + '"');
console.log(calc(parse(s)));
} catch (e) {
console.log(e.message);
}
console.log('');
}
$('1 2 + 3 *');
$('1 2 3 * +');
$('2 sqrt 3 sqrt +');
$('11 dup *');
$('0 5 /');
$('5 0 /');
$('2 3 + 4 2 +all');
$('2 3 + 4 2 *all')
$('2 3 + 4 2 clear');
$('2 3 inc * pop');
$('3.2 sin dup * 3.2 cos dup * +')
$('2 +');
$('dec')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment