成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手寫編程語言-遞歸函數是如何實現的?

開發 前端
本篇文章主要是記錄一下在 GScript 中實現遞歸調用時所遇到的坑,類似的問題在中文互聯網上我幾乎沒有找到相關的內容,所以還是很有必要記錄一下。

前言

本篇文章主要是記錄一下在 GScript 中實現遞歸調用時所遇到的坑,類似的問題在中文互聯網上我幾乎沒有找到相關的內容,所以還是很有必要記錄一下。

在開始之前還是簡單介紹下本次更新的 GScript v0.0.9 所包含的內容:

  • 支持可變參數
  • 優化append 函數語義
  • 優化編譯錯誤信息
  • 最后一個就是支持遞歸調用

先看第一個可變參數:

printf(string format, any ...a){}
string sprintf(string format, any ...a){}

以上是隨著本次更新新增的兩個標準函數,均支持可變參數,其中使用 ... 表示可變參數,調用時如下:

printf("hello %s ","123");
printf("hello-%s-%s ","123","abc");
printf("hello-%s-%d ","123",123);
string format = "this is %s ";
printf(format, "gscript");
string s = sprintf("nice to meet %s", "you");
assertEqual(s,"nice to meet you");

與大部分語言類似,可變參數本質上就是一個數組,所以可以拿來循環遍歷:

int add(string s, int ...num){
println(s);
int sum = 0;
for(int i=0;i<len(num);i++){
int v = num[i];
sum = sum+v;
}
return sum;
}
int x = add("abc", 1,2,3,4);
println(x);
assertEqual(x, 10);
append(any[] a, any v){}

之后是優化了內置函數 append() 的語義,本次優化來自于 issue12 的建議:https://github.com/crossoverJie/gscript/issues/12。

int[] a={1,2,3};
println(a);
println();
a = append(a,4);
println(a);
int[] a={1,2,3};
println(a);
println();
append(a,4);
int b = a[3];
assertEqual(4, b);
println(a);

現在 append 之后不需要再重新賦值,也會追加數據,優化后這里看起來是一個值/引用傳遞的問題,但其實底層也是值傳遞,只是在語法上增加了這樣的語法糖,幫使用者重新做了一次賦值。

之后是新增了編譯錯誤信息提示,比如下面這段代碼:

a+2;
b+c;

使用沒有聲明的變量,現在會直接編譯失敗:

1:0: undefined: a
2:0: undefined: b
2:2: undefined: c
class T{}
class T{}
2:0: class T redeclared in this block

重復聲明之類的語法錯誤也有相關提示。

最后一個才是本次討論的重點,也就是遞歸函數的支持。

int num(int x,int y){
if (y==1 || y ==x) {
return 1;
}
int v1 = num(x - 1, y - 1);
return c;
}

再上一個版本中 int v1 = num(x - 1, y - 1); 這行代碼是不會執行的,具體原因后文會分析。

現在利用遞歸便可以實現類似于打印楊輝三角之類的程序了:

int num(int x,int y){
if (y==1 || y ==x) {
return 1;
}
int v1 = num(x - 1, y - 1);
int v2 = num(x - 1, y);
int c = v1+v2;
return c;
}
printTriangle(int row){
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= row - i; j++) {
print(" ");
}
for (int j = 1; j <= i; j++) {
print(num(i, j) + " ");
}
println("");
}
}
printTriangle(7);
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1

函數中的 return

int num(int x,int y){
if (y==1 || y ==x) {
return 1;
}
int v1 = num(x - 1, y - 1);
return c;
}

現在我們來看看這樣的代碼為什么執行完 return 1 之后就不會執行后邊的語句了。

其實在此之前我首先解決的時候函數 return 后不能執行后續 statement 的需求,其實正好就是上文提到的邏輯,只是這里是遞歸而已。

先把代碼簡化一下方便分析:

int f1(int a){
if (a==10){
return 10;
}
println("abc");
}

當參數 a 等于 10 的時候確實不能執行后續的打印語句了,那么如何實現該需求呢?

以正常人類的思考方式:當我們執行完 return 語句的時候,就應該標記該語句所屬的函數直接返回,不能在執行后續的 statement。

可是這應該如何實操呢?

其實看看 AST 就能明白了:

圖片

當碰到 return 語句的時,會遞歸向上遍歷語法樹,標記上所有 block 節點表明這個 block 后續的語句不再執行了,同時還得把返回值記錄下來。

這樣當執行到下一個 statement 時,也就是 println("abc"); 則會判斷他所屬的 block 是否有被標記,如果有則直接返回,這樣便實現了 return 語句不執行后續代碼。

部分實現代碼如下:

