Assignment 3…

Mở đầu

Lúc viết bài viết này mình thực sư cũng không biết nên gọi nó là gì, nên cái tên còn để trống,

Vì đây là một bài viết cho nhiều người, mình đã hạn chế đưa “thuật toán” vào trong, về chuyện này các bạn độc lập suy nghĩ có lẽ sẽ tốt hơn. Nếu mà mình đưa ra “thuật toán” thì hậu quả sẽ khó lường. Và cũng đừng ai pm mình hỏi về chuyện này nhá!

Bài viết này chưa được hoàn thiện, nhưng gần deadline rồi, mình cố gắng viết những phần cơ bản để các bạn làm lấy điểm cơ bản, các phần sau sẽ hoàn thiện sau, nếu mình có thể

Bài viết này chỉ mang tính chất tham khảo, không yêu cầu, ràng buộc các bạn phải làm giống y như vậy. Với lại đây cũng chỉ là quan điểm của mình, chưa hẳn đã đúng.

Trước khi làm bài

Để kiểm tra chương trình, có thể chỉnh lại

def debug = false

thành

def debug = true

để kiểm tra xem thử chương trình mình có chạy đúng các visitor không?

Nhưng thực sự thì chắc cũng không cần thiết, đôi khi lại gây rối, khó nhìn nếu chưa quen, tốt nhất thì chỉ nên chủ động in ra những gì mình cần.

15 TODO

TODO 1, hoàn thành các công thức tính các toán tử nhị phân

được dùng trong trường hợp tính toán giá trị, gán cho HẰNG (constant),
các giá trị sau khi thực hiện sẽ được chuyển thành dạng chuỗi

mình sẽ phân tích code mẫu của thầy, các bạn có thể áp dụng tương tự để làm các toán tử khác

case "+" => {
  try {
    // kiểm tra xem thử leftValue có thể chuyển thành kiểu Int được không, nếu không được thì sẽ nhảy qua exception 
    Some(leftValue.toInt)

    // thực hiện đồng thời chuyển leftValue và rightValue thành kiểu Int, cộng 2 giá trị và chuyển về dạng chuỗi (để đồng nhất kiểu dữ liệu giữa các dạng toán tử)
    (leftValue.toInt + rightValue.toInt).toString
  } catch {
    // do đã được kiểm tra ở static check, nên toán tử + không phải là cộng kiểu float thì tất nhiên là kiểu Float
    // cho nên nếu khi không chuyển được thành Int sẽ nhảy vào đây, trả về phép cộng Float
    case _: NumberFormatException => (leftValue.toFloat + rightValue.toFloat).toString
  }
}

TODO 2, hiện thực khai báo biến,

mỗi lần khai báo biến thực hiện song song 2 công việc:
– xuất code jasmin
– lưu trữ symbol lại để sử dụng, kiểm tra sau này

if (debug) println(ast)
if (context.scope == SymEntry.GLOBAL) {
  em.printout(em.emitSTATICFIELD(ast.variable.name, changetype(ast.varType), false, null))
  val sym = new SymEntry(new Token(ast.variable.name), changetype(ast.varType),
null, context.scope, context.currentClass, false)
  context.symTable :+= sym
} else if(context.scope == SymEntry.CLASS){
  // trong trường hợp là thuộc tính của class thì sẽ hiện thực bằng kiểu field (không phải là static)
} else {
  // các trường hợp còn lại (tầm vực hàm, tầm vực khối lệnh (block), ), gợi ý dùng emitVAR
  // lưu ý, trong symEntry có một thuộc tính object có thể chứa tất cả kiểu dữ liệu, được dùng để lưu trữ dữ liệu cần thiết
  // theo mình thấy thì ở đây được dùng để lưu index của biến cục bộ.
  // cần chú ý index của .var trong code jasmin và index được lưu trữ trong symEntry phải giống nhau
}

TODO 3 hoàn tất khai báo hằng,

cũng gần tương tự như khai báo biến ở TODO 2 nhưng nhớ set isFinnal, isConst và value cho hằng

if (debug) println(ast)
val t1 = ast.const.visit(this, new Emitter(), context).asInstanceOf[BkoolType]
var value = eval(ast.const, context.symTable)
if (debug) println(value)

if (context.scope == SymEntry.GLOBAL || context.scope == SymEntry.CLASS) {

} else {

}

TODO 4 Khai báo tham số:

về khai báo tham số thì mình chưa rõ lắm 😦 , nhưng có lẽ cũng giống như khai báo biến

TODO 5 khai báo class khai báo class

