Add a little parser, that takes a string and generates and Expr *
syntax tree. That's what I'll used for entered dimensions, and algebraic constraints and such. Needs to be extended to handle stuff like points and entities, but I think that it can be. [git-p4: depot-paths = "//depot/solvespace/": change = 1669]
This commit is contained in:
parent
22302dca7a
commit
1fa7865024
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
||||
DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32
|
||||
CFLAGS = /W3 /nologo -I..\common\win32 /O2 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /Zi /I.
|
||||
CFLAGS = /W3 /nologo -I..\common\win32 /O2 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /Zi /I. /EHs
|
||||
|
||||
HEADERS = ..\common\win32\freeze.h ui.h solvespace.h dsc.h sketch.h expr.h
|
||||
|
||||
|
228
expr.cpp
228
expr.cpp
@ -93,16 +93,24 @@ char *Expr::Print(void) {
|
||||
}
|
||||
|
||||
void Expr::PrintW(void) {
|
||||
char c;
|
||||
switch(op) {
|
||||
case PARAM: App("(param %08x)", x.parh.v); break;
|
||||
case PARAM_PTR: App("(paramp %08x)", x.parp->h.v); break;
|
||||
case PARAM: App("param(%08x)", x.parh.v); break;
|
||||
case PARAM_PTR: App("param(p%08x)", x.parp->h.v); break;
|
||||
|
||||
case CONSTANT: App("%.3f", x.v);
|
||||
case CONSTANT: App("%.3f", x.v); break;
|
||||
|
||||
case PLUS: App("(+ "); a->PrintW(); b->PrintW(); App(")"); break;
|
||||
case MINUS: App("(- "); a->PrintW(); b->PrintW(); App(")"); break;
|
||||
case TIMES: App("(* "); a->PrintW(); b->PrintW(); App(")"); break;
|
||||
case DIV: App("(/ "); a->PrintW(); b->PrintW(); App(")"); break;
|
||||
case PLUS: c = '+'; goto p;
|
||||
case MINUS: c = '-'; goto p;
|
||||
case TIMES: c = '*'; goto p;
|
||||
case DIV: c = '/'; goto p;
|
||||
p:
|
||||
App("(");
|
||||
a->PrintW();
|
||||
App(" %c ", c);
|
||||
b->PrintW();
|
||||
App(")");
|
||||
break;
|
||||
|
||||
case NEGATE: App("(- "); a->PrintW(); App(")"); break;
|
||||
case SQRT: App("(sqrt "); a->PrintW(); App(")"); break;
|
||||
@ -114,3 +122,209 @@ void Expr::PrintW(void) {
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_UNPARSED 1024
|
||||
static Expr *Unparsed[MAX_UNPARSED];
|
||||
static int UnparsedCnt, UnparsedP;
|
||||
|
||||
static Expr *Operands[MAX_UNPARSED];
|
||||
static int OperandsP;
|
||||
|
||||
static Expr *Operators[MAX_UNPARSED];
|
||||
static int OperatorsP;
|
||||
|
||||
void Expr::PushOperator(Expr *e) {
|
||||
if(OperatorsP >= MAX_UNPARSED) throw "operator stack full!";
|
||||
Operators[OperatorsP++] = e;
|
||||
}
|
||||
Expr *Expr::TopOperator(void) {
|
||||
if(OperatorsP <= 0) throw "operator stack empty (get top)";
|
||||
return Operators[OperatorsP-1];
|
||||
}
|
||||
Expr *Expr::PopOperator(void) {
|
||||
if(OperatorsP <= 0) throw "operator stack empty (pop)";
|
||||
return Operators[--OperatorsP];
|
||||
}
|
||||
void Expr::PushOperand(Expr *e) {
|
||||
if(OperandsP >= MAX_UNPARSED) throw "operand stack full";
|
||||
Operands[OperandsP++] = e;
|
||||
}
|
||||
Expr *Expr::PopOperand(void) {
|
||||
if(OperandsP <= 0) throw "operand stack empty";
|
||||
return Operands[--OperandsP];
|
||||
}
|
||||
Expr *Expr::Next(void) {
|
||||
if(UnparsedP >= UnparsedCnt) return NULL;
|
||||
return Unparsed[UnparsedP];
|
||||
}
|
||||
void Expr::Consume(void) {
|
||||
if(UnparsedP >= UnparsedCnt) throw "no token to consume";
|
||||
UnparsedP++;
|
||||
}
|
||||
|
||||
int Expr::Precedence(Expr *e) {
|
||||
if(e->op == ALL_RESOLVED) return -1; // never want to reduce this marker
|
||||
if(e->op != BINARY_OP && e->op != UNARY_OP) oops();
|
||||
|
||||
switch(e->x.c) {
|
||||
case 's':
|
||||
case 'n': return 30;
|
||||
|
||||
case '*':
|
||||
case '/': return 20;
|
||||
|
||||
case '+':
|
||||
case '-': return 10;
|
||||
|
||||
default: oops();
|
||||
}
|
||||
}
|
||||
|
||||
void Expr::Reduce(void) {
|
||||
Expr *a, *b;
|
||||
|
||||
Expr *op = PopOperator();
|
||||
Expr *n;
|
||||
int o;
|
||||
switch(op->x.c) {
|
||||
case '+': o = PLUS; goto c;
|
||||
case '-': o = MINUS; goto c;
|
||||
case '*': o = TIMES; goto c;
|
||||
case '/': o = DIV; goto c;
|
||||
c:
|
||||
b = PopOperand();
|
||||
a = PopOperand();
|
||||
n = a->AnyOp(o, b);
|
||||
break;
|
||||
|
||||
case 'n': n = PopOperand()->Negate(); break;
|
||||
case 's': n = PopOperand()->Sqrt(); break;
|
||||
|
||||
default: oops();
|
||||
}
|
||||
PushOperand(n);
|
||||
}
|
||||
|
||||
void Expr::ReduceAndPush(Expr *n) {
|
||||
while(Precedence(n) <= Precedence(TopOperator())) {
|
||||
Reduce();
|
||||
}
|
||||
PushOperator(n);
|
||||
}
|
||||
|
||||
void Expr::Parse(void) {
|
||||
Expr *e = AllocExpr();
|
||||
e->op = ALL_RESOLVED;
|
||||
PushOperator(e);
|
||||
|
||||
for(;;) {
|
||||
Expr *n = Next();
|
||||
if(!n) throw "end of expression unexpected";
|
||||
|
||||
if(n->op == CONSTANT) {
|
||||
PushOperand(n);
|
||||
Consume();
|
||||
} else if(n->op == PAREN && n->x.c == '(') {
|
||||
Consume();
|
||||
Parse();
|
||||
n = Next();
|
||||
if(n->op != PAREN || n->x.c != ')') throw "expected: )";
|
||||
Consume();
|
||||
} else if(n->op == UNARY_OP) {
|
||||
PushOperator(n);
|
||||
Consume();
|
||||
continue;
|
||||
} else if(n->op == BINARY_OP && n->x.c == '-') {
|
||||
// The minus sign is special, because it might be binary or
|
||||
// unary, depending on context.
|
||||
n->op = UNARY_OP;
|
||||
n->x.c = 'n';
|
||||
PushOperator(n);
|
||||
Consume();
|
||||
continue;
|
||||
} else {
|
||||
throw "expected expression";
|
||||
}
|
||||
|
||||
n = Next();
|
||||
if(n && n->op == BINARY_OP) {
|
||||
ReduceAndPush(n);
|
||||
Consume();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while(TopOperator()->op != ALL_RESOLVED) {
|
||||
Reduce();
|
||||
}
|
||||
PopOperator(); // discard the ALL_RESOLVED marker
|
||||
}
|
||||
|
||||
void Expr::Lex(char *in) {
|
||||
while(*in) {
|
||||
if(UnparsedCnt >= MAX_UNPARSED) throw "too long";
|
||||
|
||||
char c = *in;
|
||||
if(isdigit(c) || c == '.') {
|
||||
// A number literal
|
||||
char number[70];
|
||||
int len = 0;
|
||||
while((isdigit(*in) || *in == '.') && len < 30) {
|
||||
number[len++] = *in;
|
||||
in++;
|
||||
}
|
||||
number[len++] = '\0';
|
||||
Expr *e = AllocExpr();
|
||||
e->op = CONSTANT;
|
||||
e->x.v = atof(number);
|
||||
Unparsed[UnparsedCnt++] = e;
|
||||
} else if(isalpha(c) || c == '_') {
|
||||
char name[70];
|
||||
int len = 0;
|
||||
while(isforname(*in) && len < 30) {
|
||||
name[len++] = *in;
|
||||
in++;
|
||||
}
|
||||
name[len++] = '\0';
|
||||
|
||||
Expr *e = AllocExpr();
|
||||
if(strcmp(name, "sqrt")==0) {
|
||||
e->op = UNARY_OP;
|
||||
e->x.c = 's';
|
||||
} else {
|
||||
throw "unknown name";
|
||||
}
|
||||
Unparsed[UnparsedCnt++] = e;
|
||||
} else if(strchr("+-*/()", c)) {
|
||||
Expr *e = AllocExpr();
|
||||
e->op = (c == '(' || c == ')') ? PAREN : BINARY_OP;
|
||||
e->x.c = c;
|
||||
Unparsed[UnparsedCnt++] = e;
|
||||
in++;
|
||||
} else if(isspace(c)) {
|
||||
// Ignore whitespace
|
||||
in++;
|
||||
} else {
|
||||
// This is a lex error.
|
||||
throw "unexpected characters";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expr *Expr::FromString(char *in) {
|
||||
UnparsedCnt = 0;
|
||||
OperandsP = 0;
|
||||
OperatorsP = 0;
|
||||
|
||||
Expr *r;
|
||||
try {
|
||||
Lex(in);
|
||||
Parse();
|
||||
r = PopOperand();
|
||||
} catch (char *e) {
|
||||
dbp("exception: parse/lex error: %s", e);
|
||||
return NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
35
expr.h
35
expr.h
@ -28,6 +28,16 @@ public:
|
||||
static const int SIN = 107;
|
||||
static const int COS = 108;
|
||||
|
||||
// Special helpers for when we're parsing an expression from text.
|
||||
// Initially, literals (like a constant number) appear in the same
|
||||
// format as they will in the finished expression, but the operators
|
||||
// are different until the parser fixes things up (and builds the
|
||||
// tree from the flat list that the lexer outputs).
|
||||
static const int ALL_RESOLVED = 1000;
|
||||
static const int PAREN = 1001;
|
||||
static const int BINARY_OP = 1002;
|
||||
static const int UNARY_OP = 1003;
|
||||
|
||||
int op;
|
||||
Expr *a;
|
||||
Expr *b;
|
||||
@ -37,6 +47,9 @@ public:
|
||||
Param *parp;
|
||||
hPoint point;
|
||||
hEntity entity;
|
||||
|
||||
// For use while parsing
|
||||
char c;
|
||||
} x;
|
||||
|
||||
static Expr *FromParam(hParam p);
|
||||
@ -62,6 +75,28 @@ public:
|
||||
void App(char *str, ...);
|
||||
char *Print(void);
|
||||
void PrintW(void); // worker
|
||||
|
||||
// Make a copy of an expression that won't get blown away when we
|
||||
// do a FreeAllExprs()
|
||||
Expr *Keep(void);
|
||||
|
||||
static Expr *FromString(char *in);
|
||||
static void Lex(char *in);
|
||||
static Expr *Next(void);
|
||||
static void Consume(void);
|
||||
|
||||
static void PushOperator(Expr *e);
|
||||
static Expr *PopOperator(void);
|
||||
static Expr *TopOperator(void);
|
||||
static void PushOperand(Expr *e);
|
||||
static Expr *PopOperand(void);
|
||||
|
||||
static void Reduce(void);
|
||||
static void ReduceAndPush(Expr *e);
|
||||
static int Precedence(Expr *e);
|
||||
|
||||
static int Precedence(int op);
|
||||
static void Parse(void);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -12,7 +12,10 @@
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
#define isforname(c) (isalnum(c) || (c) == '_' || (c) == '-' || (c) == '#')
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
@ -205,7 +205,8 @@ void TextWindow::ShowHeader(void) {
|
||||
SS.GW.EnsureValidActiveGroup();
|
||||
|
||||
if(SS.GW.pendingDescription) {
|
||||
Printf("");
|
||||
Printf(" %C4 group:%s",
|
||||
SS.group.FindById(SS.GW.activeGroup)->DescriptionString());
|
||||
} else {
|
||||
// Navigation buttons
|
||||
Printf(" %Lb%f<<%E %Lh%fhome%E %C4 group:%s",
|
||||
|
Loading…
Reference in New Issue
Block a user