Con vẹt - Hướng dẫn nhanh

Khi chúng ta đưa chương trình của mình vào Perl thông thường, trước tiên nó được biên dịch thành một biểu diễn nội bộ, hoặc mã bytecode; mã bytecode này sau đó được đưa vào hệ thống con gần như riêng biệt bên trong Perl để được thông dịch. Vì vậy, có hai giai đoạn khác nhau trong hoạt động của Perl:

  • Biên dịch thành bytecode và

  • Diễn giải mã bytecode.

Đây không phải là duy nhất đối với Perl. Các ngôn ngữ khác theo thiết kế này bao gồm Python, Ruby, Tcl và thậm chí cả Java.

Chúng ta cũng biết rằng có Máy ảo Java (JVM) là một môi trường thực thi độc lập với nền tảng chuyển đổi mã bytecode của Java thành ngôn ngữ máy và thực thi nó. Nếu bạn hiểu khái niệm này thì bạn sẽ hiểu Parrot.

Parrotlà một máy ảo được thiết kế để biên dịch và thực thi mã bytecode cho các ngôn ngữ thông dịch một cách hiệu quả. Parrot là mục tiêu cho trình biên dịch Perl 6 cuối cùng và được sử dụng làm chương trình phụ trợ cho Pugs, cũng như nhiều ngôn ngữ khác như Tcl, Ruby, Python, v.v.

Parrot đã được viết bằng ngôn ngữ phổ biến nhất "C".

Trước khi bắt đầu, hãy tải xuống một bản sao mới nhất của Parrot và cài đặt nó trên máy của chúng tôi.

Liên kết tải xuống Parrot có sẵn trong Parrot CVS Snapshot . Tải xuống phiên bản Parrot mới nhất và để cài đặt nó, hãy làm theo các bước sau:

  • Giải nén và gỡ bỏ tệp đã tải xuống.

  • Đảm bảo rằng bạn đã cài đặt Perl 5 trên máy tính của mình.

  • Bây giờ làm như sau:

% cd parrot
% perl Configure.pl
Parrot Configure
Copyright (C) 2001 Yet Another Society
Since you're running this script, you obviously have
Perl 5 -- I'll be pulling some defaults from its configuration.
...
  • Sau đó, bạn sẽ được hỏi một loạt câu hỏi về cấu hình cục bộ của mình; bạn hầu như luôn có thể nhấn return / enter cho mỗi cái.

  • Cuối cùng, bạn sẽ được yêu cầu nhập - tạo test_prog và Parrot sẽ xây dựng thành công trình thông dịch thử nghiệm.

  • Bây giờ bạn nên chạy một số thử nghiệm; vì vậy hãy nhập 'thực hiện kiểm tra' và bạn sẽ thấy một bản đọc như sau:

perl t/harness
t/op/basic.....ok,1/2 skipped:label constants unimplemented in
assembler
t/op/string....ok, 1/4 skipped:  I'm unable to write it!
All tests successful, 2 subtests skipped.
Files=2, Tests=6,......

Vào thời điểm bạn đọc nó, có thể có nhiều bài kiểm tra hơn và một số bài kiểm tra đã bỏ qua có thể không bỏ qua, nhưng hãy đảm bảo rằng không bài kiểm tra nào bị trượt!

Sau khi bạn đã cài đặt tệp thực thi vẹt, bạn có thể xem các loại ví dụ khác nhau được đưa ra trong phần 'Ví dụ' về Parrot . Ngoài ra, bạn có thể xem thư mục ví dụ trong kho lưu trữ vẹt.

Parrot hiện có thể chấp nhận các hướng dẫn để thực hiện ở bốn dạng. PIR (Biểu diễn trung gian của con vẹt) được thiết kế để được viết bởi con người và được tạo bởi trình biên dịch. Nó ẩn đi một số chi tiết cấp thấp, chẳng hạn như cách các tham số được truyền cho các hàm.

PASM (Parrot Assembly) là một cấp độ thấp hơn PIR - nó vẫn có thể đọc / ghi được và có thể được tạo bởi trình biên dịch, nhưng tác giả phải quan tâm đến các chi tiết như gọi các quy ước và cấp phát thanh ghi. PAST (Cây cú pháp trừu tượng của Parrot) cho phép Parrot chấp nhận đầu vào kiểu cây cú pháp trừu tượng - hữu ích cho những trình biên dịch viết.

