├── test ├── echo.lazy ├── hello.out ├── echo.in ├── echo.out └── hello.lazy ├── run_tests ├── Makefile ├── README.md ├── LICENSE └── lazyk.c /test/echo.lazy: -------------------------------------------------------------------------------- 1 | i 2 | -------------------------------------------------------------------------------- /test/hello.out: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /test/echo.in: -------------------------------------------------------------------------------- 1 | Hello, Lazy K! 2 | -------------------------------------------------------------------------------- /test/echo.out: -------------------------------------------------------------------------------- 1 | Hello, Lazy K! 2 | -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | for test in test/*.lazy 6 | do 7 | if [ -e ${test%.lazy}.in ] 8 | then 9 | $1 $test <${test%.lazy}.in |diff -u ${test%.lazy}.out - 10 | else 11 | $1 $test |diff -u ${test%.lazy}.out - 12 | fi 13 | done 14 | 15 | echo 'All tests passed' 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -std=c99 -Wall -O2 2 | PREFIX = /usr/local 3 | 4 | lazyk: lazyk.c 5 | $(CC) $(CFLAGS) -o $@ $< 6 | 7 | test: lazyk 8 | ./run_tests ./lazyk 9 | 10 | install: lazyk 11 | mkdir -p $(PREFIX)/bin 12 | cp $< $(PREFIX)/bin/ 13 | 14 | uninstall: 15 | rm -f $(PREFIX)/bin/lazyk 16 | 17 | clean: 18 | rm -f lazyk 19 | 20 | .PHONY: test install uninstall clean 21 | -------------------------------------------------------------------------------- /test/hello.lazy: -------------------------------------------------------------------------------- 1 | K(S(S(S(S(S(S(SI`S`K(S(S`KS(S`KK(S`KS(S(S`KS(S`K`S(SI`KK)(S`KKK)))`K`K(SI`K0)))))`K(S`K`S(S`KS(S`K`SI(S`KK(S`K(S(S(SSS)(SS(SSI(SS0))))S(S`KSK))(SI`K(S`KSK))))))(S`KKK)))(SII)`K(SII(SII(S(S`KSK)I))))(S`K`S(S(S(SSS)(SS0))S)(SSSS))(SS(SS0))(S(SI(SS0))(SS(SS(SS(SS`S(SSS)(SS0))))))(SS(SS(SS(SSSSSS(SS0))))))`S(S(S(SS(SS0))(SS0))S))`K(SS0))`K(SS(SS(S(SSS)(SS(SS0))))))I(SSSSSS(SS0)))I(S(SI(SS0))(SS(SS(SS(SS`S(SSS)(SS0))))))(SS(S(S(S(SSS)(SS0))S)(SSSS(SS(SS0)))))(S(SSS)(S(SSS)(SS0)))`K0) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy K interpreter 2 | 3 | This is an interprefer of the [Lazy K](https://tromp.github.io/cl/lazy-k.html) 4 | programming language. This implementation is 2-3 times faster than the C++ 5 | implementation included in the original Lazy K distribution. 6 | 7 | It fully supports all Lazy K syntaxes, combinator-calculus style, Unlambda 8 | style, Iota style, and Jot style (and mixture of them). 9 | 10 | ## Usage 11 | 12 | ```sh 13 | $ lazyk [options] [program-file] 14 | ``` 15 | 16 | If _program-file_ is not specified, program is read from the standard input. 17 | 18 | Options: 19 | - `-h`: Print help and exit. 20 | - `-u`: Disable stdout buffering. 21 | - `-v`: Print version and exit. 22 | - `-v0` (default): Do not print any debug information. 23 | - `-v1`: Print some statistics after execution. 24 | - `-v2`: Print logs for garbage collections. 25 | 26 | ## License 27 | 28 | This software is released under the [MIT License](LICENSE). 29 | 30 | ## References 31 | 32 | - [Lazy K language description](https://tromp.github.io/cl/lazy-k.html) 33 | - [Lazy K in esolangs.org](https://esolangs.org/wiki/Lazy_K) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008 irori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lazyk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Lazy K interpreter 3 | * 4 | * Copyright 2008 irori 5 | * This code is licensed under the MIT License (see LICENSE file for details). 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define VERSION "1.0.0" 17 | 18 | #define INITIAL_HEAP_SIZE 128*1024 19 | #define RDSTACK_SIZE 100000 20 | 21 | // Verbosity levels 22 | enum { 23 | V_NONE, 24 | V_STATS, 25 | V_GC, 26 | } verbosity = V_NONE; 27 | 28 | /********************************************************************** 29 | * Storage management 30 | **********************************************************************/ 31 | 32 | /* TAG STRUCTURE 33 | * 34 | * -------- -------- -------- ------00 Pair 35 | * -------- -------- -------- ------01 Int 36 | * -------- -------- -------- ------10 Combinator 37 | * -------- -------- -------- -----011 Character 38 | * -------- -------- -------- -----111 Miscellaneous 39 | */ 40 | 41 | struct tagPair; 42 | typedef struct tagPair *Cell; 43 | #define CELL(x) ((Cell)(x)) 44 | #define TAG(c) ((intptr_t)(c) & 0x03) 45 | 46 | /* pair */ 47 | typedef struct tagPair { 48 | Cell car; 49 | Cell cdr; 50 | } Pair; 51 | #define ispair(c) (TAG(c) == 0) 52 | #define car(c) ((c)->car) 53 | #define cdr(c) ((c)->cdr) 54 | #define SET(c,fst,snd) ((c)->car = (fst), (c)->cdr = (snd)) 55 | 56 | /* integer */ 57 | #define isint(c) (TAG(c) == 1) 58 | #define mkint(n) CELL(((n) << 2) + 1) 59 | #define intof(c) ((intptr_t)(c) >> 2) 60 | 61 | /* combinator */ 62 | #define iscomb(c) (TAG(c) == 2) 63 | #define mkcomb(n) CELL(((n) << 2) + 2) 64 | #define combof(c) ((intptr_t)(c) >> 2) 65 | #define COMB_S mkcomb(0) 66 | #define COMB_K mkcomb(1) 67 | #define COMB_I mkcomb(2) 68 | #define COMB_IOTA mkcomb(3) 69 | #define COMB_KI mkcomb(4) 70 | #define COMB_READ mkcomb(5) 71 | #define COMB_WRITE mkcomb(6) 72 | #define COMB_INC mkcomb(7) 73 | #define COMB_CONS mkcomb(8) 74 | 75 | /* character */ 76 | #define ischar(c) (((intptr_t)(c) & 0x07) == 0x03) 77 | #define mkchar(n) CELL(((n) << 3) + 0x03) 78 | #define charof(c) ((intptr_t)(c) >> 3) 79 | 80 | /* immediate objects */ 81 | #define isimm(c) (((intptr_t)(c) & 0x07) == 0x07) 82 | #define mkimm(n) CELL(((n) << 3) + 0x07) 83 | #define NIL mkimm(0) 84 | #define COPIED mkimm(1) 85 | #define UNUSED_MARKER mkimm(2) 86 | 87 | Pair *heap_area, *free_ptr; 88 | int heap_size, next_heap_size; 89 | 90 | double total_gc_time = 0.0; 91 | 92 | void gc_run(Cell *save1, Cell *save2); 93 | void rs_copy(void); 94 | Cell copy_cell(Cell c); 95 | 96 | void errexit(char *fmt, ...) 97 | { 98 | va_list arg; 99 | va_start(arg, fmt); 100 | vfprintf(stderr, fmt, arg); 101 | va_end(arg); 102 | 103 | exit(1); 104 | } 105 | 106 | void storage_init(int size) 107 | { 108 | heap_size = size; 109 | heap_area = malloc(sizeof(Pair) * heap_size); 110 | if (heap_area == NULL) 111 | errexit("Cannot allocate heap storage (%d cells)\n", heap_size); 112 | assert(((intptr_t)heap_area & 3) == 0 && (sizeof(Pair) & 3) == 0); 113 | 114 | free_ptr = heap_area; 115 | heap_area += heap_size; 116 | next_heap_size = heap_size * 3 / 2; 117 | } 118 | 119 | Cell pair(Cell fst, Cell snd) 120 | { 121 | Cell c; 122 | if (free_ptr >= heap_area) 123 | gc_run(&fst, &snd); 124 | 125 | assert(free_ptr < heap_area); 126 | c = free_ptr++; 127 | car(c) = fst; 128 | cdr(c) = snd; 129 | return c; 130 | } 131 | 132 | Cell alloc(int n) 133 | { 134 | Cell p; 135 | if (free_ptr + n > heap_area) 136 | gc_run(NULL, NULL); 137 | 138 | assert(free_ptr + n <= heap_area); 139 | p = free_ptr; 140 | free_ptr += n; 141 | return p; 142 | } 143 | 144 | 145 | void gc_run(Cell *save1, Cell *save2) 146 | { 147 | static Pair* free_area = NULL; 148 | int num_alive; 149 | Pair *scan; 150 | clock_t start = clock(); 151 | 152 | if (free_area == NULL) { 153 | free_area = malloc(sizeof(Pair) * next_heap_size); 154 | if (free_area == NULL) 155 | errexit("Cannot allocate heap storage (%d cells)\n", 156 | next_heap_size); 157 | } 158 | 159 | free_ptr = scan = free_area; 160 | free_area = heap_area - heap_size; 161 | heap_area = free_ptr + next_heap_size; 162 | 163 | rs_copy(); 164 | if (save1) 165 | *save1 = copy_cell(*save1); 166 | if (save2) 167 | *save2 = copy_cell(*save2); 168 | 169 | while (scan < free_ptr) { 170 | car(scan) = copy_cell(car(scan)); 171 | cdr(scan) = copy_cell(cdr(scan)); 172 | scan++; 173 | } 174 | 175 | num_alive = free_ptr - (heap_area - next_heap_size); 176 | if (verbosity >= V_GC) 177 | fprintf(stderr, "GC: %d / %d\n", num_alive, heap_size); 178 | 179 | if (heap_size != next_heap_size || num_alive * 8 > next_heap_size) { 180 | heap_size = next_heap_size; 181 | if (num_alive * 8 > next_heap_size) 182 | next_heap_size = num_alive * 8; 183 | 184 | free(free_area); 185 | free_area = NULL; 186 | } 187 | 188 | total_gc_time += (clock() - start) / (double)CLOCKS_PER_SEC; 189 | } 190 | 191 | Cell copy_cell(Cell c) 192 | { 193 | Cell r; 194 | 195 | if (!ispair(c)) 196 | return c; 197 | if (car(c) == COPIED) 198 | return cdr(c); 199 | 200 | r = free_ptr++; 201 | car(r) = car(c); 202 | if (car(c) == COMB_I) { 203 | Cell tmp = cdr(c); 204 | while (ispair(tmp) && car(tmp) == COMB_I) 205 | tmp = cdr(tmp); 206 | cdr(r) = tmp; 207 | } 208 | else 209 | cdr(r) = cdr(c); 210 | car(c) = COPIED; 211 | cdr(c) = r; 212 | return r; 213 | } 214 | 215 | /********************************************************************** 216 | * Reduction Machine 217 | **********************************************************************/ 218 | 219 | typedef struct { 220 | Cell *sp; 221 | Cell stack[RDSTACK_SIZE]; 222 | } RdStack; 223 | 224 | RdStack rd_stack; 225 | 226 | void rs_init(void) 227 | { 228 | int i; 229 | rd_stack.sp = rd_stack.stack + RDSTACK_SIZE; 230 | 231 | for (i = 0; i < RDSTACK_SIZE; i++) 232 | rd_stack.stack[i] = UNUSED_MARKER; 233 | } 234 | 235 | void rs_copy(void) 236 | { 237 | Cell *c; 238 | for (c = rd_stack.stack + RDSTACK_SIZE - 1; c >= rd_stack.sp; c--) 239 | *c = copy_cell(*c); 240 | } 241 | 242 | int rs_max_depth(void) 243 | { 244 | int i; 245 | for (i = 0; i < RDSTACK_SIZE; i++) { 246 | if (rd_stack.stack[i] != UNUSED_MARKER) 247 | break; 248 | } 249 | return RDSTACK_SIZE - i; 250 | } 251 | 252 | void rs_push(Cell c) 253 | { 254 | if (rd_stack.sp <= rd_stack.stack) 255 | errexit("runtime error: stack overflow\n"); 256 | *--rd_stack.sp = c; 257 | } 258 | 259 | #define TOP (*rd_stack.sp) 260 | #define POP (*rd_stack.sp++) 261 | #define POP_ (rd_stack.sp++) 262 | #define PUSH(c) rs_push(c) 263 | #define PUSHED(n) (*(rd_stack.sp+(n))) 264 | #define DROP(n) (rd_stack.sp += (n)) 265 | #define ARG(n) cdr(PUSHED(n)) 266 | #define APPLICABLE(n) (bottom - rd_stack.sp > (n)) 267 | 268 | /********************************************************************** 269 | * Loader 270 | **********************************************************************/ 271 | 272 | Cell read_one(FILE *fp, int i_is_iota); 273 | Cell read_many(FILE *fp, int closing_char); 274 | 275 | Cell load_program(const char *fname) 276 | { 277 | FILE *fp; 278 | Cell c; 279 | 280 | if (fname == NULL) 281 | fp = stdin; 282 | else { 283 | fp = fopen(fname, "r"); 284 | if (fp == NULL) 285 | errexit("cannot open %s\n", fname); 286 | } 287 | 288 | c = read_many(fp, EOF); 289 | 290 | if (fname != NULL) 291 | fclose(fp); 292 | 293 | return c; 294 | } 295 | 296 | int next_char(FILE *fp) 297 | { 298 | int c; 299 | do { 300 | c = fgetc(fp); 301 | if (c == '#') { 302 | while (c = fgetc(fp), c != '\n' && c != EOF) 303 | ; 304 | } 305 | } while (isspace(c)); 306 | return c; 307 | } 308 | 309 | Cell read_many(FILE *fp, int closing_char) 310 | { 311 | int c; 312 | Cell obj; 313 | 314 | c = next_char(fp); 315 | if (c == closing_char) 316 | return COMB_I; 317 | ungetc(c, fp); 318 | 319 | PUSH(read_one(fp, 0)); 320 | while ((c = next_char(fp)) != closing_char) { 321 | ungetc(c, fp); 322 | obj = read_one(fp, 0); 323 | obj = pair(TOP, obj); 324 | TOP = obj; 325 | } 326 | return POP; 327 | } 328 | 329 | Cell read_one(FILE *fp, int i_is_iota) 330 | { 331 | int c; 332 | Cell obj; 333 | 334 | c = next_char(fp); 335 | switch (c) { 336 | case '`': case '*': 337 | PUSH(read_one(fp, c == '*')); 338 | obj = read_one(fp, c == '*'); 339 | obj = pair(TOP, obj); 340 | POP_; 341 | return obj; 342 | case '(': 343 | obj = read_many(fp, ')'); 344 | return obj; 345 | case 's': case 'S': return COMB_S; 346 | case 'k': case 'K': return COMB_K; 347 | case 'i': return i_is_iota ? COMB_IOTA : COMB_I; 348 | case 'I': return COMB_I; 349 | case '0': case '1': { 350 | obj = COMB_I; 351 | do { 352 | if (c == '0') 353 | obj = pair(pair(obj, COMB_S), COMB_K); 354 | else 355 | obj = pair(COMB_S, pair(COMB_K, obj)); 356 | c = next_char(fp); 357 | } while (c == '0' || c == '1'); 358 | ungetc(c, fp); 359 | return obj; 360 | } 361 | case EOF: 362 | errexit("parse error: unexpected EOF\n"); 363 | return NULL; 364 | default: 365 | errexit("parse error: %c\n", c); 366 | return NULL; 367 | } 368 | } 369 | 370 | /********************************************************************** 371 | * Reducer 372 | **********************************************************************/ 373 | 374 | int reductions; 375 | 376 | void eval(Cell root) 377 | { 378 | Cell *bottom = rd_stack.sp; 379 | PUSH(root); 380 | 381 | for (;;) { 382 | while (ispair(TOP)) 383 | PUSH(car(TOP)); 384 | 385 | if (TOP == COMB_I && APPLICABLE(1)) 386 | { /* I x -> x */ 387 | POP_; 388 | TOP = cdr(TOP); 389 | } 390 | else if (TOP == COMB_S && APPLICABLE(3)) 391 | { /* S f g x -> f x (g x) */ 392 | Cell a = alloc(2); 393 | SET(a+0, ARG(1), ARG(3)); /* f x */ 394 | SET(a+1, ARG(2), ARG(3)); /* g x */ 395 | DROP(3); 396 | SET(TOP, a+0, a+1); /* f x (g x) */ 397 | } 398 | else if (TOP == COMB_K && APPLICABLE(2)) 399 | { /* K x y -> I x */ 400 | Cell x = ARG(1); 401 | DROP(2); 402 | SET(TOP, COMB_I, x); 403 | TOP = cdr(TOP); /* shortcut reduction of I */ 404 | } 405 | else if (TOP == COMB_IOTA && APPLICABLE(1)) 406 | { /* IOTA x -> x S K */ 407 | Cell xs = pair(ARG(1), COMB_S); 408 | POP_; 409 | SET(TOP, xs, COMB_K); 410 | } 411 | else if (TOP == COMB_KI && APPLICABLE(2)) 412 | { /* KI x y -> I y */ 413 | DROP(2); 414 | car(TOP) = COMB_I; 415 | } 416 | else if (TOP == COMB_CONS && APPLICABLE(3)) 417 | { /* CONS x y f -> f x y */ 418 | Cell fx, y; 419 | fx = pair(ARG(3), ARG(1)); 420 | y = ARG(2); 421 | DROP(3); 422 | SET(TOP, fx, y); 423 | } 424 | else if (TOP == COMB_READ && APPLICABLE(2)) 425 | { /* READ NIL f -> CONS CHAR(c) (READ NIL) f */ 426 | intptr_t c = getchar(); 427 | Cell a = alloc(2); 428 | SET(a+0, COMB_CONS, mkchar(c == EOF ? 256 : c)); 429 | SET(a+1, COMB_READ, NIL); 430 | POP_; 431 | SET(TOP, a+0, a+1); 432 | } 433 | else if (TOP == COMB_WRITE && APPLICABLE(1)) 434 | { /* WRITE x -> putc(eval((car x) INC NUM(0))); WRITE (cdr x) */ 435 | Cell a = alloc(3); 436 | SET(a+0, ARG(1), COMB_K); /* (car x) */ 437 | SET(a+1, a+0, COMB_INC); /* (car x) INC */ 438 | SET(a+2, a+1, mkint(0)); /* (car x) INC NUM(0) */ 439 | POP_; 440 | eval(a+2); 441 | 442 | if (!isint(TOP)) 443 | errexit("invalid output format (result was not a number)\n"); 444 | if (intof(TOP) >= 256) 445 | return; 446 | 447 | putchar(intof(TOP)); 448 | POP_; 449 | a = pair(cdr(TOP), COMB_KI); 450 | cdr(TOP) = a; 451 | } 452 | else if (TOP == COMB_INC && APPLICABLE(1)) 453 | { /* INC x -> eval(x)+1 */ 454 | Cell c = ARG(1); 455 | POP_; 456 | eval(c); 457 | 458 | c = POP; 459 | if (!isint(c)) 460 | errexit("invalid output format (attempted to apply inc to a non-number)\n"); 461 | SET(TOP, COMB_I, mkint(intof(c) + 1)); 462 | } 463 | else if (ischar(TOP) && APPLICABLE(2)) { 464 | intptr_t c = charof(TOP); 465 | if (c <= 0) { /* CHAR(0) f z -> z */ 466 | Cell z = ARG(2); 467 | DROP(2); 468 | SET(TOP, COMB_I, z); 469 | } 470 | else { /* CHAR(n+1) f z -> f (CHAR(n) f z) */ 471 | Cell a = alloc(2); 472 | Cell f = ARG(1); 473 | SET(a+0, mkchar(c-1), f); /* CHAR(n) f */ 474 | SET(a+1, a+0, ARG(2)); /* CHAR(n) f z */ 475 | DROP(2); 476 | SET(TOP, f, a+1); /* f (CHAR(n) f z) */ 477 | } 478 | } 479 | else if (isint(TOP) && APPLICABLE(1)) 480 | errexit("invalid output format (attempted to apply a number)\n"); 481 | else 482 | return; 483 | reductions++; 484 | } 485 | } 486 | 487 | void eval_print(Cell root) 488 | { 489 | eval(pair(COMB_WRITE, 490 | pair(root, 491 | pair(COMB_READ, NIL)))); 492 | } 493 | 494 | /********************************************************************** 495 | * Main 496 | **********************************************************************/ 497 | 498 | void help(const char *progname) { 499 | printf("Usage: %s [options] sourcefile\n", progname); 500 | printf(" -h print this help and exit\n"); 501 | printf(" -u disable stdout buffering\n"); 502 | printf(" -v print version and exit\n"); 503 | printf(" -v[0-2] set verbosity level (default: 0)\n"); 504 | } 505 | 506 | int main(int argc, char *argv[]) 507 | { 508 | Cell root; 509 | clock_t start; 510 | char *prog_file = NULL; 511 | int i; 512 | 513 | for (i = 1; i < argc; i++) { 514 | if (argv[i][0] == '-' && argv[i][1] == 'v' && isdigit(argv[i][2])) { 515 | verbosity = argv[i][2] - '0'; 516 | } else if (strcmp(argv[i], "-h") == 0) { 517 | help(argv[0]); 518 | return 0; 519 | } else if (strcmp(argv[i], "-u") == 0) { 520 | setbuf(stdout, NULL); 521 | } else if (strcmp(argv[i], "-v") == 0) { 522 | printf("Lazy K interpreter " VERSION " by irori\n"); 523 | return 0; 524 | } else if (argv[i][0] == '-') { 525 | fprintf(stderr, "bad option %s (Try -h for more information).\n", argv[i]); 526 | return 1; 527 | } else { 528 | prog_file = argv[i]; 529 | } 530 | } 531 | 532 | storage_init(INITIAL_HEAP_SIZE); 533 | rs_init(); 534 | 535 | root = load_program(prog_file); 536 | 537 | start = clock(); 538 | eval_print(root); 539 | 540 | if (verbosity >= V_STATS) { 541 | double evaltime = (clock() - start) / (double)CLOCKS_PER_SEC; 542 | 543 | printf("\n%d reductions\n", reductions); 544 | printf(" total eval time --- %5.2f sec.\n", evaltime - total_gc_time); 545 | printf(" total gc time --- %5.2f sec.\n", total_gc_time); 546 | printf(" max stack depth --- %d\n", rs_max_depth()); 547 | } 548 | return 0; 549 | } 550 | --------------------------------------------------------------------------------