diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2002-04-04 17:24:56 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2002-04-04 17:24:56 -0300 |
commit | c3b90061ea8c9add053538945ac85188e48e48b1 (patch) | |
tree | 1271293b5cbfbd557b8070a7a97534cee4d6e589 /liolib.c | |
parent | a2e414d6793fa39f17c320106e3aae87cb576ca2 (diff) | |
download | lua-c3b90061ea8c9add053538945ac85188e48e48b1.tar.gz lua-c3b90061ea8c9add053538945ac85188e48e48b1.tar.bz2 lua-c3b90061ea8c9add053538945ac85188e48e48b1.zip |
new design for iolib (object style)
Diffstat (limited to 'liolib.c')
-rw-r--r-- | liolib.c | 297 |
1 files changed, 152 insertions, 145 deletions
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | ** $Id: liolib.c,v 1.132 2002/03/20 12:54:08 roberto Exp roberto $ | 2 | ** $Id: liolib.c,v 1.133 2002/03/27 15:30:41 roberto Exp roberto $ |
3 | ** Standard I/O (and system) library | 3 | ** Standard I/O (and system) library |
4 | ** See Copyright Notice in lua.h | 4 | ** See Copyright Notice in lua.h |
5 | */ | 5 | */ |
@@ -27,32 +27,21 @@ | |||
27 | */ | 27 | */ |
28 | 28 | ||
29 | 29 | ||
30 | #ifdef POPEN | 30 | #ifndef POPEN |
31 | /* FILE *popen(); | 31 | #define pclose(f) (-1) |
32 | int pclose(); */ | ||
33 | #define CLOSEFILE(L, f) ((pclose(f) == -1) ? fclose(f) : 0) | ||
34 | #else | ||
35 | /* no support for popen */ | ||
36 | #define popen(x,y) NULL /* that is, popen always fails */ | ||
37 | #define CLOSEFILE(L, f) (fclose(f)) | ||
38 | #endif | 32 | #endif |
39 | 33 | ||
40 | 34 | ||
41 | #define INFILE 0 | ||
42 | #define OUTFILE 1 | ||
43 | #define NOFILE 2 | ||
44 | |||
45 | #define FILEHANDLE "FileHandle" | 35 | #define FILEHANDLE "FileHandle" |
46 | #define CLOSEDFILEHANDLE "ClosedFileHandle" | 36 | #define CLOSEDFILEHANDLE "ClosedFileHandle" |
47 | 37 | ||
48 | 38 | #define IO_INPUT "_input" | |
49 | static const char *const filenames[] = {"_INPUT", "_OUTPUT"}; | 39 | #define IO_OUTPUT "_output" |
50 | static const char *const basicfiles[] = {"stdin", "stdout"}; | ||
51 | 40 | ||
52 | 41 | ||
53 | static int pushresult (lua_State *L, int i) { | 42 | static int pushresult (lua_State *L, int i) { |
54 | if (i) { | 43 | if (i) { |
55 | lua_pushnumber(L, 1); | 44 | lua_pushboolean(L, 1); |
56 | return 1; | 45 | return 1; |
57 | } | 46 | } |
58 | else { | 47 | else { |
@@ -64,43 +53,15 @@ static int pushresult (lua_State *L, int i) { | |||
64 | } | 53 | } |
65 | 54 | ||
66 | 55 | ||
67 | static int checkfile (lua_State *L, int findex, const char *tname) { | 56 | static FILE *tofile (lua_State *L, int findex) { |
68 | int res; | 57 | FILE *f = (FILE *)lua_touserdata(L, findex); |
69 | lua_getmetatable(L, findex); | 58 | if (f && lua_getmetatable(L, findex) && |
70 | lua_pushstring(L, tname); | 59 | lua_equal(L, -1, lua_upvalueindex(1))) { |
71 | lua_rawget(L, LUA_REGISTRYINDEX); | 60 | lua_pop(L, 1); |
72 | res = lua_equal(L, -1, -2); | 61 | return f; |
73 | lua_pop(L, 2); | ||
74 | return res; | ||
75 | } | ||
76 | |||
77 | |||
78 | /* temporary?? should be in auxlib... */ | ||
79 | static void *luaL_check_userdata (lua_State *L, int findex, const char *tn) { | ||
80 | luaL_arg_check(L, checkfile(L, findex, tn), findex, "bad file"); | ||
81 | return lua_touserdata(L, findex); | ||
82 | } | ||
83 | |||
84 | |||
85 | static FILE *getopthandle (lua_State *L, int inout) { | ||
86 | FILE *p = (FILE *)(lua_touserdata(L, 1)); | ||
87 | if (p != NULL) { /* is it a userdata ? */ | ||
88 | if (!checkfile(L, 1, FILEHANDLE)) { /* not a valid file handle? */ | ||
89 | if (checkfile(L, 1, CLOSEDFILEHANDLE)) | ||
90 | luaL_argerror(L, 1, "file is closed"); | ||
91 | else | ||
92 | luaL_argerror(L, 1, "(invalid value)"); | ||
93 | } | ||
94 | lua_pushvalue(L, 1); lua_remove(L, 1); /* move it to stack top */ | ||
95 | } | ||
96 | else { /* try global value */ | ||
97 | lua_getglobal(L, filenames[inout]); | ||
98 | if (!checkfile(L, -1, FILEHANDLE)) | ||
99 | luaL_verror(L, "global variable `%.10s' is not a valid file handle", | ||
100 | filenames[inout]); | ||
101 | p = (FILE *)(lua_touserdata(L, -1)); | ||
102 | } | 62 | } |
103 | return p; /* leave handle at stack top to avoid GC */ | 63 | luaL_argerror(L, findex, "bad file"); |
64 | return NULL; /* to avoid warnings */ | ||
104 | } | 65 | } |
105 | 66 | ||
106 | 67 | ||
@@ -112,102 +73,106 @@ static void newfile (lua_State *L, FILE *f) { | |||
112 | } | 73 | } |
113 | 74 | ||
114 | 75 | ||
115 | static void newfilewithname (lua_State *L, FILE *f, const char *name) { | 76 | static void registerfile (lua_State *L, FILE *f, const char *name, |
77 | const char *impname) { | ||
116 | lua_pushstring(L, name); | 78 | lua_pushstring(L, name); |
117 | newfile(L, f); | 79 | newfile(L, f); |
80 | if (impname) { | ||
81 | lua_pushstring(L, impname); | ||
82 | lua_pushvalue(L, -2); | ||
83 | lua_settable(L, -6); | ||
84 | } | ||
118 | lua_settable(L, -3); | 85 | lua_settable(L, -3); |
119 | } | 86 | } |
120 | 87 | ||
121 | 88 | ||
122 | static int setnewfile (lua_State *L, FILE *f, int inout) { | 89 | static int setnewfile (lua_State *L, FILE *f) { |
123 | if (f == NULL) | 90 | if (f == NULL) |
124 | return pushresult(L, 0); | 91 | return pushresult(L, 0); |
125 | else { | 92 | else { |
126 | newfile(L, f); | 93 | newfile(L, f); |
127 | if (inout != NOFILE) { | ||
128 | lua_pushvalue(L, -1); | ||
129 | lua_setglobal(L, filenames[inout]); | ||
130 | } | ||
131 | return 1; | 94 | return 1; |
132 | } | 95 | } |
133 | } | 96 | } |
134 | 97 | ||
135 | 98 | ||
136 | static void resetfile (lua_State *L, int inout) { | ||
137 | lua_getglobal(L, "io"); | ||
138 | lua_pushstring(L, basicfiles[inout]); | ||
139 | lua_gettable(L, -2); | ||
140 | lua_setglobal(L, filenames[inout]); | ||
141 | lua_pop(L, 1); | ||
142 | } | ||
143 | |||
144 | |||
145 | static int io_close (lua_State *L) { | 99 | static int io_close (lua_State *L) { |
146 | FILE *f = (FILE *)(luaL_check_userdata(L, 1, FILEHANDLE)); | 100 | FILE *f = tofile(L, 1); |
147 | int status = 1; | 101 | int status = 1; |
148 | if (f != stdin && f != stdout && f != stderr) { | 102 | if (f != stdin && f != stdout && f != stderr) { |
149 | lua_settop(L, 1); /* make sure file is on top */ | 103 | lua_settop(L, 1); /* make sure file is on top */ |
150 | lua_pushliteral(L, CLOSEDFILEHANDLE); | 104 | lua_pushliteral(L, CLOSEDFILEHANDLE); |
151 | lua_rawget(L, LUA_REGISTRYINDEX); | 105 | lua_rawget(L, LUA_REGISTRYINDEX); |
152 | lua_setmetatable(L, 1); | 106 | lua_setmetatable(L, 1); |
153 | status = (CLOSEFILE(L, f) == 0); | 107 | status = (pclose(f) != -1) || (fclose(f) == 0); |
154 | } | 108 | } |
155 | return pushresult(L, status); | 109 | return pushresult(L, status); |
156 | } | 110 | } |
157 | 111 | ||
158 | 112 | ||
159 | static int file_collect (lua_State *L) { | 113 | static int io_open (lua_State *L) { |
160 | FILE *f = (FILE *)(luaL_check_userdata(L, 1, FILEHANDLE)); | 114 | FILE *f = fopen(luaL_check_string(L, 1), luaL_opt_string(L, 2, "r")); |
161 | if (f != stdin && f != stdout && f != stderr) | 115 | return setnewfile(L, f); |
162 | CLOSEFILE(L, f); | ||
163 | return 0; | ||
164 | } | 116 | } |
165 | 117 | ||
166 | 118 | ||
167 | static int io_open (lua_State *L) { | 119 | static int io_popen (lua_State *L) { |
168 | FILE *f = fopen(luaL_check_string(L, 1), luaL_check_string(L, 2)); | 120 | #ifndef POPEN |
169 | return setnewfile(L, f, NOFILE); | 121 | lua_error(L, "`popen' not supported"); |
122 | return 0; | ||
123 | #else | ||
124 | FILE *f = popen(luaL_check_string(L, 1), luaL_opt_string(L, 2, "r")); | ||
125 | return setnewfile(L, f); | ||
126 | #endif | ||
170 | } | 127 | } |
171 | 128 | ||
172 | 129 | ||
173 | static int io_tmpfile (lua_State *L) { | 130 | static int io_tmpfile (lua_State *L) { |
174 | return setnewfile(L, tmpfile(), NOFILE); | 131 | return setnewfile(L, tmpfile()); |
175 | } | 132 | } |
176 | 133 | ||
177 | 134 | ||
135 | static FILE *getiofile (lua_State *L, const char *name) { | ||
136 | lua_pushstring(L, name); | ||
137 | lua_rawget(L, lua_upvalueindex(1)); | ||
138 | return tofile(L, -1); | ||
139 | } | ||
178 | 140 | ||
179 | static int io_fromto (lua_State *L, int inout, const char *mode) { | 141 | |
180 | FILE *current; | 142 | static int g_iofile (lua_State *L, const char *name, const char *mode) { |
181 | if (lua_isnone(L, 1)) { | 143 | if (lua_isnone(L, 1)) { |
182 | getopthandle(L, inout); | 144 | lua_pushstring(L, name); |
183 | resetfile(L, inout); | 145 | lua_rawget(L, lua_upvalueindex(1)); |
184 | return io_close(L); | 146 | return 1; |
185 | } | 147 | } |
186 | else { | 148 | else { |
187 | const char *s = luaL_check_string(L, 1); | 149 | const char *filename = lua_tostring(L, 1); |
188 | current = (*s == '|') ? popen(s+1, mode) : fopen(s, mode); | 150 | lua_pushstring(L, name); |
189 | return setnewfile(L, current, inout); | 151 | if (filename) { |
152 | FILE *f = fopen(filename, mode); | ||
153 | luaL_arg_check(L, f, 1, strerror(errno)); | ||
154 | newfile(L, f); | ||
155 | } | ||
156 | else { | ||
157 | lua_pushvalue(L, 1); | ||
158 | tofile(L, -1); /* check that it's a valid file handle */ | ||
159 | } | ||
160 | lua_rawset(L, lua_upvalueindex(1)); | ||
161 | return 0; | ||
190 | } | 162 | } |
191 | } | 163 | } |
192 | 164 | ||
193 | 165 | ||
194 | static int io_readfrom (lua_State *L) { | 166 | static int io_input (lua_State *L) { |
195 | return io_fromto(L, INFILE, "r"); | 167 | return g_iofile(L, IO_INPUT, "r"); |
196 | } | ||
197 | |||
198 | |||
199 | static int io_writeto (lua_State *L) { | ||
200 | return io_fromto(L, OUTFILE, "w"); | ||
201 | } | 168 | } |
202 | 169 | ||
203 | 170 | ||
204 | static int io_appendto (lua_State *L) { | 171 | static int io_output (lua_State *L) { |
205 | FILE *current = fopen(luaL_check_string(L, 1), "a"); | 172 | return g_iofile(L, IO_OUTPUT, "w"); |
206 | return setnewfile(L, current, OUTFILE); | ||
207 | } | 173 | } |
208 | 174 | ||
209 | 175 | ||
210 | |||
211 | /* | 176 | /* |
212 | ** {====================================================== | 177 | ** {====================================================== |
213 | ** READ | 178 | ** READ |
@@ -307,19 +272,18 @@ static int read_chars (lua_State *L, FILE *f, size_t n) { | |||
307 | } | 272 | } |
308 | 273 | ||
309 | 274 | ||
310 | static int io_read (lua_State *L) { | 275 | static int g_read (lua_State *L, FILE *f, int first) { |
311 | FILE *f = getopthandle(L, INFILE); | ||
312 | int nargs = lua_gettop(L) - 1; | 276 | int nargs = lua_gettop(L) - 1; |
313 | int success; | 277 | int success; |
314 | int n; | 278 | int n; |
315 | if (nargs == 0) { /* no arguments? */ | 279 | if (nargs == 0) { /* no arguments? */ |
316 | success = read_until(L, f, "\n", 1); /* read until \n (a line) */ | 280 | success = read_until(L, f, "\n", 1); /* read until \n (a line) */ |
317 | n = 2; /* will return n-1 results */ | 281 | n = first+1; /* to return 1 result */ |
318 | } | 282 | } |
319 | else { /* ensure stack space for all results and for auxlib's buffer */ | 283 | else { /* ensure stack space for all results and for auxlib's buffer */ |
320 | luaL_check_stack(L, nargs+LUA_MINSTACK, "too many arguments"); | 284 | luaL_check_stack(L, nargs+LUA_MINSTACK, "too many arguments"); |
321 | success = 1; | 285 | success = 1; |
322 | for (n = 1; n<=nargs && success; n++) { | 286 | for (n = first; nargs-- && success; n++) { |
323 | if (lua_type(L, n) == LUA_TNUMBER) { | 287 | if (lua_type(L, n) == LUA_TNUMBER) { |
324 | size_t l = (size_t)lua_tonumber(L, n); | 288 | size_t l = (size_t)lua_tonumber(L, n); |
325 | success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); | 289 | success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); |
@@ -360,18 +324,26 @@ static int io_read (lua_State *L) { | |||
360 | lua_pop(L, 1); /* remove last result */ | 324 | lua_pop(L, 1); /* remove last result */ |
361 | lua_pushnil(L); /* push nil instead */ | 325 | lua_pushnil(L); /* push nil instead */ |
362 | } | 326 | } |
363 | return n - 1; | 327 | return n - first; |
328 | } | ||
329 | |||
330 | |||
331 | static int io_read (lua_State *L) { | ||
332 | return g_read(L, getiofile(L, IO_INPUT), 1); | ||
333 | } | ||
334 | |||
335 | |||
336 | static int f_read (lua_State *L) { | ||
337 | return g_read(L, tofile(L, 1), 2); | ||
364 | } | 338 | } |
365 | 339 | ||
366 | /* }====================================================== */ | 340 | /* }====================================================== */ |
367 | 341 | ||
368 | 342 | ||
369 | static int io_write (lua_State *L) { | 343 | static int g_write (lua_State *L, FILE *f, int arg) { |
370 | FILE *f = getopthandle(L, OUTFILE); | 344 | int nargs = lua_gettop(L) - 1; |
371 | int nargs = lua_gettop(L)-1; | ||
372 | int arg; | ||
373 | int status = 1; | 345 | int status = 1; |
374 | for (arg=1; arg<=nargs; arg++) { | 346 | for (; nargs--; arg++) { |
375 | if (lua_type(L, arg) == LUA_TNUMBER) { | 347 | if (lua_type(L, arg) == LUA_TNUMBER) { |
376 | /* optimization: could be done exactly as for strings */ | 348 | /* optimization: could be done exactly as for strings */ |
377 | status = status && | 349 | status = status && |
@@ -388,10 +360,20 @@ static int io_write (lua_State *L) { | |||
388 | } | 360 | } |
389 | 361 | ||
390 | 362 | ||
391 | static int io_seek (lua_State *L) { | 363 | static int io_write (lua_State *L) { |
364 | return g_write(L, getiofile(L, IO_OUTPUT), 1); | ||
365 | } | ||
366 | |||
367 | |||
368 | static int f_write (lua_State *L) { | ||
369 | return g_write(L, tofile(L, 1), 2); | ||
370 | } | ||
371 | |||
372 | |||
373 | static int f_seek (lua_State *L) { | ||
392 | static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; | 374 | static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; |
393 | static const char *const modenames[] = {"set", "cur", "end", NULL}; | 375 | static const char *const modenames[] = {"set", "cur", "end", NULL}; |
394 | FILE *f = (FILE *)(luaL_check_userdata(L, 1, FILEHANDLE)); | 376 | FILE *f = tofile(L, 1); |
395 | int op = luaL_findstring(luaL_opt_string(L, 2, "cur"), modenames); | 377 | int op = luaL_findstring(luaL_opt_string(L, 2, "cur"), modenames); |
396 | long offset = luaL_opt_long(L, 3, 0); | 378 | long offset = luaL_opt_long(L, 3, 0); |
397 | luaL_arg_check(L, op != -1, 2, "invalid mode"); | 379 | luaL_arg_check(L, op != -1, 2, "invalid mode"); |
@@ -406,25 +388,60 @@ static int io_seek (lua_State *L) { | |||
406 | 388 | ||
407 | 389 | ||
408 | static int io_flush (lua_State *L) { | 390 | static int io_flush (lua_State *L) { |
409 | FILE *f = (lua_isnoneornil(L, 1)) ? (FILE *)(NULL) : | 391 | return pushresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0); |
410 | (FILE *)(luaL_check_userdata(L, 1, FILEHANDLE)); | 392 | } |
411 | return pushresult(L, fflush(f) == 0); | 393 | |
394 | |||
395 | static int f_flush (lua_State *L) { | ||
396 | return pushresult(L, fflush(tofile(L, 1)) == 0); | ||
412 | } | 397 | } |
413 | 398 | ||
399 | |||
414 | static const luaL_reg iolib[] = { | 400 | static const luaL_reg iolib[] = { |
415 | {"appendto", io_appendto}, | 401 | {"input", io_input}, |
402 | {"output", io_output}, | ||
416 | {"close", io_close}, | 403 | {"close", io_close}, |
417 | {"flush", io_flush}, | 404 | {"flush", io_flush}, |
418 | {"open", io_open}, | 405 | {"open", io_open}, |
419 | {"read", io_read}, | 406 | {"popen", io_popen}, |
420 | {"readfrom", io_readfrom}, | 407 | {"read", io_read}, |
421 | {"seek", io_seek}, | 408 | {"tmpfile", io_tmpfile}, |
422 | {"tmpfile", io_tmpfile}, | 409 | {"write", io_write}, |
423 | {"write", io_write}, | ||
424 | {"writeto", io_writeto}, | ||
425 | {NULL, NULL} | 410 | {NULL, NULL} |
426 | }; | 411 | }; |
427 | 412 | ||
413 | |||
414 | static const luaL_reg flib[] = { | ||
415 | {"flush", f_flush}, | ||
416 | {"read", f_read}, | ||
417 | {"seek", f_seek}, | ||
418 | {"write", f_write}, | ||
419 | {NULL, NULL} | ||
420 | }; | ||
421 | |||
422 | |||
423 | static void createmeta (lua_State *L) { | ||
424 | lua_pushliteral(L, FILEHANDLE); /* S: FH */ | ||
425 | lua_newtable(L); /* S: mt FH */ | ||
426 | /* close files when collected */ | ||
427 | lua_pushliteral(L, "__gc"); /* S: `gc' mt FH */ | ||
428 | lua_pushvalue(L, -2); /* S: mt `gc' mt FH */ | ||
429 | lua_pushcclosure(L, io_close, 1); /* S: close `gc' mt FH */ | ||
430 | lua_rawset(L, -3); /* S: mt FH */ | ||
431 | /* file methods */ | ||
432 | lua_pushliteral(L, "__gettable"); /* S: `gettable' mt FH */ | ||
433 | lua_pushvalue(L, -2); /* S: mt `gettable' mt FH */ | ||
434 | lua_rawset(L, -3); /* S: mt FH */ | ||
435 | lua_pushvalue(L, -1); /* S: mt mt FH */ | ||
436 | luaL_openlib(L, flib, 1); /* S: mt FH */ | ||
437 | /* put new metatable into registry */ | ||
438 | lua_rawset(L, LUA_REGISTRYINDEX); /* S: empty */ | ||
439 | /* meta table for CLOSEDFILEHANDLE */ | ||
440 | lua_pushliteral(L, CLOSEDFILEHANDLE); | ||
441 | lua_newtable(L); | ||
442 | lua_rawset(L, LUA_REGISTRYINDEX); | ||
443 | } | ||
444 | |||
428 | /* }====================================================== */ | 445 | /* }====================================================== */ |
429 | 446 | ||
430 | 447 | ||
@@ -613,29 +630,19 @@ static const luaL_reg syslib[] = { | |||
613 | 630 | ||
614 | 631 | ||
615 | LUALIB_API int lua_iolibopen (lua_State *L) { | 632 | LUALIB_API int lua_iolibopen (lua_State *L) { |
616 | lua_pushliteral(L, FILEHANDLE); | 633 | createmeta(L); |
617 | lua_newtable(L); /* meta table for FILEHANDLE */ | 634 | luaL_opennamedlib(L, "os", syslib, 0); |
618 | /* close files when collected */ | 635 | lua_pushliteral(L, FILEHANDLE); /* S: FH */ |
619 | lua_pushliteral(L, "__gc"); | 636 | lua_rawget(L, LUA_REGISTRYINDEX); /* S: mt */ |
620 | lua_pushcfunction(L, file_collect); | 637 | lua_pushvalue(L, -1); /* S: mt mt */ |
621 | lua_rawset(L, -3); | 638 | luaL_opennamedlib(L, "io", iolib, 1); /* S: mt */ |
622 | /* put new metatable into registry */ | 639 | lua_pushliteral(L, "io"); /* S: `io' mt */ |
623 | lua_rawset(L, LUA_REGISTRYINDEX); | 640 | lua_gettable(L, LUA_GLOBALSINDEX); /* S: io mt */ |
624 | /* meta table for CLOSEDFILEHANDLE */ | 641 | /* put predefined file handles into `io' table */ |
625 | lua_pushliteral(L, CLOSEDFILEHANDLE); | 642 | registerfile(L, stdin, "stdin", IO_INPUT); |
626 | lua_newtable(L); | 643 | registerfile(L, stdout, "stdout", IO_OUTPUT); |
627 | lua_rawset(L, LUA_REGISTRYINDEX); | 644 | registerfile(L, stderr, "stderr", NULL); |
628 | luaL_opennamedlib(L, "os", syslib); | 645 | lua_pop(L, 2); /* S: empty */ |
629 | lua_pushliteral(L, "io"); | ||
630 | lua_newtable(L); | ||
631 | luaL_openlib(L, iolib); | ||
632 | /* predefined file handles */ | ||
633 | newfilewithname(L, stdin, basicfiles[INFILE]); | ||
634 | newfilewithname(L, stdout, basicfiles[OUTFILE]); | ||
635 | newfilewithname(L, stderr, "stderr"); | ||
636 | lua_settable(L, LUA_GLOBALSINDEX); | ||
637 | resetfile(L, INFILE); | ||
638 | resetfile(L, OUTFILE); | ||
639 | return 0; | 646 | return 0; |
640 | } | 647 | } |
641 | 648 | ||