문제

사용자가 Excel과 유사한 표현식을 작성할 수 있도록 해야하는 응용 프로그램이 있습니다.

(H1 + (D1 / C3)) * i8

그리고 더 복잡한 것들

if (h1 = 'true', d3 * .2, d3 * .5)

나는 정기적 인 표현으로 만 그렇게 많이 할 수 있습니다. 이 작업에 대한 올바른 접근 방식과 내가 배울 수있는 자원에 대한 제안은 대단히 감사 할 것입니다.

감사!

도움이 되었습니까?

해결책

다른 질문은 다음과 같은 힌트를 찾을 수 있습니다.

행운을 빕니다!

다른 팁

짧은 단일 선 표현을 처리해야 할 필요성이 비슷한 상황에 직면했을 때 나는 파서를 썼습니다. 표현은 형식의 부울 논리였습니다

n1 = y and n2 > z
n2 != x or (n3 > y and n4 = z) 

등등. 영어에서는 원자가 및 또는 또는 OR, 각 원자에는 왼쪽 속성, 연산자 및 값의 세 가지 요소가 있습니다. 너무 신속했기 때문에 구문 분석이 더 쉬웠다 고 생각합니다. 가능한 속성 세트는 알려져 있고 제한적입니다 (예 : 이름, 크기, 시간). 연산자는 속성에 따라 다릅니다. 다른 속성은 다른 연산자 세트를 취합니다. 그리고 가능한 값의 범위와 형식은 속성에 따라 다릅니다.

구문 분석하기 위해 String.split ()를 사용하여 공백에서 문자열을 분할합니다. 나중에 split () 전에 입력 문자열을 정규화해야한다는 것을 깨달았습니다. regex.replace ()로 그렇게했습니다.

분할의 출력은 토큰 배열입니다. 그런 다음 구문 분석은 왼쪽 속성 값에 스위치를 사용하여 루프를 위해 큰 경우에 발생합니다. 루프의 각각의 라운드마다, 나는 한 무리의 토큰으로 슬러프로 설정되었습니다. 첫 번째 토큰이 오픈 플라렌이라면, 그룹의 길이는 단 하나의 토큰이었다 : Paren 자체. 잘 알려진 이름 인 토큰의 경우 (내 속성 값 - 파서는 3 개의 토큰 그룹에서 각각 이름, 연산자 및 값에 대해 무시되어야했습니다. 어느 시점에서 충분한 토큰이 없다면, 파서는 예외를 던집니다. 토큰의 흐름에 따라 파서 상태가 바뀔 것입니다. 이전 원자를 스택에 밀어 넣는 결합 (및 xor)은 다음 원자를 완성했을 때 이전 원자를 터 뜨리고 두 원자를 화합물 원자로 결합시킨다. 등등. 주 경영진은 파서의 각 루프가 끝날 때 발생했습니다.

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

}

그것은 단순화되었습니다. 그러나 아이디어는 각 사례 진술이 상당히 간단하다는 것입니다. 표현의 원자 단위로 구문 분석하기가 쉽습니다. 까다로운 부분은 그들 모두를 적절하게 함께 합류하는 것이 었습니다.

이 트릭은 상태 스택과 원자 스택을 사용하여 각 슬러프 루프의 끝에서 하우스 키핑 섹션에서 달성되었습니다. 파서 상태에 따라 다른 일이 발생할 수 있습니다. 내가 말했듯이, 각 사례 진술에서, 파서 상태는 변경 될 수 있으며, 이전 상태가 스택에 밀려 나갈 수 있습니다. 그런 다음 스위치 명세서의 끝에서, 주정부가 방금 원자를 구문 분석을 마치고 계류중인 결합이 있었다면 방금 구식 원자를 복합 원자로 옮겼습니다. 코드는 다음과 같습니다.

            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); 
            }

또 다른 마법은 Enumutil.parse였습니다. 이를 통해 "<"와 같은 것들을 열렬한 값으로 구문 분석 할 수 있습니다. 열거를 다음과 같이 정의한다고 가정 해보십시오.

internal enum Operator
{
    [Description(">")]   GreaterThan,
    [Description(">=")]  GreaterThanOrEqualTo,
    [Description("<")]   LesserThan,
    [Description("<=")]  LesserThanOrEqualTo,
    [Description("=")]   EqualTo,
    [Description("!=")]  NotEqualTo
}

일반적으로 enum.parse는 열거 값의 상징적 이름을 찾고 <는 유효한 상징적 이름이 아닙니다. enumutil.parse () 설명의 내용을 찾습니다. 코드는 다음과 같습니다.

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);
    }

}

나는 다른 곳에서 enumutil.parse () 물건을 얻었습니다. 어쩌면 여기?

약간의 재귀 살해 파서가 이에 적합합니다. 당신은 아마도 구문 분석 트리를 만들 필요조차 없을 것입니다 - 당신은 당신이 구문 분석 할 때 평가를 할 수 있습니다.

 /* here's a teeny one in C++ */
void ScanWhite(const char* &p){
  while (*p==' ') p++;
}

