當前位置: 華文星空 > 知識

為什麽中國出了這麽多厲害的互聯網公司,但沒有自己設計過程式語言?

2019-12-08知識

說句實話,如果只是設計做出一門程式語言,並不是一件多復雜的事情,一個人三到六個月足矣,但如果要形成一整套編譯,開發,偵錯的工具鏈,讓這門語言能夠解決實際生產力問題而不是玩具,一需要投入大量的時間去開發相關的工具鏈,二要在長時間的工程實踐中去驗證去完善什麽功能是需要的,而什麽功能只是花瓶的玩具功能.對語言進行完善精簡.這就是一項龐大的工程了.

多年前我就已經從記憶體的管理開始,編寫過詞法分析器-->語法分析器-->編譯器-->虛擬機器-->偵錯程式,編寫過一個完整的類C編譯型手稿語言PainterScript,並為其編寫了一整套編譯開發工具鏈.作為自己設計的圖形遊戲引擎功能的補充 , 曾經相當一段長的時間,這門語言的作用僅僅只是證明我也是寫過編譯器的人了,下次碰上杠精我就能更加有底氣地直接把github的地址扔上去,

可惜在計畫中,其功能定位尷尬,絕大多數時候只能整個計畫框架裏吃灰,淪為比上不足比下有余的玩具語言.但隨著PainterEngine UI框架的完善和PainterEngine Live2D(LiveFramework)的控制需求,這門玩具語言終於迎來了了柳暗花明又一村的春天,使用了PainterScript恰了幾口飯終於成為了生產力的時候,我不禁發出了真香的感嘆.

當然,作為一個曾經被C艹的各種規範和語法糖惡心到多年的人,這門語言最開始的設計理念就是,簡單,更加簡單,無腦,更加無腦,因此,設計這門語言我的一開始的目標就是,關鍵詞能少就盡可能的少,不該出現的奇奇怪怪的語法糖堅決不出現,不需要的功能哪怕現在吹的再香,堅決不用,

最好就是那種十分鐘入門,二十分鐘上手,三十分鐘出貨的語言.杜絕心智負擔,杜絕無腦吹,杜絕對學習成本的白嫖.

鑒於我本人也是懶出名的實在不想整些花裏胡哨的東西以顯示自己的標新立異,我認為這些東西除了增加語言的學習成本根本沒卵用,因此關鍵詞設計起來和C語言極其相似,這樣我就可以直接用visual studio code來直接寫程式碼這樣就能白嫖各種令人舒服的語法檢查和補全功能,同時對有C語言基礎的人,只需要再花十分鐘看看,就可以直接上手了

那麽這門語言到底是怎麽樣的呢,我甚至能在這篇回答裏寫完這個指令碼的教程

首先需要搭建執行環境,因為PainterScript就和它的名字一樣,是圖形/遊戲引擎PainterEngine的功能補充,因此語言的執行環境是基於PainterEngine執行的條件下的,關於這個下面有PainterEngine的在windows/android/linux的移植教程

之後就非常簡單了,PainterEngine內建了一個Executer Console,只需要不操過六行程式碼,就可以將指令碼編譯到執行的環境搭建起來