Tất cả các dạng đầu vào trên đều được tự động chuyển đổi bên trong Parrot sang PBC (Parrot Bytecode). Điều này giống như mã máy, nhưng được hiểu bởi trình thông dịch Parrot.

Nó không nhằm mục đích con người có thể đọc được hoặc con người có thể ghi được, nhưng không giống như các biểu mẫu khác, việc thực thi biểu mẫu có thể bắt đầu ngay lập tức mà không cần giai đoạn lắp ráp. Parrot bytecode độc ​​lập với nền tảng.

Bộ hướng dẫn

Tập lệnh Parrot bao gồm các toán tử số học và logic, so sánh và rẽ nhánh / nhảy (để thực hiện các vòng lặp, cấu trúc if ... then, v.v.), tìm và lưu trữ các biến toàn cục và từ vựng, làm việc với các lớp và đối tượng, gọi các chương trình con và phương thức cùng với các tham số, I / O, luồng và hơn thế nữa.

Giống như Máy ảo Java, Parrot cũng giúp bạn không phải lo lắng về việc giảm cấp phát bộ nhớ.

  • Parrot cung cấp dịch vụ thu gom rác thải.

  • Các chương trình vẹt không cần giải phóng bộ nhớ một cách rõ ràng.

  • Bộ nhớ được cấp phát sẽ được giải phóng khi không còn được sử dụng tức là không còn được tham chiếu.

  • Parrot Garbage Collector chạy định kỳ để xử lý bộ nhớ không mong muốn.

CPU Parrot có bốn kiểu dữ liệu cơ bản:

  • IV

    Một kiểu số nguyên; đảm bảo đủ rộng để chứa một con trỏ.

  • NV

    Một loại dấu phẩy động độc lập với kiến ​​trúc.

  • STRING

    Loại chuỗi trừu tượng, không phụ thuộc vào mã hóa.

  • PMC

    Một vô hướng.

Ba loại đầu tiên tự giải thích khá nhiều; loại cuối cùng - Parrot Magic Cookies, hơi khó hiểu hơn.

PMC là gì?

PMC là viết tắt của Parrot Magic Cookie. PMC đại diện cho bất kỳ cấu trúc hoặc kiểu dữ liệu phức tạp nào, bao gồm các kiểu dữ liệu tổng hợp (mảng, bảng băm, v.v.). PMC có thể triển khai hành vi của riêng mình đối với các phép toán số học, logic và chuỗi được thực hiện trên nó, cho phép đưa ra hành vi theo ngôn ngữ cụ thể. PMC có thể được tích hợp sẵn để Parrot thực thi hoặc được tải động khi cần.

Máy ảo Perl 5 hiện tại là một máy xếp chồng. Nó giao tiếp các giá trị giữa các hoạt động bằng cách giữ chúng trên một ngăn xếp. Các phép toán tải các giá trị vào ngăn xếp, làm bất cứ điều gì họ cần làm và đưa kết quả trở lại ngăn xếp. Điều này rất dễ thực hiện, nhưng nó chậm.

Để cộng hai số với nhau, bạn cần thực hiện ba lần đẩy ngăn xếp và hai lần bật ngăn xếp. Tệ hơn nữa, ngăn xếp phải phát triển trong thời gian chạy, và điều đó có nghĩa là cấp phát bộ nhớ ngay khi bạn không muốn cấp phát nó.

Vì vậy, Parrot sẽ phá vỡ truyền thống đã có đối với máy ảo và sử dụng kiến ​​trúc thanh ghi, gần giống với kiến ​​trúc của một CPU phần cứng thực. Điều này có một lợi thế khác. Chúng tôi có thể sử dụng tất cả các tài liệu hiện có về cách viết trình biên dịch và trình tối ưu hóa cho CPU dựa trên thanh ghi cho CPU phần mềm của chúng tôi!

Parrot có các thanh ghi chuyên dụng cho mỗi loại: 32 thanh ghi IV, 32 thanh ghi NV, 32 thanh ghi chuỗi và 32 thanh ghi PMC. Trong trình hợp dịch Parrot, chúng được đặt tên lần lượt là I1 ... I32, N1 ... N32, S1 ... S32, P1 ... P32.