Những điều cần lưu ý về hàm khởi tạo (constructor) (theo nghiên cứu các ngôn ngữ OOP khác):
– tất cả các hàm, thuộc tính của cha, tổ tiên nó đều được thừa kế ngoại trừ hàm khởi tạo thì không có thừa kế. (không cần quan tâm, thầy đã xử lý)
– nếu không khao báo hàm tạo, thì mặc định class đó sẽ có một hàm tạo không tham số (cái này thầy làm mẫu rồi)
– khi gọi hàm tạo của class con, nó sẽ gọi lại hàm tạo của class cha (hàm tạo không tham số)
– cấp phát các thuộc tính kiểu mảng của class ở đây.

TODO 6 hiện thực các toán tử nhị phân

Lưu ý, ở đây khác với TODO 1, TODO 1 được dùng để tính toán giá trị của dữ liệu tĩnh (static), còn TODO 6 được dùng để sinh code và thực thi lúc runtime.
Lưu ý: vì thầy đã visit các biểu thức left và right, nên nó đã được đặt trên stack, công việc của chúng ta chỉ là sinh ra toán tử tính toán và trả về kiểu thích hợp

Các hàm emit của toán tử, có yêu cầu kiểu dữ liệu đầu vào để trong đó chọn lệnh thích hợp, có thể dùng targetType của thầy tính toán sẵn (đọc lại khai báo của nó để biết thêm chi tiết)

TODO 7 hiện thực toán tử đơn phân

tương tự TODO 6

TODO 8 hiện thực toán tử new,

chỉ đơn giản là gọi hàm, chưa làm 😀 , nhưng làm giống phần code mẫu

if(func.size < 1){ 
  ast.exprs.foreach(_.visit(this, em, context));
  em.printout(em.emitINVOKESPECIAL(BKIT.GLOBAL_PACKAGE + "/" + nm + "/<init>", new BkoolMethodType(new BkoolProductType(null,null), BkoolType.VOIDTYPE))) 
} else {
  //TODO: 8. Complete the NewExpr
}

nhưng nhớ chú ý sửa lại phần BkoolMethodType, phần này các bạn tự nghiên cứu nhá 🙂

TODO 9 CallExpr, TODO 10 FieldAccess, TODO 11 ArrayCell mình cũng chưa làm nốt …

TODO 12 hiện thực mệnh đề if,

làm tới đây rồi, theo nhận định của mình thì phần này trở về sau đơn giản, dễ hiểu, đỡ ảo hơn các phần trên nhiều
hơn nữa trong slide của thầy cũng đã có hướng dẫn các bạn chịu khó đọc,
sau đây mình sẽ phân tích phần code trong slide “Code Generation” trang 40/66 của thầy nhá:

visitIfThenElseStmtAST {
  ast ast.e.visit(this,o)              // tính toán biểu thức điều kiện, đưa vào stack
  label1 = Frame.getNewLabel();        // tạo 1 label mới, sẽ được dùng làm label của mệnh đề else
  em.printout(em.emitIFFALSE(label1)); // in ra lệnh: nếu điều kiện sai thì nhảy tới label1 (tất là label của else)
  ast.s1.visit(this,o) {               // thực hiện mệnh đề then
    label2 = Frame.getNewLabel();      // tạo 1 label mới, sẽ được dùng là label thoát
    em.printout(em.emitGOTO(label2));  // nhảy tới label thoát
    em.printout(em.emitLABEL(label1)); // đánh dấu label của mệnh đề else
  }
  ast.s2.visit(this,o) {               // thực hiện mệnh đề else
    em.printout(em.emitLABEL(label2)); // đánh dấu label thoát
  }
}

một điều cần chú ý ở đây, mình có thể tạo label ở bất cứ chổ nào, miễn sao label được đánh dấu đúng chổ và các lệnh nhảy (goto) đúng chổ.

TODO 13, 14, 15

Các mệnh đề lặp này gần tương tự nhau, các bạn nhớ đối chiếu với các mô hình, lưu đồ ở slide 42/66 “Code Generation”
Những điều cần lưu ý ở đây:
– Trước khi vào vòng lặp các bạn dùng lệnh em.getFrame().enterLoop(); để tạo 2 continueLabel và breakLabel và đưa vào stack tương tự
lúc ra cũng nhớ exitLoop, để xoá 2 label này khỏi stack trả lại các label của vòng lặp bên ngoài.
– Các bạn phải sử đụng, đánh dấu nhãn Break và Continue đúng chổ
– Để lấy nhãn Continue dùng lệnh em.getFrame().getContinueLabel() , để lấy nhãn Break dùng lệnh em.getFrame().getBreakLabel()
Ngoài ra thì vòng lặp for gần tương tự như vòng while nhưng cần lưu ý khi for i:=1 to 10 thì tính cả số từ 1 đến 10 nhá (tính cả số 10)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s