2021-02-28

微信小程序制作科學(xué)計(jì)算器

項(xiàng)目地址:https://github.com/planck-fanqi/wxapp_calculatorWin10

1. 表達(dá)式求值

實(shí)現(xiàn)方法:

本例使用利用簡單編譯器對用戶輸入的表達(dá)式進(jìn)行求值仲吏。相關(guān)文件calculator_compiler.js

測試代碼:

var expression='(1+2)*3'
var tokens=tokenizer(expression);
var ast=parser(tokens);
var est=traverser(ast);
var result=compile(est)

測試結(jié)果:

計(jì)算步驟
1. 詞法解析
function numToken(){//數(shù)值token檢測類岸霹,支持浮點(diǎn)數(shù)檢測
  var dotFlag=false;let NUMBERS = /[0-9]/;
  this.test=function(char){
    if(char=='.' && !dotFlag && (dotFlag=true)) return true;
    if(NUMBERS.test(char)) return true;
    return false;
  }
}
function tokenizer(input, varibles=null) {
  let current = 0;  let tokens = [];//current定位當(dāng)前字符所在位置
  let operators=['+','-','*','/','^','√'];let op_grade={'+':1,'-':1,'*':2,'/':2,'^':3,'√':3};
  let LETTERS = /[a-z]/i;
  // let NUMBERS = /[0-9]/;
  while (current < input.length) {
    let char = input[current++];
    let NUMBERS=new numToken();
    if (char === '(') 
      tokens.push({ type: 'paren', value: '(' });
    else if (char === ')') 
      tokens.push({ type: 'paren', value: ')' });
    else if (NUMBERS.test(char)) {
      let value = char;
      while (NUMBERS.test(char=input[current]) || char=='.') {//連續(xù)數(shù)值字符串檢測
        value += char;
        char = input[current++];
      }
      tokens.push({ type: 'Number', value: parseFloat(value) } );
    }
    else if (LETTERS.test(char) || char=='π') {
      let value = char; 
      var bugcnt='';
      while (LETTERS.test(char=input[current]) && char) {//連續(xù)函數(shù)字符串檢測
        value += char;
        char = input[current++];
      }

      if(varibles && varibles[value])//變量字符串賦值连锯,比如Ans者春,e,π
        tokens.push({ type:'Number', value:varibles[value]});
      else if(value=='e')      
        tokens.push({ type:'Number', value:Math.E});
      else if(value=='π') 
        tokens.push({ type:'Number', value:Math.PI});
      else tokens.push({ type: 'Function', value });
    }
    else if(operators.indexOf(char)>=0)
      tokens.push({ type: 'Operator', value: char, grade:op_grade[char] });
    else throw new TypeError('I dont know what this character is: ' + char);
  }
  return tokens;
}
2. 句法解析
function parser(tokens) {
  let current = 0;
  
  function walk() {//遞歸函數(shù)遍歷生成語法樹
    let token = tokens[current++];
    if (token.type === 'Function') 
      return { type:'Function', value:token.value, expression:walk()}//生成函數(shù)子節(jié)點(diǎn)
    if (token.type === 'paren' && token.value === '(' ) {//生成新子語法樹節(jié)點(diǎn)
      token = tokens[current];
      let node = { type: 'CallExpression', params: [] };

      while (
        (token.type !== 'paren') ||
        (token.type === 'paren' && token.value !== ')')
      ) {
        node.params.push(walk());
        token = tokens[current];
      }
      current++;
      return node;
    }
    return token;
  }
 
  let ast = { type: 'CallExpression', params:[]};//新建語法樹根節(jié)點(diǎn)
  while (current < tokens.length)
    ast.params.push(walk());
  return ast;
}
3. 語法樹轉(zhuǎn)換

當(dāng)前獲得的語法樹并不適合計(jì)算,需要將進(jìn)行轉(zhuǎn)換成以計(jì)算符號為父節(jié)點(diǎn)的語法樹。

