ช่วงนี้เพิ่งได้เรียนรู้ภาษาดาร์ต (dart) และก็พบว่าเป็นภาษาที่น่าสนใจดี เขียนแล้วก็สนุกดี มีความสามารถอะไรหลายอย่างที่ช่วยให้สามารถเขียนได้ง่าย
แต่ว่าน่าเสียดายว่าภาษานี้ไม่สามารถใช้ความสามารถ sprintf คือการแปลงข้อมูลสายอักขระโดยใช้
%d
%f
%s
แบบในภาษา C หรือไพธอน หรือรูบีได้
ภาษาจาวาสคริปต์เองก็ไม่มีความสามารถตรงนี้ ทำให้เราต้องทำการเขียนใส่เพิ่มเข้ามาใหม่เอง ซึ่งได้เคยเขียนถึงและลงโค้ดเอาไว้
>>
การใส่ความสามารถเพิ่มเติมให้สายอักขระใน javascript สามารถใช้ sprintf ได้ ครั้งนี้เราก็เลยขอมาลองทำแบบเดียวกันกับภาษาดาร์ตบ้าง โดยในภาษาดาร์ตเองก็สามารถเพิ่มความสามารถให้กับออบเจ็กต์พื้นฐานที่มีอยู่เดิมได้ เช่นเดียวกับจาวาสคริปต์ อีกทั้งในภาษาดาร์ตเราสามารถนิยามการใช้ตัวดำเนินการของออบเจ็กต์ขึ้นเองได้ ในขณะที่จาวาสคริตป์ทำแบบนั้นไม่ได้
ดังนั้นเราสามารถเพิ่มความสามารถให้กับสายอักขระ ให้เวลาตามด้วย
%
แล้วจะเกิดการแปลงขึ้นมา
ลองสร้างไฟล์ sprintf.dart ขึ้นมาดังนี้
extension Sprintf on String {
// เขียนทับการดำเนินการด้วย % ให้กับออบเจ็กต์คลาส String
String operator %(a) {
// ถ้าไม่ใช่ลิสต์ก็ให้ทำเป็นลิสต์ไว้
if (a is! List) {
a = [a];
}
String e;
int i = -1;
return this.replaceAllMapped(
RegExp(r'(%+)(#+)?(\+)?(0)?(\d+|\*)?(\.(\d+))?([bBcdeEfFgGiosuxX])'),
(Match m) {
/* คำอธิบายตัวแปร
m[1]: กลุ่มเครื่องหมาย % ด้านหน้า
m[2]: กลุ่มเครื่องหมาย # ด้านหน้า ถ้ามี (มีผลกับ b, o, x, X)
m[3]: เครื่องหมาย + ถ้ามี
m[4]: ถ้ามีเลข 0 ให้เติมด้วย 0 ถ้าไม่มีให้เติมด้วยช่องว่าง
m[5]: ส่วนที่บอกว่าจะเติมสายอักขระให้ยาวอย่างต่ำกี่ตัว
m[6]: ส่วนที่บอกว่าจะเอาเลขทศนิยมกี่ตำแหน่ง
m[7]: จำนวนตำแหน่งทศนิยม
m[8]: ตัวกำหนดรูปแบบการเขียน
*/
// เริ่มจากจัดการกับเครื่องหมาย % ด้านหน้า
if (m[1]!.length % 2 == 0) {
// ถ้าจำนวนของ % เป็นเลขคู่ แค่ลดจำนวน % เหลือครึ่ง
return m[0]!.replaceAll('%%', '%');
}
// ถ้าจำนวนของ % เป็นเลขคี่ จึงดำเนินการแทนค่า
i++;
// ขึ้นต้นด้วย % ที่นำหน้า ถ้ามี แต่เหลือครึ่งเดียว
String s = m[1]!.substring(0, (m[1]!.length / 2).toInt());
String k = '';
// ใส่เรื่องหมาย + ถ้าระบุ + และค่าเป็นจำนวนบวกและไม่ใช่สายอักขระ
if (m[3] != null && !'cs'.contains(m[8]!) && a[i] > 0) {
k = '+';
}
// จำนวนที่จะเติม 0 หรือช่องว่าง
int p = 0;
// ถ้าใส่เป็น * มาให้ดึงตัวเลขข้างหน้ามาใช้แล้วแปลงค่าตัวถัดไปแทน
if (m[5] == '*') {
p = a[i];
i++; // เลื่อนไปอีกตำแหน่ง
} // ถ้ามีโดยใส่มาเป็นตัวเลขให้ใช้ตัวเลขนั้น
else if (m[5] != null) {
p = int.parse(m[5]!);
}
// แปลงข้อมูลตามชนิดที่ใส่มา
// เลขฐาน 2
if ('bB'.contains(m[8]!)) {
// ถ้ามี #
if (m[2] != null) {
if (m[8] == 'B')
k += '0B';
else
k += '0b';
}
k += a[i].toRadixString(2);
} // โค้ดตัวหนังสือ
else if (m[8] == 'c') {
k += String.fromCharCode((a[i]));
} // เลขจำนวนเต็ม
else if ('diu'.contains(m[8]!)) {
k += a[i].toInt().toString();
} // เลขทศนิยมในรูป E
else if ('eE'.contains(m[8]!)) {
if (m[6] != null) // ถ้าระบุจำนวนตำแหน่งทศนิยม
e = a[i].toStringAsExponential(int.parse(m[7]!));
else // ถ้าไม่ระบุจำนวนตำแหน่งทศนิยมก็ให้เป็น 6
e = a[i].toStringAsExponential(6);
// กรณีที่เลขหลัง +- มีหลักเดียว เติม 0 อีกตัว
if ('+-'.contains(e[e.length - 2]))
e = e.replaceAll('e+', 'e+0').replaceAll('e-', 'e-0');
if (m[8] == 'E') // กรณี E เป็นตัวพิมพ์ใหญ่
e = e.replaceAll('e', 'E');
k += e;
} // เลขทศนิยม
else if ('fF'.contains(m[8]!)) {
if (m[6] != null) // ถ้าระบุจำนวนตำแหน่งทศนิยม
k += a[i].toStringAsFixed(int.parse((m[7]!)));
else // ถ้าไม่ระบุจำนวนตำแหน่งทศนิยมก็ให้เป็น 6
k += a[i].toStringAsFixed(6);
} // เลขที่ปรับรูปตามสมควร
else if ('gG'.contains(m[8]!)) {
if (a[i] >= 0.0001 && a[i] < 1000000) // กรณีเลขไม่มากหรือน้อยเกินไป
k += a[i].toString();
else {
// กรณีเลขมากหรือน้อยจนถึงระดับหนึ่ง
e = a[i].toStringAsExponential();
// กรณีที่เลขหลัง +- มีหลักเดียว เติม 0 อีกตัว
if ('+-'.contains(e[e.length - 2]))
e = e.replaceAll('e+', 'e+0').replaceAll('e-', 'e-0');
if (m[8] == 'G') // กรณี G เป็นตัวพิมพ์ใหญ่
e = e.replaceAll('e', 'E');
k += e;
}
} // เลขฐาน 8
else if (m[8] == 'o') {
if (m[2] != null) k += '0o'; // ถ้ามี #
k += a[i].toRadixString(8);
} // เลขฐาน 16 โดยใช้ตัวพิมพ์เล็ก
else if (m[8] == 'x') {
if (m[2] != null) k += '0x'; // ถ้ามี #
k += a[i].toRadixString(16);
} // เลขฐาน 16 โดยใช้ตัวพิมพ์ใหญ่
else if (m[8] == 'X') {
if (m[2] != null) k += '0X'; // ถ้ามี #
k += a[i].toRadixString(16).toUpperCase();
} // สายอักขระธรรมดา
else {
k += a[i].toString();
}
// ใส่ 0 หรือช่องว่าง
String _0 = '';
if (p != 0) {
int n0 = p - k.length;
while (n0 > 0) {
n0--;
// ใส่ 0 ถ้าระบุ 0 และไม่ใช่สายอักขระ
if (!'cs'.contains(m[8]!) != 's' && m[4] == '0')
_0 += '0';
// ใส่ช่องว่าง
else
_0 += ' ';
}
}
// ถ้าเลือกเติม 0 และมีเครื่องหมาย +- อยู่ให้แทรกไว้หลัง +-
if ('+-'.contains(k[0]) && _0[0] == '0')
return s + k[0] + _0 + k.substring(1);
else // ถ้าไม่มี +- ก็เติม 0 หรือช่องว่างไปเลย
return s + _0 + k;
},
);
}
}
จากนั้นแล้วก็สามารถหยิบมาใช้ได้โดย
import
เข้ามา
import 'sprintf.dart';
void main() {
String ss = '''%+04d
%+4d
%+3d
%%d
%%%%02d
%%%d
%8.3G
%9.3E
%#03o
%#05x
%+07X
%#b
%c
%+7s
%7G
%g
%0*d
%0*.2f''';
print(ss%[1, 1.1, -2, 3.1, 4.2, 0.0053, 9, 1965, 1965, 61, 100, 'ห', 0.02, 2e-5, 5, 1, 7, 1.11]);
}
ผลที่ได้
+001
+1
-2
%d
%%02d
%3
4.2
5.300E-03
0o11
0x7ad
+0007AD
0b111101
d
ห
0.02
2e-05
00001
0001.11
ลองเทียบกับที่เขียนด้วยจาวาสคริปต์แล้วจะเห็นว่าการเขียนคล้ายกันมาก เพราะว่าภาษาดาร์ตได้รับอิทธิพลจากจาวาสคริปต์มามาก และเดิมทีเป็นภาษาที่ถูกสร้างขึ้นมาเพื่อแทนที่จาวาสคริปต์ จึงดูแล้วน่าใช้กว่าจริงๆ
แต่ความยุ่งยากที่เพิ่มเข้ามาก็คือการที่ต้องมาพะวงเรื่องชนิดข้อมูล จำเป็นต้องประกาศตัวแปรก่อน และต้องระวังว่าค่าไหนเป็น
null
ได้หรือไม่ ตรงส่วนนี้จึงทำให้โค้ดต่างไปจากจาวาสคริปต์ เขียนยาวขึ้นหน่อย แต่ว่าการที่ระบบภาษาเป็นแบบนี้ก็เป็นอะไรที่สร้างความรัดกุมและทำให้เกิดข้อผิดพลาดได้ยากขึ้น
ที่สำคัญคือการที่สามารถนิยามการทำงานของตัวดำเนินการของออบเจ็กต์แต่ละคลาสได้ตามที่ต้องการ ทำให้เราสามารถเพิ่มความสามารถให้เขียนในลักษณะที่คล้ายกับภาษาไพธอนหรือรูบีได้ แบบนี้ทำอะไรได้คล่องตัวกว่า
ภาษาดาร์ตนั้นยังขาดความสามารถอะไรหลายๆอย่างที่ไพธอนหรือรูบีมีอยู่ ถ้าใครเคยใช้อยู่จนชินแล้วก็อาจต้องการให้มีในดาร์ตด้วย การที่สามารถเพิ่มความสามารถขึ้นมาได้เองนั้นสะดวกดี แน่นอนว่าไม่ใช่ว่าจำเป็นต้องเขียนขึ้นเอง แต่สามารถไปหาแพ็กเกจที่คนทำเอาไว้มาลงได้ด้วย