+
+static void errmsg(ParserState *ps, const char *fmt, ...)
+{
+ fprintf(stderr, "%s line %d error: ", ps->fname, ps->nline);
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fputc('\n', stderr);
+}
+
+static void expected(ParserState *ps, const char *str)
+{
+ errmsg(ps, "expected: %s", str);
+ ++ps->nerrors;
+}
+
+static void set_var(ParserState *ps, const char *name, const Value &value)
+{
+ ps->var[name] = value;
+
+ Gear *gear = this_gear(ps);
+ if(gear) {
+ set_gear_var(gear, name, value);
+ }
+
+ if(ps->cur_motor) {
+ set_motor_var(ps, ps->cur_motor, name, value);
+ }
+}
+
+static bool have_var(ParserState *ps, const char *name)
+{
+ return ps->var.find(name) != ps->var.end();
+}
+
+static const Value &get_var(ParserState *ps, const char *name)
+{
+ return ps->var[name];
+}
+
+static void set_func(ParserState *ps, const char *name, Func func, int nargs)
+{
+ FuncDesc fdesc;
+ fdesc.func = func;
+ fdesc.arity = nargs;
+ ps->func[name] = fdesc;
+}
+
+static bool have_func(ParserState *ps, const char *name)
+{
+ return ps->func.find(name) != ps->func.end();
+}
+
+static Func get_func(ParserState *ps, const char *name)
+{
+ return ps->func[name].func;
+}
+
+static int get_func_arity(ParserState *ps, const char *name)
+{
+ return ps->func[name].arity;
+}
+
+static void print_value(const Value &val, FILE *fp)
+{
+ switch(val.type) {
+ case VAL_NUM:
+ fprintf(fp, "%f", val.v[0]);
+ break;
+
+ case VAL_VEC:
+ fprintf(fp, "[%f %f %f %f]", val.v[0], val.v[1], val.v[2], val.v[3]);
+ break;
+
+ case VAL_STR:
+ fprintf(fp, "\"%s\"", val.s.c_str());
+ break;
+
+ default:
+ fprintf(fp, "<invalid>");
+ }
+}
+
+// Gear-specific stuff
+
+static Gear *begin_gear(ParserState *ps)
+{
+ Gear *res = new Gear;
+ ps->cur_gear.push(res);
+ return res;
+}
+
+static bool end_gear(ParserState *ps)
+{
+ if(ps->cur_gear.empty()) {
+ errmsg(ps, "parser error: unbalanced end_gear");
+ return false;
+ }
+ Gear *gear = ps->cur_gear.top();
+ ps->cur_gear.pop();
+
+ printf("DBG: end_gear: %s\n", gear->name.c_str());
+
+ if(gear->name.empty() || ps->gears[gear->name]) {
+ char buf[32];
+ sprintf(buf, "gear%04d", (int)ps->gears.size());
+ gear->name = buf;
+ }
+ printf("DBG: adding gear: %s\n", gear->name.c_str());
+ ps->gears[gear->name] = gear;
+ ps->mcn->add_gear(gear);
+
+ if(!ps->cur_gear.empty()) {
+ ps->cur_gear.top()->attach(gear);
+ }
+ return true;
+}
+
+static Gear *this_gear(ParserState *ps)
+{
+ return ps->cur_gear.empty() ? 0 : ps->cur_gear.top();
+}
+
+static void update_gear_vars(ParserState *ps, Gear *gear)
+{
+ std::map<std::string, Value>::const_iterator it = ps->var.begin();
+ while(it != ps->var.end()) {
+ set_gear_var(gear, it->first.c_str(), it->second);
+ ++it;
+ }
+}
+
+#define ASSERT_TYPE(v, t) \
+ do { \
+ if((v).type != (t)) { \
+ fprintf(stderr, "type mismatch while trying to set %s to value: ", name); \
+ print_value(val, stderr); \
+ fputc('\n', stderr); \
+ return false; \
+ } \
+ } while(0)
+
+#define VVEC3(val) Vec3((val).v[0], (val).v[1], (val).v[2])
+#define VVEC4(val) Vec4((val).v[0], (val).v[1], (val).v[2], (val).v[3])
+
+static bool set_gear_var(Gear *gear, const char *name, const Value &val)
+{
+ if(strcmp(name, "name") == 0) {
+ ASSERT_TYPE(val, VAL_STR);
+ gear->name = val.s;
+
+ } else if(strcmp(name, "position") == 0) {
+ ASSERT_TYPE(val, VAL_VEC);
+ gear->pos = VVEC3(val);
+
+ } else if(strcmp(name, "plane") == 0) {
+ ASSERT_TYPE(val, VAL_VEC);
+ gear->axis = VVEC3(val);
+ gear->pdist = val.v[3];
+
+ } else if(strcmp(name, "thickness") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ gear->thickness = val.v[0];
+
+ } else if(strcmp(name, "teeth") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ gear->set_teeth(val.v[0]); // set teeth and recalc radius
+
+ } else if(strcmp(name, "teeth_length") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ gear->teeth_length = val.v[0];
+
+ } else if(strcmp(name, "pitch") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ gear->set_teeth(gear->nteeth, val.v[0]); // set pitch and recalc radius
+
+ } else if(strcmp(name, "color") == 0) {
+ ASSERT_TYPE(val, VAL_VEC);
+ gear->color = VVEC3(val);
+
+ } else if(strcmp(name, "roughness") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ gear->roughness = val.v[0];
+
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+// motor stuff
+
+static void update_motor_vars(ParserState *ps, Motor *motor)
+{
+ std::map<std::string, Value>::const_iterator it = ps->var.begin();
+ while(it != ps->var.end()) {
+ set_motor_var(ps, motor, it->first.c_str(), it->second);
+ ++it;
+ }
+}
+
+static bool set_motor_var(ParserState *ps, Motor *motor, const char *name, const Value &val)
+{
+ if(strcmp(name, "drive") == 0) {
+ ASSERT_TYPE(val, VAL_STR);
+ Gear *gear = ps->gears[val.s];
+ if(!gear) {
+ errmsg(ps, "undefined gear: %s", val.s.c_str());
+ return false;
+ }
+ int idx = ps->mcn->get_gear_index(gear);
+ assert(idx >= 0);
+ motor->drive = idx;
+
+ } else if(strcmp(name, "speed") == 0) {
+ ASSERT_TYPE(val, VAL_NUM);
+ motor->speed = val.v[0];
+
+ } else {
+ return false;
+ }
+ return true;
+}
+
+
+// built-in functions
+static bool func_adjacent(ParserState *ps)
+{
+ Value val_angle = ps->val.top(); ps->val.pop();
+ Value val_gear_name = ps->val.top(); ps->val.pop();
+
+ if(val_gear_name.type != VAL_STR) {
+ expected(ps, "gear name (string) as 1st arg to adjacent");
+ return false;
+ }
+ if(val_angle.type != VAL_NUM) {
+ expected(ps, "angle as 2nd arg to adjacent");
+ return false;
+ }
+
+ Gear *gthis = this_gear(ps);
+ if(!gthis) {
+ errmsg(ps, "adjacent: called outside of a gear block");
+ return false;
+ }
+
+ Gear *gother = ps->gears[val_gear_name.s];
+ if(!gother) {
+ errmsg(ps, "adjacent: gear \"%s\" not found", val_gear_name.s.c_str());
+ return false;
+ }
+
+ Mat4 xform;
+ xform.rotation(deg_to_rad(val_angle.v[0]), gother->axis);
+ Vec3 dir = xform * Vec3(0, 1, 0);
+
+ float sum_radii = gthis->radius + gother->radius;
+ float avg_teeth_len = (gthis->teeth_length + gother->teeth_length) * 0.5;
+ Vec3 pos = gother->pos + dir * (sum_radii - avg_teeth_len * 0.75);
+
+ Value res;
+ res.type = VAL_VEC;
+ res.v[0] = pos.x;
+ res.v[1] = pos.y;
+ res.v[2] = pos.z;
+ ps->val.push(res);
+ return true;
+}
+
+static bool func_coaxial(ParserState *ps)
+{
+ Value val_dist = ps->val.top(); ps->val.pop();
+ Value val_gear_name = ps->val.top(); ps->val.pop();
+
+ if(val_gear_name.type != VAL_STR) {
+ expected(ps, "gear name (string) as 1st arg to func_coaxial");
+ return false;
+ }
+ if(val_dist.type != VAL_NUM) {
+ expected(ps, "stacking distance as 2nd arg to func_coaxial");
+ return false;
+ }
+
+ Gear *gthis = this_gear(ps);
+ if(!gthis) {
+ errmsg(ps, "coaxial: called outside of a gear block");
+ return false;
+ }
+
+ Gear *gother = ps->gears[val_gear_name.s];
+ if(!gother) {
+ errmsg(ps, "coaxial: gear \"%s\" not found", val_gear_name.s.c_str());
+ return false;
+ }
+
+ Vec3 pos = gother->pos + gother->axis * val_dist.v[0];
+
+ Value res;
+ res.type = VAL_VEC;
+ res.v[0] = pos.x;
+ res.v[1] = pos.y;
+ res.v[2] = pos.z;
+ ps->val.push(res);
+ return true;
+}