Bây giờ chúng ta hãy xem xét một số trình lắp ráp. Chúng ta có thể đặt các thanh ghi này bằng toán tử set:

set I1, 10
	set N1, 3.1415
	set S1, "Hello, Parrot"

Tất cả các hoạt động của Parrot đều có cùng một định dạng: tên của toán tử, thanh ghi đích và sau đó là các toán hạng.

Có nhiều thao tác bạn có thể thực hiện. Ví dụ, chúng ta có thể in ra nội dung của một thanh ghi hoặc một hằng số:

set I1, 10
print "The contents of register I1 is: "
print I1
print "\n"

Các hướng dẫn trên sẽ dẫn đến Nội dung của thanh ghi I1 là: 10

Chúng ta có thể thực hiện các phép toán trên thanh ghi:

# Add the contents of I2 to the contents of I1
add I1, I1, I2
# Multiply I2 by I4 and store in I3
mul I3, I2, I4
# Increment I1 by one
inc I1
# Decrement N3 by 1.5
dec N3, 1.5

Chúng tôi thậm chí có thể thực hiện một số thao tác chuỗi đơn giản:

set S1, "fish"
set S2, "bone"
concat S1, S2       # S1 is now "fishbone"
set S3, "w"
substr S4, S1, 1, 7
concat S3, S4       # S3 is now "wishbone"
length I1, S3       # I1 is now 8

Code hơi nhàm chán nếu không có điều khiển luồng; Đối với người mới bắt đầu, Parrot biết về phân nhánh và nhãn. Chi nhánh op tương đương với goto của Perl:

branch TERRY
JOHN:    print "fjords\n"
         branch END
MICHAEL: print " pining"
         branch GRAHAM
TERRY:   print "It's"
         branch MICHAEL
GRAHAM:  print " for the "
         branch JOHN
END:     end

Nó cũng có thể thực hiện các bài kiểm tra đơn giản để xem liệu một thanh ghi có chứa giá trị đúng hay không:

set I1, 12
         set I2, 5
         mod I3, I2, I2
         if I3, REMAIND, DIVISOR
REMAIND: print "5 divides 12 with remainder "
         print I3
         branch DONE
DIVISOR: print "5 is an integer divisor of 12"
DONE:    print "\n"
         end

Đây là những gì sẽ giống như trong Perl, để so sánh:

$i1 = 12;
    $i2 = 5;
    $i3 = $i1 % $i2;
    if ($i3) {
      print "5 divides 12 with remainder ";
      print $i3;
    } else {
      print "5 is an integer divisor of 12";
    }
    print "\n";
    exit;

Nhà điều hành con vẹt

Chúng tôi có đầy đủ các bộ so sánh số: eq, ne, lt, gt, le và ge. Lưu ý rằng bạn không thể sử dụng các toán tử này trên các đối số của các kiểu khác nhau; bạn thậm chí có thể cần thêm hậu tố _i hoặc _n vào op, để cho nó biết bạn đang sử dụng kiểu đối số nào, mặc dù trình hợp dịch phải phân tích điều này cho bạn, vào thời điểm bạn đọc nó.

Lập trình vẹt tương tự như lập trình hợp ngữ và bạn có cơ hội làm việc ở trình độ thấp hơn. Dưới đây là danh sách các ví dụ lập trình để giúp bạn biết các khía cạnh khác nhau của Lập trình vẹt.

  • Chào thế giới cổ điển!
  • Sử dụng sổ đăng ký
  • Tổng bình phương
  • Số Fibonacci
  • Tính giai thừa
  • Biên dịch sang PBC
  • PIR so với PASM

Chào thế giới cổ điển!

Tạo một tệp có tên hello.pir chứa mã sau:

.sub _main
      print "Hello world!\n"
      end
  .end

Sau đó chạy nó bằng cách gõ:

parrot hello.pir

Như mong đợi, nó sẽ hiển thị dòng chữ 'Hello world!' trên bảng điều khiển, theo sau là một dòng mới (do \ n).