bool ParseNum(const char* &p, double &v){
  ScanWhite(p);
  if (!DIGIT(*p)) return false;
  const char* p0 = p;
  while(DIGIT(*p)) p++;
  if (*p == '.'){
    p++;
    while(DIGIT(*p)) p++;
  }
  v = /* value of characters p0 up to p */;
  return true;
}

bool ParseId(const char* &p, double &v){
  ScanWhite(p);
  if (ALPHA(p[0]) && DIGIT(p[1])){
    v = /* value of cell whose name is p[0], p[1] */;
    p += 2;
    return true;
  }
  return false;
}

bool ParseChar(const char* &p, char c){
  ScanWhite(p);
  if (*p != c) return false;
  p++;
  return true;
}

void ParseExpr(const char* &p, double &v); /* forward declaration */

void ParsePrimitive(const char* &p, double &v){
  if (ParseNum(p, v));
  else if (ParseId(p, v));
  else if (ParseChar(p, '(')){
    ParseExpr(p, v);
    if (!ParseChar(p, ')'){/* throw syntax error */}
  }
  else {/* throw syntax error */}
}
#define PARSE_HIGHER ParsePrimitive

void ParseUnary(const char* &p, double &v){
  if (ParseChar(p, '-')){
    ParseUnary(p, v);
    v = -v;
  }
  else {
    PARSE_HIGHER(p, v);
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseUnary

void ParseProduct(const char* &p, double &v){
  double v2;
  PARSE_HIGHER(p, v);
  while(true){
    if (ParseChar(p, '*')){
      PARSE_HIGHER(p, v2);
      v *= v2;
    }
    else if (ParseChar(p, '/')){
      PARSE_HIGHER(p, v2);
      v /= v2;
    }
    else break;
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseProduct

void ParseSum(const char* &p, double &v){
  double v2;
  PARSE_HIGHER(p, v);
  while(true){
    if (ParseChar(p, '+')){
      PARSE_HIGHER(p, v2);
      v += v2;
    }
    else if (ParseChar(p, '-')){
      PARSE_HIGHER(p, v2);
      v -= v2;
    }
    else break;
  }
}
#undef  PARSE_HIGHER
#define PARSE_HIGHER ParseSum

void ParseExpr(const char* &p, double &v){
  PARSE_HIGHER(p, v);
}

double ParseTopLevel(const char* buf){
  const char* p = buf;
  double v;
  ParseExpr(p, v);
  return v;
}

이제 Parsetop에 전화하면 표현식의 값을 계산합니다.

parse_higher 매크로의 이유는 중간 수준의 우선 순위에서 작업자를 더 쉽게 추가 할 수 있기 때문입니다.

"if"진술을하는 것은 조금 더 관여합니다. 각 구문 분석 루틴에는 추가 "활성화"인수가 필요하므로 활성화되지 않으면 계산하지 않습니다. 그런 다음 "if"라는 단어를 구문 분석하고 테스트 표현식을 구문 분석 한 다음 두 가지 결과 표현식을 사용하지 않고 비활성화 한 것입니다.

.NET jscript 컴파일러를 사용하거나 Ironpython, Ironruby 또는 Ironscheme (알파벳순으로, 선호도가 아닌)과 인터페이스 할 수 있습니다.

나는 방법의 반례를 얻었다 ~ 아니다 그것을 위해 : Will o 'wisp (이것은 내 자신의 코드이기 때문에 나는 그것을 비판하는 자신감을 느낍니다).

뭐야 좋은 코드에 대해?

  1. 결과적으로 디자인 패턴을 사용합니다 : 통역사 패턴
  2. 다소 깨끗한 디자인이 있습니다
  3. 그것은 좋은 방법으로 속성을 사용합니다.
  4. 멋진 그래픽을 생성합니다. ;-)

Turtle Graphics http://i3.codeplex.com/project/download/filedownload.aspx?projectname=wisp&downloadid=34823

뭐야 나쁜 코드에 대해?

  1. 느린다!
  2. 언어는 목록 (데이터 대 코드)과 관련하여 잘못 정의됩니다.

체크 아웃 antlr. 언어 구문을 정의하고 GUI 도구를 사용하여 테스트하고 다양한 언어로 소스 코드를 생성합니다. 오픈 소스.

나는 책을 추천 할 것이다 작은 언어를 구성합니다. 이 작업을 올바르게 완료하는 데 필요한 많은 컴파일러 기본 사항을 사용합니다.

언어에 엄격한 한계가 없으면 정규 표현이 효과가 없다는 사실을 제기했습니다. 다른 사람들이 말했듯이, a 재귀 하강 파서 트릭을 할 것입니다.

다음에 선택은 사용 여부입니다 파서 생성기 처럼 antlr, 또는 처음부터 하나를 쓰십시오.

이 오픈 소스 프로젝트를 살펴보십시오.

탁월한 재무 기능

Corecalc/Funcalc 작업을 보는 것이 좋습니다.http://www.itu.dk/people/sestoft/funcalc/

나는 생산에서 Coco r Parser Generator에 그들의 문법을 사용했으며 실제로 빠르게 작동합니다.

1. Corecalc에서 Excel 문법을 얻습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top