function traverser(ast) {
  function traverseNode(params,start,end) {
    if(start+1==end) 
      if(params[start].type=='CallExpression')//遞歸轉(zhuǎn)換每一個子表達(dá)式
        return traverser(params[start]);
      else if(params[start].type=='Function')
        return { type:'Function', value:params[start].value,
                 expression:traverser(params[start].expression)}//轉(zhuǎn)換函數(shù)節(jié)點(diǎn)子表達(dá)式
      else return params[start];
    var mid=end, grade_min=9;

    for(var i=start;i<end;i++) 
      if(params[i].type=='Operator' && params[i].grade<=grade_min) //對于沒有括號但是計(jì)算符號等級不同的計(jì)算式,查找優(yōu)先級最低負(fù)號進(jìn)行分割灾常,比如 1*2+3*4=>(1*2)+(3*4)
        grade_min=params[mid=i].grade;
    var node={ type:'BinaryOperator', params:[], value:params[mid].value};
    if(mid!=start) node.params.push(traverseNode(params,start,mid));
    if(mid!=end) node.params.push(traverseNode(params,mid+1,end));
    return node;
  }
  return traverseNode(ast.params,0,ast.params.length);
}
4.編譯計(jì)算
function compile(t_ast,angle=true){
  var visitor={//各符號與函數(shù)對應(yīng)計(jì)算式
    '+':     (a,b)=>{ return a+b; },
    '-':     (a,b)=>{ if(b==undefined) return -a;//當(dāng) 減號 為單目運(yùn)算符時,將數(shù)字轉(zhuǎn)換成負(fù)數(shù)
                      return a-b; },
    '*':     (a,b)=>{ return a*b; },
    '/':     (a,b)=>{ return a/b; },
    '^':     (a,b)=>{ return Math.pow(a,  b); },
    '√':     (a,b)=>{ if(b==undefined) return Math.pow(a,.5)//當(dāng) 根號 為單目運(yùn)算符時铃拇,默認(rèn)為開平方根
                      return Math.pow(b,1/a); },
    'sin':   (a,angle=false)=>{ return   Math.sin(  (angle?Math.PI/180:1)*a); },
    'cos':   (a,angle=false)=>{ return   Math.cos(  (angle?Math.PI/180:1)*a); },
    'tan':   (a,angle=false)=>{ return   Math.tan(  (angle?Math.PI/180:1)*a); },
    'cot':   (a,angle=false)=>{ return 1/Math.tan(  (angle?Math.PI/180:1)*a); },
    'asin':  (a,angle=false)=>{ return   Math.asin(a)/(angle?Math.PI/180:1); },
    'acos':  (a,angle=false)=>{ return   Math.acos(a)/(angle?Math.PI/180:1); },
    'atan':  (a,angle=false)=>{ return   Math.atan(a)/(angle?Math.PI/180:1); },
    'acot':  (a,angle=false)=>{ return 1/Math.atan(a)/(angle?Math.PI/180:1); },
    'log':   (a)=>{ return Math.log10(a); },
    'ln':    (a)=>{ return Math.log(a); },
    'sqrt':  (a)=>{ return Math.pow(a,0.5); },
    'square':(a)=>{ return Math.pow(a,2); },
  }
  function calculateNode(node){//遞歸方法計(jì)算表達(dá)式根節(jié)點(diǎn)
    if(!node) return undefined
    if(node.type=='Number') return node.value;
    var method=visitor[node.value];
    if(node.type=='BinaryOperator') 
      return method(calculateNode(node.params[0]),calculateNode(node.params[1]));
    if(node.type=='Function')
      return method(calculateNode(node.expression),angle);
  }
  return calculateNode(t_ast);
}

2. 用戶輸入合法性檢查

相關(guān)文件BtnCheck.js 钞瀑,為了保證用戶輸入表達(dá)式正確性將對用戶每個輸入進(jìn)行合法性檢查,比如用戶已輸入3.45 后再輸入. 即為無效輸入慷荔。此文件通過用戶輸入生成不同狀態(tài)的狀態(tài)機(jī)以檢查下一個輸入合法性雕什。狀態(tài)機(jī)轉(zhuǎn)換圖如下:

3. 小程序結(jié)構(gòu)

  • 界面設(shè)計(jì)

    軟件界面采用毛玻璃UI設(shè)計(jì)。相關(guān)文件index.wxssindex.wxml

  • 邏輯設(shè)計(jì)

    相關(guān)文件index.js

4. 其他

除了基本的計(jì)算功能之外贷岸,本項(xiàng)目添加了一些提高用戶交互的功能壹士,比如,歷史記錄長按輸入偿警。函數(shù)鍵盤滑動查看三角函數(shù)躏救,以及屏幕區(qū)字符定位等功能。不過實(shí)現(xiàn)邏輯比較復(fù)雜螟蒸,沒有詳細(xì)解釋盒使。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市七嫌,隨后出現(xiàn)的幾起案子少办,更是在濱河造成了極大的恐慌,老刑警劉巖抄瑟,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡泣,死亡現(xiàn)場離奇詭異,居然都是意外死亡皮假,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門骂维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惹资,“玉大人,你說我怎么就攤上這事航闺⊥什猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵潦刃,是天一觀的道長侮措。 經(jīng)常有香客問我,道長乖杠,這世上最難降的妖魔是什么分扎? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮胧洒,結(jié)果婚禮上畏吓,老公的妹妹穿的比我還像新娘。我一直安慰自己卫漫,他們只是感情好菲饼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著列赎,像睡著了一般宏悦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天饼煞,我揣著相機(jī)與錄音源葫,去河邊找鬼。 笑死派哲,一個胖子當(dāng)著我的面吹牛臼氨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芭届,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼储矩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了褂乍?” 一聲冷哼從身側(cè)響起持隧,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逃片,沒想到半個月后屡拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褥实,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年呀狼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片损离。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哥艇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僻澎,到底是詐尸還是另有隱情貌踏,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布窟勃,位于F島的核電站祖乳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秉氧。R本人自食惡果不足惜眷昆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谬运。 院中可真熱鬧隙赁,春花似錦、人聲如沸梆暖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轰驳。三九已至厚掷,卻和暖如春弟灼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冒黑。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工田绑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抡爹。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓掩驱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冬竟。 傳聞我的和親對象是個殘疾皇子欧穴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容