Khi đối mặt với một tình huống tương tự - sự cần thiết phải xử lý các biểu một dòng ngắn - Tôi đã viết một phân tích cú pháp. Các biểu thức là logic boolean, của biểu mẫu
n1 = y and n2 > z
n2 != x or (n3 > y and n4 = z)
v.v. Trong tiếng Anh, bạn có thể nói rằng có các nguyên tử được nối với AND và OR, và mỗi nguyên tử có ba phần tử - một thuộc tính bên trái, một toán tử và một giá trị. Bởi vì nó đã rất succint tôi nghĩ rằng việc phân tích cú pháp được dễ dàng hơn. Tập hợp các thuộc tính có thể được biết và giới hạn (ví dụ: tên, kích thước, thời gian).Các toán tử thay đổi theo thuộc tính: các thuộc tính khác nhau lấy các toán tử khác nhau. Và phạm vi và định dạng của các giá trị có thể khác nhau tùy theo thuộc tính.
Để phân tích cú pháp, tôi chia chuỗi trên khoảng trắng bằng cách sử dụng String.Split(). Sau này tôi nhận ra rằng trước khi Split(), tôi cần phải bình thường hóa chuỗi đầu vào - chèn khoảng trắng trước và sau khi parens. Tôi đã làm điều đó với một regex.Replace().
Đầu ra của phần tách là một mảng mã thông báo. Sau đó phân tích cú pháp xảy ra trong một vòng lặp lớn với một công tắc trên giá trị thuộc tính bên trái. Với mỗi vòng lặp của vòng lặp, tôi đã được thiết lập để slurp trong một nhóm các thẻ. Nếu mã thông báo đầu tiên là một paren mở, thì nhóm chỉ dài một mã thông báo: chính là paren. Đối với các mã thông báo là các tên nổi tiếng - các giá trị thuộc tính của tôi - trình phân tích cú pháp phải ẩn trong một nhóm gồm 3 thẻ, mỗi mã cho tên, toán tử và giá trị. Nếu tại bất kỳ điểm nào không có đủ mã thông báo, trình phân tích cú pháp sẽ đưa ra một ngoại lệ. Dựa trên luồng mã thông báo, trạng thái phân tích cú pháp sẽ thay đổi. Một kết hợp (AND, OR, XOR) có nghĩa là đẩy nguyên tử trước vào một chồng, và khi nguyên tử tiếp theo được hoàn thành, tôi sẽ bật nguyên tử trước đó và nối hai nguyên tử đó thành một nguyên tử phức hợp. Và cứ thế. Việc quản lý nhà nước xảy ra ở cuối mỗi vòng lặp của trình phân tích cú pháp.
Atom current;
for (int i=0; i < tokens.Length; i++)
{
switch (tokens[i].ToLower())
{
case "name":
if (tokens.Length <= i + 2)
throw new ArgumentException();
Comparison o = (Comparison) EnumUtil.Parse(typeof(Comparison), tokens[i+1]);
current = new NameAtom { Operator = o, Value = tokens[i+2] };
i+=2;
stateStack.Push(ParseState.AtomDone);
break;
case "and":
case "or":
if (tokens.Length <= i + 3)
throw new ArgumentException();
pendingConjunction = (LogicalConjunction)Enum.Parse(typeof(LogicalConjunction), tokens[i].ToUpper());
current = new CompoundAtom { Left = current, Right = null, Conjunction = pendingConjunction };
atomStack.Push(current);
break;
case "(":
state = stateStack.Peek();
if (state != ParseState.Start && state != ParseState.ConjunctionPending && state != ParseState.OpenParen)
throw new ArgumentException();
if (tokens.Length <= i + 4)
throw new ArgumentException();
stateStack.Push(ParseState.OpenParen);
break;
case ")":
state = stateStack.Pop();
if (stateStack.Peek() != ParseState.OpenParen)
throw new ArgumentException();
stateStack.Pop();
stateStack.Push(ParseState.AtomDone);
break;
// more like that...
case "":
// do nothing in the case of whitespace
break;
default:
throw new ArgumentException(tokens[i]);
}
// insert housekeeping for parse states here
}
Đơn giản hóa, chỉ một chút. Nhưng ý tưởng là mỗi tuyên bố trường hợp khá đơn giản. Thật dễ dàng để phân tích cú pháp trong một đơn vị nguyên tử của biểu thức. Phần khó khăn đã được tham gia tất cả cùng nhau một cách thích hợp.
Bí quyết đó được thực hiện trong phần bảo trì, ở cuối mỗi vòng lặp slurp, sử dụng ngăn xếp trạng thái và ngăn xếp nguyên tử. Các công cụ khác nhau có thể xảy ra theo trạng thái phân tích cú pháp. Như tôi đã nói, trong mỗi tuyên bố trường hợp, trạng thái phân tích cú pháp có thể thay đổi, với trạng thái trước đó bị đẩy lên một chồng. Sau đó, ở cuối báo cáo chuyển đổi, nếu tiểu bang cho biết tôi vừa hoàn thành phân tích cú pháp một nguyên tử và có một kết hợp đang chờ xử lý, tôi sẽ chuyển nguyên tử vừa được phân tích cú pháp vào CompoundAtom. Mã trông giống như sau:
state = stateStack.Peek();
if (state == ParseState.AtomDone)
{
stateStack.Pop();
if (stateStack.Peek() == ParseState.ConjunctionPending)
{
while (stateStack.Peek() == ParseState.ConjunctionPending)
{
var cc = critStack.Pop() as CompoundAtom;
cc.Right = current;
current = cc; // mark the parent as current (walk up the tree)
stateStack.Pop(); // the conjunction is no longer pending
state = stateStack.Pop();
if (state != ParseState.AtomDone)
throw new ArgumentException();
}
}
else stateStack.Push(ParseState.AtomDone);
}
Một chút ma thuật khác là EnumUtil.Parse. Điều đó cho phép tôi phân tích cú pháp những thứ như "<" thành giá trị enum. Giả sử bạn xác định sự đếm của bạn như thế này:
internal enum Operator
{
[Description(">")] GreaterThan,
[Description(">=")] GreaterThanOrEqualTo,
[Description("<")] LesserThan,
[Description("<=")] LesserThanOrEqualTo,
[Description("=")] EqualTo,
[Description("!=")] NotEqualTo
}
thường Enum.Parse tìm kiếm tên mang tính biểu tượng của giá trị enum, và < không phải là một tên mang tính biểu tượng hợp lệ. EnumUtil.Parse() tìm kiếm điều trong phần mô tả. Mã trông giống như sau:
internal sealed class EnumUtil
{
/// <summary>
/// Returns the value of the DescriptionAttribute if the specified Enum value has one.
/// If not, returns the ToString() representation of the Enum value.
/// </summary>
/// <param name="value">The Enum to get the description for</param>
/// <returns></returns>
internal static string GetDescription(System.Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivilant enumerated object.
/// Note: Utilised the DescriptionAttribute for values that use it.
/// </summary>
/// <param name="enumType">The System.Type of the enumeration.</param>
/// <param name="value">A string containing the name or value to convert.</param>
/// <returns></returns>
internal static object Parse(Type enumType, string value)
{
return Parse(enumType, value, false);
}
/// <summary>
/// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivilant enumerated object.
/// A parameter specified whether the operation is case-sensitive.
/// Note: Utilised the DescriptionAttribute for values that use it.
/// </summary>
/// <param name="enumType">The System.Type of the enumeration.</param>
/// <param name="value">A string containing the name or value to convert.</param>
/// <param name="ignoreCase">Whether the operation is case-sensitive or not.</param>
/// <returns></returns>
internal static object Parse(Type enumType, string stringValue, bool ignoreCase)
{
if (ignoreCase)
stringValue = stringValue.ToLower();
foreach (System.Enum enumVal in System.Enum.GetValues(enumType))
{
string description = GetDescription(enumVal);
if (ignoreCase)
description = description.ToLower();
if (description == stringValue)
return enumVal;
}
return System.Enum.Parse(enumType, stringValue, ignoreCase);
}
}
Tôi nhận được điều EnumUtil.Parse() từ một nơi khác. Có lẽ là ở đây?
Bạn có muốn biên dịch mã cho CLR không? Điều đó có vẻ là một mục tiêu hợp lý cho một DSL. – MSalters
Đây là một nhiệm vụ khó khăn (để giải thích các loại chức năng đó). Nhắm mục tiêu CLR không nhất thiết phải làm cho nó dễ dàng hơn ... –
bản sao có thể có của [Học viết một trình biên dịch] (http://stackoverflow.com/questions/1669/learning-to-write-a-compiler) –