func (v *Visitor) scanBlockStatementCtx(tree antlr.ParseTree, value interface{}) {
context, ok := tree.(*parser.BlockContext)
if ok {
if v.blockCtx2Mark == nil {
v.blockCtx2Mark = make(map[*parser.BlockContext]interface{})
}
v.blockCtx2Mark[context] = value
}
if tree.GetParent() != nil {
v.scanBlockStatementCtx(tree.GetParent().(antlr.ParseTree), value)
}
}

圖片

源碼地址:https://github.com/crossoverJie/gscript/blob/793d196244416574bd6be641534742e57c54db7a/visitor.go#L182。

遞歸的問題

但同時問題也來了,就是遞歸的時候也不會執行后續的遞歸代碼了。

其實解決問題的方法也很簡單,就是在判斷是否需要直接返回那里新增一個條件,這個 block 中不存在遞歸調用。

所以我們就得先知道這個 block 中是否存在遞歸調用。

整個過程有以下幾步:

  • 編譯期:在函數聲明處記錄下函數與當前context 的映射關系。
  • 編譯期:掃描statement 時,取出該 statement 的 context 所對應的函數。
  • 編譯期:掃描到的statement 如果是一個函數調用,則判斷該函數是否為該 block 中的函數,也就是第二步取出的函數。
  • 編譯期:如果兩個函數相等,則將當前block 標記為遞歸調用。
  • 運行期:在剛才判斷return 語句處,額外多出判斷當前 block 是否為遞歸調用,如果是則不能返回。

部分代碼如下:

圖片圖片

https://github.com/crossoverJie/gscript/blob/3e179f27cb30ca5c3af57b3fbf2e46075baa266b/resolver/ref_resolver.go#L70。

總結

這里的遞歸調用其實卡了我挺長時間的,思路是有的,但是寫出來的代碼總是和預期不符,當天晚上坐在電腦面前到凌晨兩三點,百思不得其解。

最后受不了上床休息的時候,突然一個靈光乍現讓我想到了解決方案,于是第二天起了個早床趕忙實踐,還真給解決了。

所以有些時候碰到棘手問題時給自己放松一下,往往會有出其不意的效果。

最后是目前的遞歸在某些情況下性能還有些問題,后續會盡量將這些標記過程都放在編譯期,編譯慢點沒事,但運行時慢那就有問題了。

之后還會繼續優化運行時的異常,目前是直接 ??panic??,堆棧也沒有,體感非常不好;歡迎感興趣的朋友試用反饋bug。

責任編輯:姜華 來源: crossoverJie
相關推薦

2022-09-19 08:10:37

運算符函數語言

2022-10-17 09:08:01

2024-07-10 08:22:42

2021-07-20 15:42:05

編程語言PythonJava

2021-05-28 05:34:06

Golang語言編程

2021-08-30 15:47:34

編程技能開發

2011-06-20 08:48:17

編程語言

2020-11-12 07:00:50

JavaScript前端編程語言

2021-08-23 15:05:21

PyretJavaScript編程

2015-10-29 09:36:31

高端編程語言

2019-07-17 13:45:42

網絡安全防火墻軟件

2017-09-12 11:02:51

Python編程語言

2017-11-28 16:57:18

2015-10-19 09:23:44

新編編程女人

2013-02-18 09:20:10

2018-11-21 09:33:01

2017-12-27 14:52:21

JSGo編程語言

2009-11-18 16:39:51

PHP遞歸刪除目錄

2019-01-30 12:38:41

JavaScript前端編程語言

2012-05-22 16:52:02

編程語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美国产精品久久久 | 午夜免费视频 | 免费久久99精品国产婷婷六月 | 精品国产一区二区三区性色av | av片网站| 玖玖免费| 91精品国产一区 | 91久久国产综合久久91精品网站 | 国产精品乱码一区二三区小蝌蚪 | 欧美成人a∨高清免费观看 色999日韩 | 日韩有码一区 | 久久久爽爽爽美女图片 | 户外露出一区二区三区 | 一区二区三区网站 | 亚洲视频在线观看 | 一级黄色毛片子 | 欧美日韩精品免费观看 | 在线观看亚洲专区 | 91精品国产91久久久 | 99色综合| 亚洲一区二区三区高清 | 国产精品18久久久久久白浆动漫 | 欧美日韩不卡合集视频 | 欧美久久久久久久 | 国产一区二区三区高清 | 欧美综合精品 | 日日干天天干 | 蜜臀久久99精品久久久久野外 | 久久久久久国产精品免费免费狐狸 | 亚洲免费在线 | 日本久草 | 亚洲精品日韩在线观看 | 国产一区二区三区在线 | 一级毛片视频 | 天天想天天干 | 色天天综合| 精品久久久久久亚洲精品 | 91九色麻豆| 日朝毛片 | 欧美日韩亚洲成人 | 国产精品视频网 |