PainterEngine_Application . h typedef struct { PX_Executer exe ; //定義Executer PX_Runtime runtime ; } PX_Application ; PainterEngine_Application . c #include "PainterEngine_Application.h" PX_Application App ; px_bool PX_ApplicationInitialize ( PX_Application * pApp , px_int screen_width , px_int screen_height ) { PX_ApplicationInitializeDefault ( & pApp -> runtime , screen_width , screen_height ); PX_ExecuterInitialize ( & pApp -> runtime , & pApp -> exe ); //初始化Executer //載入script.ps指令碼檔,編譯然後執行 PX_ExecuterRunScipt ( & pApp -> exe ,( px_char * ) PX_LoadFileToIOData ( "script.ps" ). buffer ); return PX_TRUE ; } px_void PX_ApplicationUpdate ( PX_Application * pApp , px_dword elpased ) { //更新控制台 PX_ExecuterUpdate ( & pApp -> exe , elpased ); } px_void PX_ApplicationRender ( PX_Application * pApp , px_dword elpased ) { px_surface * pRenderSurface =& pApp -> runtime . RenderSurface ; PX_RuntimeRenderClear ( & pApp -> runtime , PX_COLOR ( 255 , 255 , 255 , 255 )); //渲染控制台 PX_ExecuterRender ( & pApp -> exe , elpased ); } px_void PX_ApplicationPostEvent ( PX_Application * pApp , PX_Object_Event e ) { PX_ApplicationEventDefault ( & pApp -> runtime , e ); //處理I/O訊息 PX_ExecuterPostEvent ( & pApp -> exe , e ); }

如果成功執行,在windows中,你會看到這樣的界面

當然,鑒於PainterEngine的全平台支持特性,移植時程式碼你一個字母都不需要改,使用Android NDK重新編譯後在你的Android手機上執行,絕對沒一點毛病

現在,就可以開啟script.ps檔,開始寫PainterEngine Script 程式碼了,為了證明這個十分鐘入門,二十分鐘上手,三十分鐘出貨的口號不是在吹牛逼,當然我也不指望這門手稿語言有多少人用(畢竟自己用的爽,才是這門語言的最終目的),但花個十分鐘瞧一瞧看一看你也買不了吃虧買不了上當,何況PainterEngine Script從詞法分析語法分析到編譯虛擬機器的程式碼都是完整開源的,如果你是正在發愁今年畢業設計做什麽的大四本科僧,你要看得上盡管拿去用,撈個院優校優畢設,應該不成問題,老規矩,仍然從Hello World開始

Hello PainterScript

#name "main" #include "stdio.h" export int main () { print ( "Hello PainterScript" ); }

首先,PainterScript的開頭必須是 #name "名稱" 的格式,用來表示這個程式碼的"檔名",之所以這麽做是因為PainterEngine的移植平台有可能是沒有檔案系統的嵌入式環境,必須要有一個標識來記錄這個程式碼檔的名稱,然後你就可以使用#include "名稱"的方式,將這個原始碼包含進來.這和C語言的#include本質上大同小異,只是C語言的#include很可能要求你的編譯平台需要有檔案系統的支持

之後的int main()仍然和C語言一樣,是Executer的入口函式,也就是說第一行程式碼從這裏執行,之後就是使用Print函式將文本打印出來了.

PainterScript 基本數據型別

PainterScript有且僅有4中基本基礎數據型別+其對應的指標型別,當然,陣列的聲明和使用也和C語言一模一樣

分別是 int,float,string,memory

int 和 float就不多說了,和C語言的定義基本一樣,int表示整數型,float表示浮點型,運算和隱式轉換規則也和C語言一模一樣,你可能會問char short double long...也支持麽,那只能說抱歉,PainterScript不需要這些玩意,int儲存整數,float儲存浮點數(遵循IEEE754標準)沒了,你可以完全按照C來寫.

那麽重點就來到了PainterScript的自建型別string和memory了,這兩個型別,完全是為了貫徹落實簡單,更加簡單,無腦,更加無腦的理念而誕生的.

string數據型別

先說說string,你可以直接用一個字串對其進行賦值

string somestring="PainterScript String";

當然,加法也沒有問題,例如

string somestring="PainterScript"+"Hello";

或者

string a="hello"; string b="world"; string c=a+b;

如果說,你想存取字串的第一個字元怎麽辦,你可以透過[]對字串的元素存取,例如

int asc; string str="abc"; asc=str[0];//'a'的asc碼 asc=str[1];//'b'的asc碼 asc=str[2];//'c'的asc碼

你也可以直接修改它們

string str="abc"; str[0]='x';//xbc print(str);

當然,PainterScript擁有記憶體的自適應系統,你可以直接建立一個字串

string str; str[0]='a'; str[1]='b'; str[2]='c'; print(str);

最後,你可以使用string這個關鍵字,將其它型別轉換為string

string str; str="PI="+string(3.14); print(str); string _666="six six six "+string(666); print(_666);

memory數據型別

string數據型別用於儲存0結尾的字串,memory數據型別就是用來儲存數據流的,例如下面的程式碼,可以用於構建一個長度為3字節的0x00,0x01,0x02

memory data= @000102@

在PainterScript中,字串使用雙引號包含,而數據流用兩個 @ 符號包含

同樣的,data數據型別同樣支持加法拼接,例如

memory data; data=@01@; data=data+@0203@;

都是合法的

你同樣可以透過[]運算子號存取對應的索引字節的值,例如

memory data=@00FF@; print(string(data[0]));//0 print(string(data[1]));//255

結構體

PainterScript的結構體定義,和C艹的差不多,例如

struct person { string name; int age; } export int main() { person zhangsan; zhangsan.name="zhang san"; zhangsan.age=18; print(zhangsan.name); print(string(zhangsan.age)); }

好了,基本數據型別說完了,剩下說說語句結構

IF語句

不用多說,和c語言差不多其結構為

if(運算式){}else{}

運算式不為0,則為真,需要註意的是,運算式結果必須是int型別或float型別,如果運算式結果是一個string或memory型別,那麽編譯會報錯,但string型別和memory型別可以使用==,!=比較運算子直接進行比較,例如

string a="abc"; a=="abc";//真 a=="aaa";//假

下面是一個簡單的指令碼比較密碼的程式

string a=gets(); if(a=="123456") print("correct"); else print("wrong");

while語句

還是和C語言一毛一樣

while(運算式){執行語句;}

運算式不為0,則為真,執行語句塊程式碼

int i=6; while(i--) { print(string(i)); }

for語句

仍然是和C語言一毛一樣,就不多說了

for (初始化運算式; 迴圈條件運算式 ;迴圈後的操作運算式 ){執行語句;}

switch語句

這個和C語言有點不一樣,Switch的Case運算式,可以不是一個常量,而且運算式必須用括弧包含,其格式為

switch(運算式1) { case (運算式2) { 執行語句1; } case (運算式3) { 執行語句2; } ....... }

該語句將會將運算式1的結果與運算式2..3....逐一比較,如果比較為真則執行對應語句塊程式碼

string a=gets(); switch(a) { case ("abc") { print("input abc"); } case ("123") { print("input 123"); } }

其它關鍵字

break;跳出迴圈或switch,和c語言一樣

return;函式返回,和C語言一樣

export;匯出函式,如果這個函式需要被C語言呼叫,應該用export修飾

int()將其它數據型別轉換為整數,例如int("123")的結果是123

float()將其它數據型別轉換為浮點數,例如float("12.3")的結果是12.3

string()將其它數據型別轉換為字串

memory()將其它數據型別轉換為記憶體數據

_asm{} 行內PainterScript ASM組譯

memlen 計算memory型別的數據長度

strlen 計算string型別的字串長度

沒了.

套用場景

PainterScript的其使用場景一般有以下幾個

a.用於保護一些關鍵的"加密"程式碼,因為PainterScript作為一個編譯型手稿語言,由VM進行執行,其逆向難度將大大增加.

b.用於一系列效能要求不高的邏輯控制,因為執行環境可控,能能避免程式碼導致的記憶體和其它致命錯誤問題,能夠提高系統的穩定性,

c.用的最多的地方是,用於平台無關的協程或執行緒實作,例如下面的一個常見的工控場景

COM_Write(@010102@);//使用串口發送一個命令到裝置 Sleep(1000);//等待1秒讓裝置響應 memory data=COM_Read();//從串口讀取數據 if(data==@000000@) { print("裝置成功執行命令"); } else { print("命令執行失敗"); }

Sleep是一個阻塞函式,當PainterScript的虛擬機器執行到這個阻塞函式後,將返回,這個時候,程式就可以去處理UI繪制之類的其它工作了

第二個是多執行緒

int Thread1() { //do something } int Thread2() { //do something } int Thread3() { //do something }

在工控場景中,各個執行緒由PainterScript VM負責執行緒排程,實作並行"多執行緒"

你可能會問了,為什麽要用那麽麻煩的方式實作協程或多執行緒呢?

問得好!

1.C語言本身沒有協程和多執行緒,要實作要麽需要平台支持,奇怪的寫法要編譯器支持

2.一份程式碼,我一個字都不想改就想在windows/linux/andorid/iOS哪怕是裸奔的微控制器上跑

3.個人非常非常懶,甚至不想寫平台相容程式碼,是的,只需要一個ANSI C 編譯器哪怕沒有任何的標準庫支持,PainterScript都可以一個字都不用改重新編譯即可完成移植,PainterEngine 是一個無檔案系統依賴,無記憶體管理依賴,無編譯器特性相關,無平台依賴就可以完成移植的引擎,何況裸片的工控嵌入式幾乎也沒有作業系統平台

4.某語言吹快把記憶體安全和導致的問題吹上天了,你看,用一門手稿語言控制邏輯,內部排程自行gc,哪來的那麽多問題,還要啥車輪子?為什麽還要折騰自己?有那麽多時間學點別的不好麽?

5.逼格高啊.

最後點一下題:並不是沒有人設計程式語言,相反的,我相信很多的公司或從業很久的業內人士都有可能開發自己的程式語言,但這個語言首先是為了服務自己的需求誕生的,在此基礎上,這個語言要有多流行多少人用或者需要些什麽牛哄哄的功能,就顯得不那麽重要了,畢竟不管怎麽來,貼切解決了自己的問題的語言,才是一門好語言