Tôi đã viết SLIC (Hệ thống ngôn ngữ để triển khai trình biên dịch). Sau đó tự tay biên soạn nó thành lắp ráp. Có rất nhiều thứ cho SLIC vì nó là một trình biên dịch duy nhất gồm năm ngôn ngữ phụ:
- Ngôn ngữ lập trình phân tích cú pháp SYNTAX PPL
- GENERATOR LISP 2 ngôn ngữ tạo mã PSEUDO thu thập dữ liệu dựa trên cây
- ISO theo trình tự, mã PSEUDO, ngôn ngữ tối ưu hóa
- PSEUDO Macro giống như ngôn ngữ sản xuất mã hội.
- Máy móc hướng dẫn xác định ngôn ngữ máy.
SLIC được lấy cảm hứng từ CWIC (Trình biên dịch để viết và triển khai trình biên dịch). Không giống như hầu hết các gói phát triển trình biên dịch SLIC và CWIC tạo địa chỉ mã với các ngôn ngữ chuyên biệt, tên miền cụ thể. SLIC mở rộng việc tạo mã CWIC bằng cách thêm các ngôn ngữ phụ ISO, PSEUDO và MachOP tách biệt các chi tiết cụ thể của máy mục tiêu ra khỏi ngôn ngữ trình tạo thu thập dữ liệu cây.
LISP 2 cây và danh sách
Hệ thống quản lý bộ nhớ động của ngôn ngữ trình tạo LISP 2 là thành phần chính. Danh sách được thể hiện bằng ngôn ngữ được bao gồm trong dấu ngoặc vuông, các thành phần của nó được phân tách bằng dấu phẩy tức là danh sách ba phần tử [a, b, c].
Cây:
ADD
/ \
MPY 3
/ \
5 x
được đại diện bởi các danh sách có mục nhập đầu tiên là một đối tượng nút:
[ADD,[MPY,5,x],3]
Cây thường được hiển thị với nút riêng biệt trước các nhánh:
ADD[MPY[5,x],3]
Unparsing với các chức năng tạo dựa trên LISP 2
Hàm tạo là một tập hợp có tên (unparse) => hành động> cặp ...
<NAME>(<unparse>)=><action>;
(<unparse>)=><action>;
...
(<unparse>)=><action>;
Các biểu thức khác nhau là các thử nghiệm khớp với các mẫu cây và / hoặc các loại đối tượng phá vỡ chúng và gán các phần đó cho biến cục bộ để được xử lý bằng hành động thủ tục của nó. Kiểu như một hàm quá tải lấy các kiểu đối số khác nhau. Ngoại trừ các thử nghiệm () => ... được thử theo thứ tự được mã hóa. Unparse thành công đầu tiên thực hiện hành động tương ứng của nó. Các biểu thức khác nhau là các bài kiểm tra tháo gỡ. ADD [x, y] khớp với hai nhánh ADD cây gán các nhánh của nó cho các biến cục bộ x và y. Hành động có thể là một biểu thức đơn giản hoặc một khối mã giới hạn .BEGIN ... .END. Tôi sẽ sử dụng các khối c {{} ngày hôm nay. Kết hợp cây, [], các quy tắc khác nhau có thể gọi các trình tạo chuyển qua (các) kết quả được trả về cho hành động:
expr_gen(ADD[expr_gen(x),expr_gen(y)])=> x+y;
Cụ thể, expr_gen unparse ở trên khớp với cây ADD hai nhánh. Trong mẫu thử nghiệm, một trình tạo đối số duy nhất được đặt trong nhánh cây sẽ được gọi với nhánh đó. Danh sách đối số của nó mặc dù là các biến cục bộ được gán đối tượng trả về. Phía trên unparse chỉ định hai nhánh là THÊM phân tách cây, đệ quy nhấn từng nhánh để expr_gen. Trả về nhánh bên trái được đặt vào các biến cục bộ x. Tương tự, nhánh bên phải được truyền cho expr_gen với y đối tượng return. Ở trên có thể là một phần của bộ đánh giá biểu thức số. Có các tính năng phím tắt được gọi là vectơ ở trên thay vì chuỗi nút, vectơ của các nút có thể được sử dụng với một vectơ hành động tương ứng:
expr_gen(#node[expr_gen(x),expr_gen(y)])=> #action;
node: ADD, SUB, MPY, DIV;
action: x+y, x-y, x*y, x/y;
(NUMBER(x))=> x;
(SYMBOL(x))=> val:(x);
Trình đánh giá biểu thức đầy đủ hơn ở trên chỉ định trả về từ expr_gen nhánh trái cho x và nhánh phải cho y. Các vectơ hành động tương ứng được thực hiện trên x và y được trả về. Các cặp hành động unparse => cuối cùng khớp với các đối tượng số và ký hiệu.
Biểu tượng và thuộc tính biểu tượng
Biểu tượng có thể có thuộc tính được đặt tên. val: (x) truy cập thuộc tính val của đối tượng ký hiệu có trong x. Một bảng biểu tượng tổng quát là một phần của SLIC. Bảng SYMBOL có thể được đẩy và bật cung cấp các ký hiệu cục bộ cho các hàm. Biểu tượng mới được tạo ra được xếp vào bảng biểu tượng trên cùng. Tra cứu biểu tượng tìm kiếm ngăn xếp bảng biểu tượng từ bảng trên cùng trước tiên xuống phía sau ngăn xếp.
Tạo mã độc lập cho máy
Ngôn ngữ trình tạo của SLIC tạo ra các đối tượng hướng dẫn PSEUDO, nối chúng vào danh sách mã phần. Một .Lush làm cho danh sách mã PSEUDO của nó được chạy loại bỏ từng lệnh PSEUDO khỏi danh sách và gọi nó. Sau khi thực hiện, bộ nhớ đối tượng PSEUDO được giải phóng. Các cơ quan tố tụng của các hành động PSEUDO và GENERATOR về cơ bản là cùng một ngôn ngữ ngoại trừ đầu ra của chúng. PSEUDO có nghĩa là hoạt động như các macro lắp ráp cung cấp tuần tự mã độc lập cho máy. Chúng cung cấp một sự tách biệt của máy mục tiêu cụ thể ra khỏi ngôn ngữ trình tạo thu thập dữ liệu cây. Các PSEUDO gọi các hàm MachOP để xuất mã máy. Máy được sử dụng để xác định ops giả lắp ráp (như dc, xác định hằng số, v.v.) và hướng dẫn máy hoặc một họ các hướng dẫn được định dạng giống như sử dụng mục nhập vectơ. Họ chỉ đơn giản chuyển đổi các tham số của họ thành một chuỗi các trường bit tạo thành lệnh. Các cuộc gọi MachOP có nghĩa là trông giống như lắp ráp và cung cấp định dạng in của các trường khi lắp ráp được hiển thị trong danh sách biên dịch. Trong mã ví dụ tôi đang sử dụng nhận xét kiểu c có thể dễ dàng thêm nhưng không có trong các ngôn ngữ gốc. Máy đang sản xuất mã vào một bộ nhớ địa chỉ bit. Trình liên kết SLIC xử lý đầu ra của trình biên dịch. Một máy cho hướng dẫn chế độ người dùng DEC-10 bằng cách sử dụng mục nhập có vectơ: Máy đang sản xuất mã vào một bộ nhớ địa chỉ bit. Trình liên kết SLIC xử lý đầu ra của trình biên dịch. Một máy cho hướng dẫn chế độ người dùng DEC-10 bằng cách sử dụng mục nhập có vectơ: Máy đang sản xuất mã vào một bộ nhớ địa chỉ bit. Trình liên kết SLIC xử lý đầu ra của trình biên dịch. Một máy cho hướng dẫn chế độ người dùng DEC-10 bằng cách sử dụng mục nhập có vectơ:
.MACHOP #opnm register,@indirect offset (index): // Instruction's parameters.
.MORG 36, O(18): $/36; // Align to 36 bit boundary print format: 18 bit octal $/36
O(9): #opcd; // Op code 9 bit octal print out
(4): register; // 4 bit register field appended print
(1): indirect; // 1 bit appended print
(4): index; // 4 bit index register appended print
O(18): if (#opcd&&3==1) offset // immediate mode use value else
else offset/36; // memory address divide by 36
// to get word address.
// Vectored entry opcode table:
#opnm := MOVE, MOVEI, MOVEM, MOVES, MOVS, MOVSI, MOVSM, MOVSS,
MOVN, MOVNI, MOVNM, MOVNS, MOVM, MOVMI, MOVMM, MOVMS,
IMUL, IMULI, IMULM, IMULB, MUL, MULI, MULM, MULB,
...
TDO, TSO, TDOE, TSOE, TDOA, TSOA, TDON, TSON;
// corresponding opcode value:
#opcd := 0O200, 0O201, 0O202, 0O203, 0O204, 0O205, 0O206, 0O207,
0O210, 0O211, 0O212, 0O213, 0O214, 0O215, 0O216, 0O217,
0O220, 0O221, 0O222, 0O223, 0O224, 0O225, 0O226, 0O227,
...
0O670, 0O671, 0O672, 0O673, 0O674, 0O675, 0O676, 0O677;
.MORG 36, O (18): $ / 36; căn chỉnh vị trí thành ranh giới 36 bit in vị trí địa chỉ $ / 36 từ 18 bit trong bát phân. Opcd 9 bit, thanh ghi 4 bit, bit gián tiếp và thanh ghi chỉ số 4 bit được kết hợp và in như thể một trường 18 bit duy nhất. Địa chỉ 18 bit / 36 hoặc giá trị ngay lập tức là đầu ra và được in bằng số bát phân. Một ví dụ MOVEI in ra với r1 = 1 và r2 = 2:
400020 201082 000005 MOVEI r1,5(r2)
Với tùy chọn lắp ráp trình biên dịch, bạn có được mã lắp ráp được tạo trong danh sách biên dịch.
Liên kết nó lại với nhau
Trình liên kết SLIC được cung cấp dưới dạng thư viện xử lý các độ phân giải liên kết và ký hiệu. Định dạng tệp tải đầu ra cụ thể mặc dù phải được ghi cho các máy đích và được liên kết với thư viện thư viện liên kết.
Ngôn ngữ trình tạo có khả năng ghi cây vào một tệp và đọc chúng cho phép thực hiện nhiều trình biên dịch.
Tóm tắt ngắn về việc tạo mã và nguồn gốc
Tôi đã đi qua việc tạo mã trước tiên để đảm bảo rằng SLIC là một trình biên dịch trình biên dịch thực sự. SLIC được lấy cảm hứng từ CWIC (Trình biên dịch để viết và triển khai trình biên dịch) được phát triển tại Tập đoàn Phát triển Hệ thống vào cuối những năm 1960. CWIC chỉ có các ngôn ngữ SYNTAX và GENERATOR tạo mã byte số ngoài ngôn ngữ GENERATOR. Mã byte được đặt hoặc trồng (thuật ngữ được sử dụng trong tài liệu CWICs) vào bộ đệm bộ nhớ được liên kết với các phần được đặt tên và được viết ra bởi một câu lệnh. Một bài báo ACM trên CWIC có sẵn từ kho lưu trữ ACM.
Thực hiện thành công một ngôn ngữ lập trình chính
Vào cuối những năm 1970, SLIC đã được sử dụng để viết một trình biên dịch chéo COBOL. Hoàn thành trong khoảng 3 tháng chủ yếu bởi một lập trình viên duy nhất. Tôi đã làm việc một chút với các lập trình viên khi cần thiết. Một lập trình viên khác đã viết thư viện thời gian chạy và MÁY TÍNH cho máy tính mini TI-990. Trình biên dịch COBOL đó đã biên dịch nhiều dòng hơn mỗi giây sau đó trình biên dịch COBOL gốc DEC-10 được viết bằng cách lắp ráp.
Thêm vào một trình biên dịch sau đó thường nói về
Một phần lớn của việc viết một trình biên dịch từ đầu là thư viện thời gian chạy. Bạn cần một bảng biểu tượng. Bạn cần đầu vào và đầu ra. Quản lý bộ nhớ động, vv Có thể dễ dàng hơn khi viết thư viện thời gian chạy cho trình biên dịch sau đó viết trình biên dịch. Nhưng với SLIC, thư viện thời gian chạy là chung cho tất cả các trình biên dịch phát triển trong SLIC. Lưu ý có hai thư viện thời gian chạy. Một cho máy mục tiêu của ngôn ngữ (ví dụ như COBOL). Cái khác là trình biên dịch thư viện thời gian chạy.
Tôi nghĩ rằng tôi đã thiết lập rằng đây không phải là trình tạo phân tích cú pháp. Vì vậy, bây giờ với một chút hiểu biết về back end, tôi có thể giải thích ngôn ngữ lập trình trình phân tích cú pháp.
Ngôn ngữ lập trình phân tích cú pháp
Trình phân tích cú pháp được viết bằng công thức được viết dưới dạng các phương trình đơn giản.
<name> <formula type operator> <expression> ;
Yếu tố ngôn ngữ ở cấp độ thấp nhất là nhân vật. Mã thông báo được hình thành từ một tập hợp con của các ký tự của ngôn ngữ. Các lớp ký tự được sử dụng để đặt tên và định nghĩa các tập hợp con ký tự đó. Toán tử định nghĩa lớp ký tự là ký tự dấu hai chấm (:). Các ký tự là thành viên của lớp được mã hóa ở bên phải của định nghĩa. Các ký tự có thể in được đặt trong các chuỗi số nguyên tố. Các ký tự không in và đặc biệt có thể được biểu diễn bằng số thứ tự của chúng. Các thành viên trong lớp được phân tách bằng một phương án | nhà điều hành. Một công thức lớp kết thúc bằng dấu chấm phẩy. Các lớp nhân vật có thể bao gồm các lớp được xác định trước đó:
/* Character Class Formula class_mask */
bin: '0'|'1'; // 0b00000010
oct: bin|'2'|'3'|'4'|'5'|'6'|'7'; // 0b00000110
dgt: oct|'8'|'9'; // 0b00001110
hex: dgt|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; // 0b00011110
upr: 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|
'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'; // 0b00100000
lwr: 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'; // 0b01000000
alpha: upr|lwr; // 0b01100000
alphanum: alpha|dgt; // 0b01101110
Skip_group 0b00000001 được xác định trước nhưng có thể vượt quá định nghĩa là Skip_group.
Tóm lại: Một lớp ký tự là một danh sách thay thế chỉ có thể là hằng số ký tự, thứ tự của một ký tự hoặc một lớp ký tự được xác định trước đó. Khi tôi triển khai các lớp ký tự: Công thức lớp được gán một mặt nạ bit lớp. (Hiển thị trong các nhận xét ở trên) Bất kỳ công thức lớp nào có bất kỳ ký tự hoặc ký tự nào đều khiến bit lớp được phân bổ. Một mặt nạ được tạo bằng cách ghép (các) mặt nạ lớp của lớp được bao gồm cùng với bit được phân bổ (nếu có). Một bảng lớp được tạo từ các lớp nhân vật. Một mục được lập chỉ mục bởi thứ tự của một ký tự chứa các bit biểu thị tư cách thành viên lớp của nhân vật. Kiểm tra lớp được thực hiện nội tuyến. Một ví dụ mã IA-86 với thứ tự của nhân vật trong eax minh họa thử nghiệm lớp:
test byte ptr [eax+_classmap],dgt
Theo sau là một:
jne <success>
hoặc là
je <failure>
Các ví dụ mã lệnh IA-86 được sử dụng vì tôi nghĩ rằng các lệnh IA-86 được biết đến rộng rãi hơn ngày nay. Tên lớp đánh giá cho mặt nạ lớp của nó là không phá hủy ANDed với bảng lớp được lập chỉ mục bởi các ký tự thứ tự (trong eax). Một kết quả khác không chỉ ra thành viên lớp. (EAX bằng 0 ngoại trừ al (8 bit thấp của EAX) có chứa ký tự).
Mã thông báo có một chút khác biệt trong các trình biên dịch cũ này. Từ khóa không được giải thích là mã thông báo. Chúng chỉ đơn giản được khớp bởi các hằng chuỗi được trích dẫn trong ngôn ngữ trình phân tích cú pháp. Chuỗi trích dẫn thường không được giữ. Công cụ sửa đổi có thể được sử dụng. A + giữ cho chuỗi khớp. (tức là + '-' khớp với một - ký tự giữ ký tự khi thành công) Thao tác, (tức là 'E') chèn chuỗi vào mã thông báo. Khoảng trắng được xử lý bằng công thức mã thông báo bỏ qua các ký tự SKIP_CLASS hàng đầu cho đến khi trận đấu đầu tiên được thực hiện. Lưu ý rằng một kết hợp ký tự Skip_group rõ ràng sẽ dừng việc bỏ qua cho phép mã thông báo bắt đầu bằng ký tự Skip_group. Công thức mã thông báo chuỗi bỏ qua các ký tự bỏ qua hàng đầu khớp với một ký tự trích dẫn trích dẫn đơn hoặc một chuỗi trích dẫn kép. Điều đáng quan tâm là khớp một "ký tự trong một chuỗi" được trích dẫn:
string .. (''' .ANY ''' | '"' $(-"""" .ANY | """""","""") '"') MAKSTR[];
Sự thay thế đầu tiên phù hợp với bất kỳ trích dẫn duy nhất trích dẫn nhân vật. Lựa chọn thay thế phù hợp với một chuỗi trích dẫn kép có thể bao gồm các ký tự trích dẫn kép sử dụng hai ký tự "cùng nhau để thể hiện một ký tự". Công thức này xác định các chuỗi được sử dụng trong định nghĩa riêng của nó. Thay thế bên phải bên trong '"' $ (-" "" .ANY | "" "" "", "" "") '"' khớp với một trích dẫn kép trích dẫn. Chúng ta có thể sử dụng một ký tự được trích dẫn để khớp với một ký tự "trích dẫn". Tuy nhiên, trong chuỗi trích dẫn "kép" nếu chúng ta muốn sử dụng một ký tự "chúng ta phải sử dụng hai" ký tự để có được một ký tự. Ví dụ: ở bên trái thay thế phù hợp với bất kỳ ký tự nào ngoại trừ một trích dẫn:
-"""" .ANY
một cái nhìn tiêu cực phía trước - "" "" được sử dụng mà khi thành công (không khớp với "ký tự) thì khớp với ký tự .ANY (không thể là" ký tự vì - "" "" đã loại bỏ khả năng đó). Lựa chọn thay thế phù hợp đang đảm nhận - "" "" khớp với một ký tự "và thất bại là lựa chọn thay thế phù hợp:
"""""",""""
cố gắng khớp hai "ký tự thay thế chúng bằng một ký tự kép" bằng cách sử dụng "," "" để chèn ký tự thw ". Cả hai lựa chọn bên trong không thể ký tự trích dẫn chuỗi đóng được khớp và MAKSTR [] được gọi để tạo đối tượng chuỗi. chuỗi, vòng lặp trong khi thành công, toán tử được sử dụng để khớp chuỗi. Công thức mã thông báo bỏ qua các ký tự lớp bỏ qua hàng đầu (khoảng trắng). Khi một kết quả khớp đầu tiên được thực hiện bỏ qua bỏ qua. Chúng ta có thể gọi các hàm được lập trình bằng các ngôn ngữ khác bằng cách sử dụng []. MAKSTR [], MAKBIN [], MAKOCT [], MAKHEX [], MAKFLOAT [] và MAKINT [] được cung cấp chức năng thư viện để chuyển đổi chuỗi mã thông báo phù hợp thành đối tượng được nhập. Công thức số bên dưới minh họa nhận dạng mã thông báo khá phức tạp:
number .. "0B" bin $bin MAKBIN[] // binary integer
|"0O" oct $oct MAKOCT[] // octal integer
|("0H"|"0X") hex $hex MAKHEX[] // hexadecimal integer
// look for decimal number determining if integer or floating point.
| ('+'|+'-'|--) // only - matters
dgt $dgt // integer part
( +'.' $dgt // fractional part?
((+'E'|'e','E') // exponent part
('+'|+'-'|--) // Only negative matters
dgt(dgt(dgt|--)|--)|--) // 1 2 or 3 digit exponent
MAKFLOAT[] ) // floating point
MAKINT[]; // decimal integer
Công thức mã thông báo số ở trên nhận ra số nguyên và số dấu phẩy động. Các - lựa chọn thay thế luôn luôn thành công. Các đối tượng số có thể được sử dụng trong tính toán. Các đối tượng mã thông báo được đẩy lên ngăn xếp phân tích thành công của công thức. Dẫn số mũ trong (+ 'E' | 'e', 'E') rất thú vị. Chúng tôi muốn luôn có chữ hoa E cho MAKEFLOAT []. Nhưng chúng tôi cho phép viết thường 'e' thay thế bằng 'E'.
Bạn có thể đã nhận thấy tính nhất quán của lớp ký tự và công thức mã thông báo. Công thức phân tích cú pháp tiếp tục bổ sung các giải pháp thay thế quay lui và các toán tử xây dựng cây. Các toán tử thay thế quay lui và không quay lui có thể không được trộn lẫn trong một mức biểu thức. Bạn có thể không có (a | b \ c) trộn không quay lại | tạm biệt thay thế quay lui. (a \ b \ c), (a | b | c) và ((a | b) \ c) là hợp lệ. Một thay thế quay lui \ lưu trạng thái phân tích cú pháp trước khi thử thay thế bên trái và khi thất bại khôi phục trạng thái phân tích cú pháp trước khi thử phương án thay thế bên phải. Trong một chuỗi các lựa chọn thay thế, sự thay thế thành công đầu tiên thỏa mãn nhóm. Các lựa chọn thay thế khác không được cố gắng. Bao thanh toán và nhóm cung cấp cho một phân tích tiến bộ liên tục. Sự thay thế backtrack tạo ra một trạng thái đã lưu của phân tích cú pháp trước khi nó cố gắng thay thế bên trái của nó. Quay lui được yêu cầu khi phân tích cú pháp có thể khớp một phần và sau đó không thành công:
(a b | c d)\ e
Trong trường hợp trên nếu lỗi trả về thì cd thay thế được thử. Nếu sau đó c trả về thất bại, thay thế backtrack sẽ được thử. Nếu a thành công và b thất bại, parse wile sẽ bị quay lại và e đã cố gắng. Tương tự như vậy, một c thất bại thành công và b thất bại phân tích cú pháp được quay lại và thay thế e đã thực hiện. Quay lui không giới hạn trong một công thức. Nếu bất kỳ công thức phân tích cú pháp nào thực hiện khớp một phần bất cứ lúc nào và sau đó thất bại, phân tích cú pháp sẽ được đặt lại về backtrack hàng đầu và thay thế được thực hiện. Một lỗi biên dịch có thể xảy ra nếu mã đã được xuất có nghĩa là backtrack được tạo. Một backtrack được thiết lập trước khi bắt đầu biên dịch. Trả lại thất bại hoặc quay lại với nó là một lỗi biên dịch. Backtracks được xếp chồng lên nhau. Chúng ta có thể sử dụng tiêu cực - và tích cực? nhìn trộm / nhìn về phía trước các nhà khai thác để kiểm tra mà không tiến tới phân tích cú pháp. kiểm tra chuỗi là một cái nhìn phía trước chỉ cần lưu lại và thiết lập lại trạng thái đầu vào. Nhìn về phía trước sẽ là một biểu thức phân tích làm cho khớp một phần trước khi thất bại. Một cái nhìn phía trước được thực hiện bằng cách sử dụng quay lui.
Ngôn ngữ trình phân tích cú pháp không phải là trình phân tích cú pháp LL hoặc LR. Nhưng một ngôn ngữ lập trình để viết một trình phân tích cú pháp tốt đệ quy trong đó bạn lập trình xây dựng cây:
:<node name> creates a node object and pushes it onto the node stack.
.. Token formula create token objects and push them onto
the parse stack.
!<number> pops the top node object and top <number> of parstack
entries into a list representation of the tree. The
tree then pushed onto the parse stack.
+[ ... ]+ creates a list of the parse stack entries created
between them:
'(' +[argument $(',' argument]+ ')'
could parse an argument list. into a list.
Một ví dụ phân tích cú pháp thường được sử dụng là một biểu thức số học:
Exp = Term $(('+':ADD|'-':SUB) Term!2);
Term = Factor $(('*':MPY|'/':DIV) Factor!2);
Factor = ( number
| id ( '(' +[Exp $(',' Exp)]+ ')' :FUN!2
| --)
| '(' Exp ')" )
(^' Factor:XPO!2 |--);
Exp và Term sử dụng một vòng lặp tạo ra một cây thuận tay trái. Yếu tố sử dụng đệ quy đúng tạo ra một cây thuận tay phải:
d^(x+5)^3-a+b*c => ADD[SUB[EXP[EXP[d,ADD[x,5]],3],a],MPY[b,c]]
ADD
/ \
SUB MPY
/ \ / \
EXP a b c
/ \
d EXP
/ \
ADD 3
/ \
x 5
Dưới đây là một chút về trình biên dịch cc, một phiên bản cập nhật của SLIC với các bình luận kiểu c. Các loại hàm (ngữ pháp, mã thông báo, lớp ký tự, trình tạo, PSEUDO hoặc MachOP được xác định theo cú pháp ban đầu theo id của chúng. Với các trình phân tích cú pháp từ trên xuống này, bạn bắt đầu với một công thức xác định chương trình:
program = $((declaration // A program is a sequence of
// declarations terminated by
|.EOF .STOP) // End Of File finish & stop compile
\ // Backtrack: .EOF failed or
// declaration long-failed.
(ERRORX["?Error?"] // report unknown error
// flagging furthest parse point.
$(-';' (.ANY // find a ';'. skiping .ANY
| .STOP)) // character: .ANY fails on end of file
// so .STOP ends the compile.
// (-';') failing breaks loop.
';')); // Match ';' and continue
declaration = "#" directive // Compiler directive.
| comment // skips comment text
| global DECLAR[*1] // Global linkage
|(id // functions starting with an id:
( formula PARSER[*1] // Parsing formula
| sequencer GENERATOR[*1] // Code generator
| optimizer ISO[*1] // Optimizer
| pseudo_op PRODUCTION[*1] // Pseudo instruction
| emitor_op MACHOP[*1] // Machine instruction
) // All the above start with an identifier
\ (ERRORX["Syntax error."]
garbol); // skip over error.
// Lưu ý cách id được bao thanh toán và sau đó kết hợp khi tạo cây.
formula = ("==" syntax :BCKTRAK // backtrack grammar formula
|'=' syntax :SYNTAX // grammar formula.
|':' chclass :CLASS // character class define
|".." token :TOKEN // token formula
)';' !2 // Combine node name with id
// parsed in calling declaration
// formula and tree produced
// by the called syntax, token
// or character class formula.
$(-(.NL |"/*") (.ANY|.STOP)); Comment ; to line separator?
chclass = +[ letter $('|' letter) ]+;// a simple list of character codes
// except
letter = char | number | id; // when including another class
syntax = seq ('|' alt1|'\' alt2 |--);
alt1 = seq:ALT!2 ('|' alt1|--); Non-backtrack alternative sequence.
alt2 = seq:BKTK!2 ('\' alt2|--); backtrack alternative sequence
seq = +[oper $oper]+;
oper = test | action | '(' syntax ')' | comment;
test = string | id ('[' (arg_list| ,NILL) ']':GENCALL!2|.EMPTY);
action = ':' id:NODE!1
| '!' number:MAKTREE!1
| "+[" seq "]+" :MAKLST!1;
// C style comments
comment = "//" $(-.NL .ANY)
| "/*" $(-"*/" .ANY) "*/";
Đáng chú ý là cách ngôn ngữ trình phân tích cú pháp xử lý bình luận và phục hồi lỗi.
Tôi nghĩ rằng tôi đã trả lời câu hỏi. Đã viết một phần lớn của người kế nhiệm SLIC, ngôn ngữ cc ở đây. Không có trình biên dịch cho nó được nêu ra. Nhưng tôi có thể biên dịch nó thành mã lắp ráp, các hàm cm hoặc c ++ trần trụi.