]> mj.ucw.cz Git - paperjam.git/blob - paperjam.cc
First attempts...
[paperjam.git] / paperjam.cc
1 #include <cassert>
2 #include <cstdarg>
3 #include <cstdlib>
4 #include <cstdio>
5
6 using namespace std;
7
8 #include "jam.h"
9
10 /*** Lexer ***/
11
12 enum token_type {
13   TOK_NONE,
14   TOK_END,
15   TOK_EQUAL,
16   TOK_COMMA,
17   TOK_OPEN_PAREN,
18   TOK_CLOSE_PAREN,
19   TOK_OPEN_BRACE,
20   TOK_CLOSE_BRACE,
21   TOK_IDENT,
22   TOK_STRING,
23   TOK_NUMBER,
24 };
25
26 const char *in_pos;
27 static token_type this_token = TOK_NONE;
28 static token_type buffered_token = TOK_NONE;
29 static string token;
30 static double token_num;
31
32 static void NONRET parse_error(const char *msg, ...);
33
34 static void parse_error(const char *msg, ...)
35 {
36   va_list args;
37   va_start(args, msg);
38   fprintf(stderr, "Parse error: ");
39   vfprintf(stderr, msg, args);
40   fprintf(stderr, "\n");
41   va_end(args);
42   exit(1);
43 }
44
45 static token_type get_next_token()
46 {
47   while (*in_pos == ' ' || *in_pos == '\t' || *in_pos == '\r' || *in_pos == '\n')
48     in_pos++;
49
50   token = "";
51   if (!*in_pos)
52     return TOK_END;
53
54   if (*in_pos >= '0' && *in_pos <= '9' ||
55       *in_pos == '-' && in_pos[1] >= '0' && in_pos[1] <= '9')
56     {
57       token += *in_pos++;
58       while (*in_pos >= '0' && *in_pos <= '9' || *in_pos == '.')
59         token += *in_pos++;
60
61       size_t end_pos;
62       token_num = stod(token, &end_pos);
63       if (end_pos < token.length())
64         parse_error("Invalid number %s", token.c_str());
65       return TOK_NUMBER;
66     }
67
68   if (*in_pos >= 'A' && *in_pos <= 'Z' ||
69       *in_pos >= 'a' && *in_pos <= 'z')
70     {
71       while (*in_pos >= 'A' && *in_pos <= 'Z' ||
72              *in_pos >= 'a' && *in_pos <= 'z' ||
73              *in_pos >= '0' && *in_pos <= '9')
74         token += *in_pos++;
75       return TOK_IDENT;
76     }
77
78   if (*in_pos == '"')
79     {
80       in_pos++;
81       while (*in_pos != '"')
82         {
83           if (!*in_pos)
84             parse_error("Unterminated string");
85           if (*in_pos == '\\')
86             {
87               in_pos++;
88               if (*in_pos == '"')
89                 parse_error("Unrecognized escape sequence \\%c", *in_pos);
90             }
91           token += *in_pos++;
92         }
93       in_pos++;
94       return TOK_STRING;
95     }
96
97   uint c = *in_pos++;
98   switch (c)
99     {
100     case '=':
101       return TOK_EQUAL;
102     case ',':
103       return TOK_COMMA;
104     case '(':
105       return TOK_OPEN_PAREN;
106     case ')':
107       return TOK_CLOSE_PAREN;
108     case '{':
109       return TOK_OPEN_BRACE;
110     case '}':
111       return TOK_CLOSE_BRACE;
112     default:
113       parse_error("Unrecognized character '%c'", c);
114     }
115 }
116
117 static token_type next_token()
118 {
119   this_token = get_next_token();
120   return this_token;
121 }
122
123 static void return_token()
124 {
125   assert(this_token != TOK_NONE);
126   assert(buffered_token == TOK_NONE);
127   buffered_token = this_token;
128   this_token = TOK_NONE;
129 }
130
131 /*** Parser ***/
132
133 static const arg_def move_args[] = {
134   { "x",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
135   { "y",        AT_DIMEN | AT_MANDATORY | AT_POSITIONAL },
136   { NULL,       0 }
137 };
138
139 static const cmd_def cmd_table[] = {
140   { "move",     move_args,      NULL    },
141   { NULL,       NULL,           NULL    }
142 };
143
144 struct unit {
145   const char *name;
146   double multiplier;
147 };
148
149 #define MM (72/25.4)
150
151 static const unit units[] = {
152   { "mm",       MM },
153   { "cm",       10*MM },
154   { "dm",       100*MM },
155   { "m",        1000*MM },
156   { "in",       72 },
157   { "pt",       1 },
158   { NULL,       0 }
159 };
160
161 static double parse_dimen(const arg_def *adef)
162 {
163   token_type t = next_token();
164   if (t != TOK_NUMBER)
165     parse_error("Paremeter %s must be a dimension", adef->name);
166   double tmp = token_num;
167
168   t = next_token();
169   if (t != TOK_IDENT)
170     parse_error("Paremeter %s must have a unit", adef->name);
171   for (uint i; units[i].name; i++)
172     if (token == units[i].name)
173       return tmp * units[i].multiplier;
174   parse_error("Unknown unit %s", token.c_str());
175 }
176
177 static cmd_args *parse_args(const cmd_def *cdef)
178 {
179   cmd_args *args = new cmd_args;
180
181   const arg_def *adefs = cdef->arg_defs;
182   uint num_args = 0;
183   while (adefs[num_args].name)
184     {
185       args->arg.push_back(arg_val());
186       args->arg_given.push_back(0);
187       num_args++;
188     }
189
190   token_type t = next_token();
191   if (t != TOK_OPEN_PAREN)
192     {
193       return_token();
194       return args;
195     }
196
197   bool saw_named = false;
198   uint next_pos = 0;
199   for (;;)
200     {
201       t = next_token();
202       int argi = 0;
203       if (t == TOK_IDENT)
204         {
205           while (adefs[argi].name && token != adefs[argi].name)
206             argi++;
207           if (!adefs[argi].name)
208             parse_error("Command %s has no parameter %s", cdef->name, token.c_str());
209           t = next_token();
210           if (t != TOK_EQUAL)
211             parse_error("Parameter name must be followed by '='");
212           saw_named = true;
213         }
214       else if (saw_named)
215         parse_error("Positional parameters must precede named ones");
216       else
217         {
218           while (next_pos < num_args && !(adefs[next_pos].type & AT_POSITIONAL))
219             next_pos++;
220           if (next_pos >= num_args)
221             parse_error("Too many positional arguments for command %s", cdef->name);
222           argi = next_pos++;
223         }
224
225       const arg_def *adef = &adefs[argi];
226       switch (adef->type & AT_TYPE_MASK)
227         {
228         case AT_STRING:
229           t = next_token();
230           if (t != TOK_STRING)
231             parse_error("Paremeter %s must be a string", adef->name);
232           args->arg[argi].s = token;
233           break;
234         case AT_DOUBLE:
235           t = next_token();
236           if (t != TOK_NUMBER)
237             parse_error("Paremeter %s must be a number", adef->name);
238           args->arg[argi].d = token_num;
239           break;
240         case AT_DIMEN:
241           args->arg[argi].d = parse_dimen(adef);
242           break;
243         default:
244           abort();
245         }
246
247       t = next_token();
248       if (t == TOK_CLOSE_PAREN)
249         break;
250       if (t != TOK_COMMA)
251         parse_error("Comma expected after parameter %s", adef->name);
252     }
253
254   return args;
255 }
256
257 static cmd *parse_cmd()
258 {
259   const cmd_def *cdef = cmd_table;
260   while (cdef->name && token != cdef->name)
261     cdef++;
262   if (!cdef->name)
263     parse_error("Unknown command %s", token.c_str());
264
265   cmd_args *args = parse_args(cdef);
266 }
267
268 static void parse(list<cmd> *cmds)
269 {
270   for (;;)
271     {
272       token_type t = next_token();
273       if (t != TOK_IDENT)
274         {
275           return_token();
276           return;
277         }
278
279       cmd *c = parse_cmd();
280       cmds->push_back(c);
281     }
282 }
283
284 /*** Main ***/
285
286 int main(int argc, char **argv)
287 {
288   if (argc != 4)
289     {
290       fprintf(stderr, "Usage: pdfjam <commands> <input> <output>\n");
291       return 1;
292     }
293
294   list<cmd> cmds;
295   in_pos = argv[1];
296   parse(&cmds);
297
298   return 0;
299 }