Trong ví dụ trên, '.sub _main' nói rằng các hướng dẫn theo sau tạo nên một chương trình con có tên '_main', cho đến khi gặp phải '.end'. Dòng thứ hai chứa hướng dẫn in. Trong trường hợp này, chúng tôi đang gọi biến thể của lệnh chấp nhận một chuỗi không đổi. Trình lắp ráp sẽ đảm nhận việc quyết định biến thể của hướng dẫn sẽ sử dụng cho chúng tôi. Dòng thứ ba chứa lệnh 'end', lệnh này khiến trình thông dịch kết thúc.

Sử dụng sổ đăng ký

Chúng ta có thể sửa đổi hello.pir để lưu trữ chuỗi Hello world! \ N trước tiên trong một thanh ghi và sau đó sử dụng thanh ghi đó với lệnh in.

.sub _main
      set S1, "Hello world!\n"
      print S1
      end
  .end

Ở đây chúng tôi đã nêu chính xác đăng ký nào để sử dụng. Tuy nhiên, bằng cách thay thế S1 bằng $ S1, chúng ta có thể ủy quyền lựa chọn đăng ký nào để sử dụng cho Parrot. Cũng có thể sử dụng ký hiệu = thay vì viết lệnh thiết lập.

.sub _main
      $S0 = "Hello world!\n"
      print $S0
      end
  .end

Để làm cho PIR dễ đọc hơn, có thể sử dụng các thanh ghi đã đặt tên. Sau đó chúng được ánh xạ tới các thanh ghi được đánh số thực.

.sub _main
      .local string hello
      hello = "Hello world!\n"
      print hello
      end
  .end

Chỉ thị '.local' chỉ ra rằng thanh ghi được đặt tên chỉ cần thiết bên trong đơn vị biên dịch hiện tại (nghĩa là giữa .sub và .end). Theo sau '.local' là một kiểu. Đây có thể là int (đối với thanh ghi I), float (đối với thanh ghi N), chuỗi (đối với thanh ghi S), pmc (đối với thanh ghi P) hoặc tên của một loại PMC.

Tổng bình phương

Ví dụ này giới thiệu thêm một số hướng dẫn và cú pháp PIR. Các dòng bắt đầu bằng dấu # là nhận xét.

.sub _main
      # State the number of squares to sum.
      .local int maxnum
      maxnum = 10

      # Some named registers we'll use. 
      # Note how we can declare many
      # registers of the same type on one line.
      .local int i, total, temp
      total = 0

      # Loop to do the sum.
      i = 1
  loop:
      temp = i * i
      total += temp
      inc i
      if i <= maxnum goto loop

      # Output result.
      print "The sum of the first "
      print maxnum
      print " squares is "
      print total
      print ".\n"
      end
  .end

PIR cung cấp một chút cú pháp khiến nó trông cao cấp hơn so với lắp ráp. Ví dụ:

temp = i * i

Chỉ là một cách viết khác của assembly-ish:

mul temp, i, i

Và:

if i <= maxnum goto loop

Giống như:

le i, maxnum, loop

Và:

total += temp

Giống như:

add total, temp

Theo quy luật, bất cứ khi nào một lệnh Parrot sửa đổi nội dung của một thanh ghi, đó sẽ là thanh ghi đầu tiên khi viết lệnh ở dạng hợp ngữ.

Như thường lệ trong hợp ngữ, các vòng lặp và vùng chọn được thực hiện theo các câu lệnh và nhãn nhánh có điều kiện, như được hiển thị ở trên. Lập trình hợp ngữ là một nơi mà sử dụng goto không phải là một hình thức tồi!

Số Fibonacci

Dãy Fibonacci được định nghĩa như thế này: lấy hai số, 1 và 1. Sau đó cộng lại liên tục hai số cuối cùng với nhau để tạo thành dãy tiếp theo: 1, 1, 2, 3, 5, 8, 13, v.v. . Số Fibonacci fib (n) là số thứ n trong chuỗi. Đây là một chương trình hợp ngữ Parrot đơn giản để tìm 20 số Fibonacci đầu tiên:

# Some simple code to print some Fibonacci numbers

        print   "The first 20 fibonacci numbers are:\n"
        set     I1, 0
        set     I2, 20
        set     I3, 1
        set     I4, 1
REDO:   eq      I1, I2, DONE, NEXT
NEXT:   set     I5, I4
        add     I4, I3, I4
        set     I3, I5
        print   I3
        print   "\n"
        inc     I1
        branch  REDO
DONE:   end

Đây là mã tương đương trong Perl:

print "The first 20 fibonacci numbers are:\n";
        my $i = 0;
        my $target = 20;
        my $a = 1;
        my $b = 1;
        until ($i == $target) {
           my $num = $b;
           $b += $a;
           $a = $num;
           print $a,"\n";
           $i++;
        }

NOTE:Như một điểm đáng quan tâm, một trong những cách ngắn nhất và chắc chắn đẹp nhất để in ra một chuỗi Fibonacci ở Perl là perl -le '$ b = 1; in $ a + = $ b trong khi in $ b + = $ a '.

Giai thừa tính toán đệ quy

Trong ví dụ này, chúng ta định nghĩa một hàm giai thừa và gọi một cách đệ quy nó để tính toán giai thừa.

.sub _fact
      # Get input parameter.
      .param int n

      # return (n > 1 ? n * _fact(n - 1) : 1)
      .local int result

      if n > 1 goto recurse
      result = 1
      goto return

  recurse:
      $I0 = n - 1
      result = _fact($I0)
      result *= n

  return:
      .return (result)
  .end


  .sub _main :main
      .local int f, i

      # We'll do factorial 0 to 10.
      i = 0
  loop:
      f = _fact(i)

      print "Factorial of "
      print i
      print " is "
      print f
      print ".\n"

      inc i
      if i <= 10 goto loop

      # That's it.
      end
  .end

Trước tiên hãy nhìn vào sub _fact. Một điểm đã được chú ý trước đó là tại sao tên của các chương trình con, tất cả đều bắt đầu bằng dấu gạch dưới! Điều này được thực hiện đơn giản như một cách để cho thấy rằng nhãn là toàn cục thay vì phạm vi đến một chương trình con cụ thể. Điều này rất quan trọng vì nhãn sau đó được hiển thị cho các chương trình con khác.

Dòng đầu tiên, .param int n, chỉ định rằng chương trình con này nhận một tham số số nguyên và chúng tôi muốn tham chiếu đến sổ đăng ký mà nó được chuyển vào bằng tên n cho phần còn lại của chương trình con.

Phần lớn những gì sau đây đã được nhìn thấy trong các ví dụ trước, ngoại trừ việc đọc dòng:

result = _fact($I0)

Dòng PIR đơn này thực sự đại diện cho khá nhiều dòng PASM. Đầu tiên, giá trị trong thanh ghi $ I0 được chuyển vào thanh ghi thích hợp để nó được hàm _fact nhận như một tham số nguyên. Các thanh ghi liên quan đến việc gọi khác sau đó được thiết lập, theo sau là _fact được gọi. Sau đó, khi _fact trả về, giá trị do _fact trả về sẽ được đưa vào thanh ghi với kết quả tên.

Ngay trước .end của _fact phụ, một chỉ thị .return được sử dụng để đảm bảo giá trị được giữ trong thanh ghi; kết quả được đặt tên được đặt vào đúng thanh ghi để nó được xem như một giá trị trả về bởi mã gọi sub.

Lệnh gọi tới _fact trong chính hoạt động giống như cách gọi đệ quy tới _fact bên trong chính _fact phụ. Một chút duy nhất còn lại của cú pháp mới là: main, được viết sau .sub _main. Theo mặc định, PIR giả định rằng việc thực thi bắt đầu với con đầu tiên trong tệp. Hành vi này có thể được thay đổi bằng cách đánh dấu phụ bắt đầu bằng: main.

Biên dịch sang PBC

Để biên dịch PIR sang bytecode, hãy sử dụng cờ -o và chỉ định tệp đầu ra có phần mở rộng .pbc.

parrot -o factorial.pbc factorial.pir

PIR so với PASM

PIR có thể được chuyển thành PASM bằng cách chạy:

parrot -o hello.pasm hello.pir

PASM cho ví dụ cuối cùng trông như thế này:

_main:
      set S30, "Hello world!\n"
      print S30
      end

PASM không xử lý cấp phát thanh ghi hoặc cung cấp hỗ trợ cho các thanh ghi được đặt tên. Nó cũng không có các chỉ thị .sub và .end, thay vào đó hãy thay thế chúng bằng một nhãn ở đầu hướng dẫn.