Compare commits
634 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8dc2939b4 | |||
| 6e868b568d | |||
| 73e4ad7b87 | |||
| b86ce6fe62 | |||
| 38316cec0b | |||
| ee7f488ebb | |||
| 02ea20cdb5 | |||
| 20cffbddfa | |||
| 4b2b573420 | |||
| ac5c97bfd3 | |||
| e1c24daac4 | |||
| a62f27b104 | |||
| 1055569dd6 | |||
| 1aea1c14d2 | |||
| 081395be5f | |||
| 35a599b284 | |||
| eda3037855 | |||
| 47c181546a | |||
| a8a5d6aaa6 | |||
| 84b255a51b | |||
| 9efdeffcac | |||
| d34b9d8a48 | |||
| 1bf8015da1 | |||
| 3ccbeb3978 | |||
| 0c719025cd | |||
| 08617378e0 | |||
| e6844ad1e8 | |||
| 3a4e217891 | |||
| 0f293fdbbc | |||
| 45faea7620 | |||
| e5a61b5638 | |||
| 5285b61320 | |||
| 5950630cf7 | |||
| 78e431f2b9 | |||
| c7dce8288f | |||
| c10053253c | |||
| a3c7cf2efa | |||
| dfa1491093 | |||
| 99c1adc434 | |||
| 5585b496fb | |||
| acd4f8487d | |||
| 7f34ccf955 | |||
| f63ff5953e | |||
| b9d37a5b4c | |||
| 23b8eec74a | |||
| bb6b6d17ec | |||
| 53acacbadf | |||
| 2ebc52891c | |||
| 3b2ef7927b | |||
| d5ced343c4 | |||
| 3ac8cab275 | |||
| 6c5e9db34b | |||
| 78871641d0 | |||
| dacbec677a | |||
| 75ed88915d | |||
| f2d1f23774 | |||
| edd90054d7 | |||
| 610e2df5f5 | |||
| 32c0b45255 | |||
| 75a3f220df | |||
| 116b54836f | |||
| 8787973de0 | |||
| 4d910dd069 | |||
| f100adead3 | |||
| 7d2cf1e687 | |||
| 49728307f3 | |||
| 20dc502438 | |||
| ce7bfc5e3f | |||
| 6e98bdd16b | |||
| 6ee365bacc | |||
| 20d8236325 | |||
| c39ee7cec0 | |||
| 9e4252173b | |||
| 2b80ba6789 | |||
| d7247f97c5 | |||
| 02df7f1c1f | |||
| b6b09b2fb1 | |||
| 0677180456 | |||
| b6952444ab | |||
| 6c3a071a02 | |||
| d7d03b4af8 | |||
| 49c5cb6a22 | |||
| 53d805a832 | |||
| b99ad5def1 | |||
| 0d44b8697b | |||
| 1a7e537921 | |||
| 807df0f3a8 | |||
| d7dd628fc9 | |||
| 1f57ba28dd | |||
| 92bd366380 | |||
| 7b93c5b4ac | |||
| 3ba8194ddb | |||
| 037565c41e | |||
| f8d12b1a93 | |||
| 518d4d8d65 | |||
| d64602cb00 | |||
| 4709248828 | |||
| 5ecf81412e | |||
| ff4db34f7b | |||
| 0f848071c2 | |||
| 6b3351b324 | |||
| 760c1ee6da | |||
| 5ab6876ea1 | |||
| 6268abda8c | |||
| d25bd325b7 | |||
| 01c04feea5 | |||
| 6fc689c46c | |||
| eccb0c4dc9 | |||
| e43823740f | |||
| 526982a564 | |||
| 252514943e | |||
| 32686fac62 | |||
| 52a627c747 | |||
| d91e7eb979 | |||
| cca3b76baa | |||
| 24e31997fc | |||
| 646710e180 | |||
| b38327b841 | |||
| fd912b2eb1 | |||
| 0e55f83d56 | |||
| 4725145d1c | |||
| edf8818f51 | |||
| 6211be8a8f | |||
| f50ddf48db | |||
| 76e01f12d2 | |||
| 406bced450 | |||
| 409dc86a92 | |||
| 4184221428 | |||
| 8cf8b36a26 | |||
| de87050188 | |||
| a1ec0cc611 | |||
| 8e5550bfa7 | |||
| 6ee21e10af | |||
| 5c44532790 | |||
| cb66c1ab19 | |||
| a28d24ba68 | |||
| 523349a204 | |||
| b185f1df3a | |||
| 5da5a61a42 | |||
| 6e9205abc4 | |||
| f61004fb5d | |||
| 321030c8d3 | |||
| 98fc89e84f | |||
| 778d00677d | |||
| ba3dbb7f02 | |||
| 7285109115 | |||
| 4755774edd | |||
| d215d837f6 | |||
| ad3c1e5a60 | |||
| d6bf5ee500 | |||
| 4b176eb868 | |||
| dceb31f542 | |||
| 075b0b5691 | |||
| 3c51b8d2ee | |||
| a46753f453 | |||
| 9070b5c9cc | |||
| ab06702e5e | |||
| ffe1fa3aac | |||
| 1a1a475dd8 | |||
| 463e3634ba | |||
| 5f8ca47ef0 | |||
| 837b887490 | |||
| c76e1d3c8e | |||
| 315f5b22d3 | |||
| dfae593e86 | |||
| d572f3a129 | |||
| c461fd138e | |||
| 6ecbe2feb1 | |||
| 80d3c6ec7d | |||
| e09806c716 | |||
| 1a772597cb | |||
| 33b3e1fc29 | |||
| 4e3f5cfbc6 | |||
| e35d4e3f70 | |||
| b4529499d6 | |||
| 7745dc24e2 | |||
| be25385d02 | |||
| 79889cd8e1 | |||
| 234759158c | |||
| 00fda29606 | |||
| 2a2840bdf2 | |||
| 06373f5126 | |||
| e69dad5fb5 | |||
| 7459057df9 | |||
| 176969c956 | |||
| cde2efacf1 | |||
| d7a7b3218c | |||
| be3bb12f28 | |||
| 032916d4fa | |||
| f3cc0cc7ad | |||
| 905337f963 | |||
| 9a95a837f6 | |||
| 8547248ea2 | |||
| a02f998fc6 | |||
| e904003e6e | |||
| 990e04f957 | |||
| d8aed9dd7a | |||
| a73d24b171 | |||
| 3ebba83bce | |||
| 6b3bfa2a11 | |||
| 867806155e | |||
| a711333a2e | |||
| af3e946bd4 | |||
| 22a36fa630 | |||
| 6d9a379c92 | |||
| dd6404c786 | |||
| 34874ef663 | |||
| 9bc8e8ca05 | |||
| 7f367cfc49 | |||
| fe999acf2c | |||
| 2ed1a1842b | |||
| bb9493d0cc | |||
| f279bf163e | |||
| 6834d9f47b | |||
| 8051faa2bf | |||
| f30e687a79 | |||
| 2b6e46576b | |||
| dc06c03112 | |||
| e8f5d3e445 | |||
| 76ce0945f7 | |||
| 340b99bad7 | |||
| 93dac956fb | |||
| 307027d23d | |||
| 896844e772 | |||
| 9fc20077a1 | |||
| d2a4adebdd | |||
| fd8e32e12b | |||
| 1e62a51c15 | |||
| 0170baa0f5 | |||
| fe5c8e9619 | |||
| 7164e8816c | |||
| e0f3b028fc | |||
| 522b5983bd | |||
| f1e2163277 | |||
| bbdf498cf3 | |||
| f75c991ed2 | |||
| a7836143cc | |||
| ba9b9cb28f | |||
| ef1baa11a8 | |||
| cfddbd60b9 | |||
| 0760141caf | |||
| b9e780e659 | |||
| e41ddc664c | |||
| 3b641ac793 | |||
| a1a62b6794 | |||
| a18d92534f | |||
| ec0963e26f | |||
| be992131b1 | |||
| 0e3960321f | |||
| 61d34fef7d | |||
| 581f1585e6 | |||
| 531cb1c249 | |||
| d1b468f35b | |||
| ff9cf80c66 | |||
| d63b18fd76 | |||
| 019470faf1 | |||
| 302430d57d | |||
| 62ef0d699d | |||
| 866de759dd | |||
| b1d6b6de44 | |||
| 7e357eea62 | |||
| d6b4c79736 | |||
| d066344af8 | |||
| f41dba069e | |||
| 703ecf6829 | |||
| ba479a1b99 | |||
| 24e6a293b0 | |||
| 28f464c4dc | |||
| 9fb611aa20 | |||
| 56d6d06d15 | |||
| d9f7e5b1ad | |||
| 0f54e01ef3 | |||
| 63f5db00b3 | |||
| 9745a5d909 | |||
| 0bb4c96481 | |||
| 1757298eb4 | |||
| 54041552d4 | |||
| 5302907dcf | |||
| eb4b17f078 | |||
| 33d70d6d1a | |||
| 9df9ad5dd1 | |||
| 34dc828ead | |||
| 29bc2c62a3 | |||
| 8eb2d77ea3 | |||
| 53bcf90d2a | |||
| f347b15146 | |||
| 115ce26ce9 | |||
| 227944b3fb | |||
| 0d01afcc9f | |||
| 00c76b41f1 | |||
| 08e0979cdd | |||
| f04f5822ec | |||
| 80d47879e9 | |||
| 985eb3d19d | |||
| 45734ab393 | |||
| c100cf349d | |||
| 8144122d2c | |||
| 188ea354ee | |||
| 2fc6bbfe10 | |||
| 847d85605e | |||
| 9c29392389 | |||
| a7b6e6f8d2 | |||
| a16ac70e4a | |||
| ab2e3f0528 | |||
| 974835a8ef | |||
| 457a656073 | |||
| 9e63e1402e | |||
| e4ded4f746 | |||
| 4e3af837e6 | |||
| ca12722c93 | |||
| d96123ab02 | |||
| f2d6d63017 | |||
| 905b2af7fa | |||
| 9307473d08 | |||
| 10a596a4cd | |||
| 609fb21505 | |||
| 7650a4a441 | |||
| f51d6023ae | |||
| 99454227d5 | |||
| 75358e5d35 | |||
| 51b272dda8 | |||
| 7f282e5460 | |||
| cff21b40f7 | |||
| 4f432da2b9 | |||
| e4b4b4fb79 | |||
| c04678c426 | |||
| 9bba40f155 | |||
| f66cd1fdb1 | |||
| f41ea96d17 | |||
| d84e690ef3 | |||
| 4b25a07699 | |||
| 3736214c5a | |||
| 78cbb7b36f | |||
| 2c87d6bf9e | |||
| 691c213d17 | |||
| fa136cb70b | |||
| 76dd01afcd | |||
| 4283fab816 | |||
| 03d4c192c2 | |||
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf | |||
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a | |||
| c39970fa7e | |||
| 14bb9e942b | |||
| 9451958218 | |||
| 91fdc1926e | |||
| 9a3abdf1b6 | |||
| ac3e690f87 | |||
| f0a152a17a | |||
| ca89931ca9 | |||
| d2e8aed4f7 | |||
| 8138cd2a80 | |||
| 6786666cf4 | |||
| 35e794701a | |||
| 52ef134be6 | |||
| 624318d84e | |||
| aa1338cd51 | |||
| c9db4b84e3 | |||
| e7e9330b71 | |||
| 8eb25bbc86 | |||
| efc92d434b | |||
| 4151f3f5e2 | |||
| f028485caa | |||
| ab07405cda | |||
| 47be0c66cf | |||
| 50e7168214 | |||
| 0a9543543d | |||
| 775751c67b | |||
| 4aaffd6c44 | |||
| 924051fbcd | |||
| 5a9b6525a2 | |||
| 8c66d90532 | |||
| 4aa0113c6a | |||
| d035fa0d5e | |||
| cf73b5c98d | |||
| 8346e28340 | |||
| 9c2eca40d7 | |||
| c3198e4c79 | |||
| 99e0190b9c | |||
| d4f63a3837 | |||
| 389d48b646 | |||
| 1f7b9131fc | |||
| dce49fd2b7 | |||
| 8ee0bb5701 | |||
| b2b0bb04c5 | |||
| f3abf5e77c | |||
| 34b7799177 | |||
| 8c3c54913a | |||
| 5910345c08 | |||
| 8b4dad1381 | |||
| 6ef468408c | |||
| 3a30d890c6 | |||
| 71ab417a56 | |||
| 7cfb89c25c | |||
| 569dbfda9d | |||
| f342dfe9f3 | |||
| 5378952394 | |||
| a9d6a82011 | |||
| 43b74131fb | |||
| cb0eac54b2 | |||
| b219b55878 | |||
| 56c86f917e | |||
| 539a4b44e9 | |||
| 74df927179 | |||
| 510966c497 | |||
| c977e82d9e | |||
| 903f1ae1ce | |||
| 9c66056c18 | |||
| f55a48aa26 | |||
| 434ddee733 | |||
| fcced6149f | |||
| 1f0f9cae22 | |||
| 1d8569d3a9 | |||
| 0fdd51049d | |||
| f9ed5776cd | |||
| a2c0a24494 | |||
| 2c5f02cc69 | |||
| d9fbe6f36d | |||
| e6174aca82 | |||
| a736bba2c7 | |||
| 7724cabdcc | |||
| 16557d70de | |||
| 04e71a1b3f | |||
| e463bd61d8 | |||
| 419af7bfea | |||
| 6c604812ee | |||
| 5cf0bfbad4 | |||
| a838361ea8 | |||
| 0dbb0ba515 | |||
| 7a0ba26aa3 | |||
| 0bca3333aa | |||
| 02b7a6df6c | |||
| 8d9963207e | |||
| f9486fa1bd | |||
| 360ebce015 | |||
| dc9eca83e8 | |||
| 2c55167dd0 | |||
| c124e880c4 | |||
| 4db015e4b1 | |||
| 5809de419f | |||
| 7c748f0e31 | |||
| cd6b7982ee | |||
| e00886b1ed | |||
| 49904f9097 | |||
| 2d0d03b975 | |||
| 8cb048edb0 | |||
| cb3d8827fa | |||
| 4c83764332 | |||
| c0c2ab8b4e | |||
| 288e93d708 | |||
| 92e862da19 | |||
| dc6975e56c | |||
| 6a2d3c53fd | |||
| 5643a57bcc | |||
| 52fb398cd8 | |||
| cdbe3dfc22 | |||
| 7aabd068ed | |||
| 924f5da725 | |||
| d657cbb51e | |||
| be874503ec | |||
| 056d42d328 | |||
| aa66d07caa | |||
| fc0e1ffaee | |||
| bf8f1a175f | |||
| 6dd8283308 | |||
| 06ab303b9e | |||
| 2ccbdb2254 | |||
| c5fca70cfc | |||
| 895778f236 | |||
| 81c85afbea | |||
| 354cb79580 | |||
| 327bffa01f | |||
| c99be491df | |||
| 60effe8f1b | |||
| 824b9382be | |||
| 9ce6b7255b | |||
| 9dbf472630 | |||
| 723976b37e | |||
| 361b84f31f | |||
| 70892aa980 | |||
| 10eec286fa | |||
| 894b1884eb | |||
| d2bab5fd9e | |||
| f94f369547 | |||
| 107ec4958f | |||
| a22047e84e | |||
| 80b7d5b988 | |||
| 2ab896bbac | |||
| 62e16219f7 | |||
| 750c660331 | |||
| 7a88449cd1 | |||
| b14dc2f1ee | |||
| d354102c6a | |||
| 761ec868e6 | |||
| 7941c2dfec | |||
| ebb2811ed3 | |||
| 75c0c0f681 | |||
| 268a968548 | |||
| 323308d86f | |||
| b28d6a8f02 | |||
| ab82bcf1ef | |||
| a628bfac39 | |||
| d1122da566 | |||
| 6ae5ca34ed | |||
| 730b59e6d3 | |||
| f198ba47e1 | |||
| 943ef3327e | |||
| 475ef3c80a | |||
| 3c0307524b | |||
| c27e487fc3 | |||
| ed973c9b7b | |||
| 15bbfacd47 | |||
| 04f934ab04 | |||
| 591b4ffc19 | |||
| fe9ab9ebd2 | |||
| 7198749063 | |||
| b76481bbf2 | |||
| 4f05e5c90a | |||
| 8ad25afdc4 | |||
| 54bc759f70 | |||
| b6887af77a | |||
| 353d495c50 | |||
| f45b2c0a88 | |||
| 624e3ac0f2 | |||
| 35fcbd2bce | |||
| 2150181303 | |||
| d643e24a1b | |||
| 43e631f2e8 | |||
| f1afbf9b49 | |||
| ed6af6603a | |||
| 51e740d243 | |||
| 53dacd5332 | |||
| e297f2a1d3 | |||
| 70cdb9367e | |||
| efd9af9030 | |||
| b67d896415 | |||
| f03ae555fc | |||
| f36ae33acc | |||
| b9ea96f649 | |||
| 838ab9fd7e | |||
| ad6b4b872f | |||
| c75e6485e0 | |||
| 7b80f7f03b | |||
| 7f9fd570b2 | |||
| b17d2250a4 | |||
| dda10749d0 | |||
| 07ca84170e | |||
| 55e136e9bc | |||
| e493c40c7b | |||
| 95605232ab | |||
| 8f396a35de | |||
| bd323efedf | |||
| a9b143d012 | |||
| 024ff42be0 | |||
| 8ab2c28343 | |||
| c4a2fcce3d | |||
| 4d94a7ad59 | |||
| 9ac88bf446 | |||
| aa195b9a68 | |||
| d3f388f7e1 | |||
| f74e523617 | |||
| 7612a59757 | |||
| ce6b88ccdd | |||
| 43b7d3b15e | |||
| 181a9210d5 | |||
| 7f9d7690f9 | |||
| 0ba96e65a5 | |||
| 574a6f5215 | |||
| d073d11ad3 | |||
| 8c3254a8f2 | |||
| fccfd2f971 | |||
| 088e347c95 | |||
| 6c02b02d4a | |||
| 4fc8ac64e7 | |||
| 4683a08da2 | |||
| 6aada9f7e4 | |||
| 4e8ebbef04 | |||
| f29b1f13b1 | |||
| 072dab4144 | |||
| f58ec3ac32 | |||
| 28e3b2f741 | |||
| 4e361f938e | |||
| aa705e68bf | |||
| 94ad968d5e | |||
| e3e5ad7da8 | |||
| d0572260c7 | |||
| 43fd457383 | |||
| e085502df3 | |||
| 85fb007a2b | |||
| f540ec28e8 | |||
| 36feed3168 | |||
| 836a9165a5 | |||
| e012afa691 | |||
| 20007a5a81 | |||
| 107484d13c | |||
| c36c88d0fd | |||
| 3ca415fc66 | |||
| ae2f7c89bd | |||
| 27f53db0f4 | |||
| 6d8c6f5154 | |||
| d20027296c | |||
| 37f0de5902 | |||
| fdc2fd8dfd | |||
| 9fa3d9fcb2 | |||
| 45c0663ea1 | |||
| 998580772a | |||
| 594806c999 | |||
| 800664c98c | |||
| 4bb1e9abcd | |||
| 33841c5861 | |||
| eaf5e29a0c | |||
| 3586aadbc1 | |||
| 19ab171109 |
+147
@@ -0,0 +1,147 @@
|
|||||||
|
= README
|
||||||
|
README about the Expressions calculator
|
||||||
|
:authors: Celestino Amoroso
|
||||||
|
:docinfo: shared
|
||||||
|
:encoding: utf-8
|
||||||
|
:toc: right
|
||||||
|
:toclevels: 4
|
||||||
|
//:toc-title: Indice Generale
|
||||||
|
:icons: font
|
||||||
|
:icon-set: fi
|
||||||
|
:numbered:
|
||||||
|
//:table-caption: Tabella
|
||||||
|
//:figure-caption: Diagramma
|
||||||
|
:docinfo1:
|
||||||
|
:sectlinks:
|
||||||
|
:sectanchors:
|
||||||
|
:source-highlighter: rouge
|
||||||
|
// :rouge-style: ThankfulEyes
|
||||||
|
:rouge-style: gruvbox
|
||||||
|
// :rouge-style: colorful
|
||||||
|
//:rouge-style: monokay
|
||||||
|
|
||||||
|
toc::[]
|
||||||
|
|
||||||
|
== Expr
|
||||||
|
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||||
|
|
||||||
|
A few examples to get started.
|
||||||
|
|
||||||
|
.Examples taken from parser_test.go
|
||||||
|
[source,go]
|
||||||
|
----
|
||||||
|
`1.0 / 2` // 0.5
|
||||||
|
`435 + 105 * 2 - 1` // 644
|
||||||
|
`4 == (3-1)*(10/5)` // true
|
||||||
|
`"uno" * (2+1)` // `unounouno`
|
||||||
|
`2+3 but 5*2` // 10 <1>
|
||||||
|
`add(add(1+4),3+2,5*(3-2))` // 15 <2>
|
||||||
|
`a=5; b=2; add(a, b*3)` // 11 <3>
|
||||||
|
`two=func(){2}; two()` // 2 <4>
|
||||||
|
`double=func(x){2*x}; a=4+1; two=func() {2}; (double(3+a) + 1) * two()` // 34
|
||||||
|
`import("./test-funcs.expr"); (double(3+a) + 1) * two()` // 34 <5>
|
||||||
|
`[1,2,"hello"]` // Mixed types list
|
||||||
|
`[1,2]+[3]` // append list, result: [1,2,3]
|
||||||
|
`add([1,[2,2],3,2])` // Deep list sum, result: 10 <2>
|
||||||
|
`[a=1,b=2,c=3] but a+b+c` // 6
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> [blue]`but` operator.
|
||||||
|
<2> The _add()_ function definition may be changed in the future.
|
||||||
|
<3> Multiple expression. Only the last expression value will returned.
|
||||||
|
<4> Simple function definition: _two()_ returns 2.
|
||||||
|
<5> _import()_ function imports expressions from the specified files. See file _test-funcs.expr_.
|
||||||
|
|
||||||
|
|
||||||
|
=== Usage
|
||||||
|
|
||||||
|
|
||||||
|
[source,go]
|
||||||
|
----
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := expr.NewSimpleVarStore()
|
||||||
|
ctx.SetVar("var", 4)
|
||||||
|
|
||||||
|
source := `(3-1)*(10/5) == var`
|
||||||
|
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
scanner := expr.NewScanner(r, expr.DefaultTranslations())
|
||||||
|
parser := expr.NewParser(ctx)
|
||||||
|
|
||||||
|
if ast, err := parser.Parse(scanner); err == nil {
|
||||||
|
if result, err := ast.Eval(ctx); err == nil {
|
||||||
|
fmt.Printf("%q -> %v [%T]\n", source, result, result)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error calculating the expression:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error parsing the expression:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
The above program is equivalent to the following one.
|
||||||
|
|
||||||
|
[source,go]
|
||||||
|
----
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := expr.NewSimpleVarStore()
|
||||||
|
ctx.SetVar("var", 4)
|
||||||
|
|
||||||
|
source := `(3-1)*(10/5) == var`
|
||||||
|
|
||||||
|
if result, err := expr.EvalString(ctx, source); err == nil {
|
||||||
|
fmt.Printf("%q -> %v [%T]\n", source, result, result)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error calculating the expression:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Here is another equivalent version.
|
||||||
|
|
||||||
|
[source,go]
|
||||||
|
----
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.portale-stac.it/go-pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
source := `(3-1)*(10/5) == var`
|
||||||
|
|
||||||
|
if result, err := expr.EvalStringA(source, expr.Arg{"var", 4}); err == nil {
|
||||||
|
fmt.Printf("%q -> %v [%T]\n", source, result, result)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error calculating the expression:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
== Context of evaluation
|
||||||
|
Unless helpers functions like _expr.EvalStringA()_ are used, a _context_ is required to compute an expession.
|
||||||
|
|
||||||
|
A context is an object that implements the _expr.ExprContext_ interface. This interface specifies a set of function to handle variables and functions.
|
||||||
|
|
||||||
|
Variables and functions can be added to a context both programmatically and ad an effect of the expression computation.
|
||||||
|
|
||||||
|
== Expressions syntax
|
||||||
|
|
||||||
|
See https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// ast.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//-------- ast
|
|
||||||
|
|
||||||
type ast struct {
|
|
||||||
root *term
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAst() *ast {
|
|
||||||
return &ast{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) String() string {
|
|
||||||
var sb strings.Builder
|
|
||||||
if self.root == nil {
|
|
||||||
sb.WriteString("(nil)")
|
|
||||||
} else {
|
|
||||||
self.root.toString(&sb)
|
|
||||||
}
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) addTokens(tokens ...*Token) (err error) {
|
|
||||||
for _, tk := range tokens {
|
|
||||||
if err = self.addToken(tk); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) addToken(tk *Token) (err error) {
|
|
||||||
if t := newTerm(tk, nil); t != nil {
|
|
||||||
err = self.addTerm(t)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("No term constructor for token %q", tk.String())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) addTerm(node *term) (err error) {
|
|
||||||
if self.root == nil {
|
|
||||||
self.root = node
|
|
||||||
} else {
|
|
||||||
self.root, err = self.insert(self.root, node)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) insert(tree, node *term) (root *term, err error) {
|
|
||||||
if tree.getPriority() < node.getPriority() {
|
|
||||||
root = tree
|
|
||||||
if tree.isComplete() {
|
|
||||||
var subRoot *term
|
|
||||||
last := tree.removeLastChild()
|
|
||||||
subRoot, err = self.insert(last, node)
|
|
||||||
subRoot.setParent(tree)
|
|
||||||
} else {
|
|
||||||
node.setParent(tree)
|
|
||||||
}
|
|
||||||
} else if !node.isLeaf() {
|
|
||||||
root = node
|
|
||||||
tree.setParent(node)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("two adjacent operators: %q and %q", tree, node)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) eval(ctx exprContext) (result any, err error) {
|
|
||||||
if self.root != nil {
|
|
||||||
result, err = self.root.compute(ctx)
|
|
||||||
} else {
|
|
||||||
err = errors.New("empty expression")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
// ast_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAstString(t *testing.T) {
|
|
||||||
tree := NewAst()
|
|
||||||
if gotResult := tree.String(); gotResult != "(nil)" {
|
|
||||||
t.Errorf(`result: got %q, want "(nil)"`, gotResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddTokensGood(t *testing.T) {
|
|
||||||
tk1 := NewValueToken(SymInteger, "100", 100)
|
|
||||||
tk2 := NewToken(SymPlus, "+")
|
|
||||||
tk3 := NewValueToken(SymInteger, "50", 500)
|
|
||||||
|
|
||||||
tree := NewAst()
|
|
||||||
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr != nil {
|
|
||||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddTokensBad(t *testing.T) {
|
|
||||||
tk0 := NewValueToken(SymInteger, "200", 200)
|
|
||||||
tk1 := NewValueToken(SymInteger, "100", 100)
|
|
||||||
tk2 := NewToken(SymPlus, "+")
|
|
||||||
tk3 := NewValueToken(SymInteger, "50", 500)
|
|
||||||
|
|
||||||
wantErr := errors.New(`two adjacent operators: "200" and "100"`)
|
|
||||||
|
|
||||||
tree := NewAst()
|
|
||||||
if gotErr := tree.addTokens(tk0, tk1, tk2, tk3); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
|
||||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddUnknownTokens(t *testing.T) {
|
|
||||||
tk0 := NewToken(SymPercent, "%")
|
|
||||||
|
|
||||||
wantErr := errors.New(`No term constructor for token "%"`)
|
|
||||||
|
|
||||||
tree := NewAst()
|
|
||||||
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
|
||||||
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Linking with Expr functions
|
||||||
|
type exprFunctor struct {
|
||||||
|
kern.BaseFunctor
|
||||||
|
params []kern.ExprFuncParam
|
||||||
|
expr kern.Expr
|
||||||
|
defCtx kern.ExprContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) GetParams() (params []kern.ExprFuncParam) {
|
||||||
|
return functor.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExprFunctor(e kern.Expr, params []kern.ExprFuncParam, ctx kern.ExprContext) *exprFunctor {
|
||||||
|
var defCtx kern.ExprContext
|
||||||
|
if ctx != nil {
|
||||||
|
defCtx = ctx
|
||||||
|
}
|
||||||
|
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) TypeName() string {
|
||||||
|
return "ExprFunctor"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) GetDefinitionContext() kern.ExprContext {
|
||||||
|
return functor.defCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) InvokeNamed(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var missing []string
|
||||||
|
for _, p := range functor.params {
|
||||||
|
if arg, exists := args[p.Name()]; exists {
|
||||||
|
if funcArg, ok := arg.(kern.Functor); ok {
|
||||||
|
paramSpecs := funcArg.GetParams()
|
||||||
|
ctx.RegisterFunc(p.Name(), funcArg, kern.TypeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if missing == nil {
|
||||||
|
missing = make([]string, 0, 1)
|
||||||
|
}
|
||||||
|
missing = append(missing, p.Name())
|
||||||
|
// ctx.UnsafeSetVar(p.Name(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if missing != nil {
|
||||||
|
err = kern.ErrMissingParams(name, missing)
|
||||||
|
} else {
|
||||||
|
result, err = functor.expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+339
@@ -0,0 +1,339 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-base.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ParamDenominator = "denominator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isNilFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = args[kern.ParamValue] == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsInteger(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsFloat(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsBool(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsString(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsFraction(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsRational(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsList(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = kern.IsDict(args[kern.ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[kern.ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
result = (v != 0)
|
||||||
|
case *kern.FractionType:
|
||||||
|
result = v.N() != 0
|
||||||
|
case float64:
|
||||||
|
result = v != 0.0
|
||||||
|
case bool:
|
||||||
|
result = v
|
||||||
|
case string:
|
||||||
|
result = len(v) > 0
|
||||||
|
case *kern.ListType:
|
||||||
|
result = len(*v) > 0
|
||||||
|
case *kern.DictType:
|
||||||
|
result = len(*v) > 0
|
||||||
|
default:
|
||||||
|
err = kern.ErrCantConvert(name, v, "bool")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[kern.ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
result = v
|
||||||
|
case float64:
|
||||||
|
result = int64(math.Trunc(v))
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = int64(1)
|
||||||
|
} else {
|
||||||
|
result = int64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var i int
|
||||||
|
if i, err = strconv.Atoi(v); err == nil {
|
||||||
|
result = int64(i)
|
||||||
|
}
|
||||||
|
case *kern.FractionType:
|
||||||
|
result = int64(v.N() / v.D())
|
||||||
|
default:
|
||||||
|
err = kern.ErrCantConvert(name, v, "int")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func decFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[kern.ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
result = float64(v)
|
||||||
|
case float64:
|
||||||
|
result = v
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = float64(1)
|
||||||
|
} else {
|
||||||
|
result = float64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var f float64
|
||||||
|
if f, err = strconv.ParseFloat(v, 64); err == nil {
|
||||||
|
result = f
|
||||||
|
}
|
||||||
|
case *kern.FractionType:
|
||||||
|
result = v.ToFloat()
|
||||||
|
default:
|
||||||
|
err = kern.ErrCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[kern.ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
result = strconv.FormatInt(v, 10)
|
||||||
|
case float64:
|
||||||
|
result = strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = "true"
|
||||||
|
} else {
|
||||||
|
result = "false"
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
result = v
|
||||||
|
case *kern.FractionType:
|
||||||
|
result = v.ToString(0)
|
||||||
|
case kern.Formatter:
|
||||||
|
result = v.ToString(0)
|
||||||
|
case fmt.Stringer:
|
||||||
|
result = v.String()
|
||||||
|
default:
|
||||||
|
err = kern.ErrCantConvert(name, v, "string")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fractFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[kern.ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
var den int64 = 1
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
if den, ok = args[ParamDenominator].(int64); !ok {
|
||||||
|
err = kern.ErrExpectedGot(name, "integer", args[ParamDenominator])
|
||||||
|
} else if den == 0 {
|
||||||
|
err = kern.ErrFuncDivisionByZero(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
result = kern.NewFraction(v, den)
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
result, err = kern.Float64ToFraction(v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = kern.NewFraction(1, 1)
|
||||||
|
} else {
|
||||||
|
result = kern.NewFraction(0, 1)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
result, err = kern.MakeGeneratingFraction(v)
|
||||||
|
case *kern.FractionType:
|
||||||
|
result = v
|
||||||
|
default:
|
||||||
|
err = kern.ErrCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func iteratorFunc(ctx expr.ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if source, ok := args[kern.ParamSource].(string); ok {
|
||||||
|
var ast kern.Expr
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = NewSimpleStoreWithoutGlobalContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
scanner := scan.NewScanner(r, scan.DefaultTranslations())
|
||||||
|
|
||||||
|
if ast, err = parser.Parse(scanner); err == nil {
|
||||||
|
CtrlEnable(ctx, kern.ControlExportAll)
|
||||||
|
result, err = ast.Eval(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func varFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var varName string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if varName, ok = args[kern.ParamName].(string); !ok {
|
||||||
|
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, ok = args[kern.ParamValue]; ok && result != nil {
|
||||||
|
ctx.GetParent().UnsafeSetVar(varName, result)
|
||||||
|
// } else {
|
||||||
|
// err = expr.ErrWrongParamType(name, expr.ParamSource, expr.TypeString, args[expr.ParamSource])
|
||||||
|
// }
|
||||||
|
} else if result, ok = ctx.GetVar(varName); !ok {
|
||||||
|
err = kern.ErrUnknownVar(name, varName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var varName string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if varName, ok = args[kern.ParamName].(string); !ok {
|
||||||
|
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, ok = args[kern.ParamValue]; ok {
|
||||||
|
ctx.GetParent().UnsafeSetVar(varName, result)
|
||||||
|
} else {
|
||||||
|
err = kern.ErrWrongParamType(name, kern.ParamValue, kern.TypeAny, args[kern.ParamValue])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var ord int
|
||||||
|
|
||||||
|
if n, ok := args[kern.ParamValue].(byte); ok {
|
||||||
|
ord = int(n)
|
||||||
|
} else if n, ok := args[kern.ParamValue].(int64); ok {
|
||||||
|
ord = int(n)
|
||||||
|
} else if n, ok := args[kern.ParamValue].(int); ok {
|
||||||
|
ord = n
|
||||||
|
} else {
|
||||||
|
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
|
||||||
|
}
|
||||||
|
if ord < 0 || ord > 255 {
|
||||||
|
err = kern.ErrFuncInvalidArg(name, fmt.Sprintf("character code must be in range 0-255, got %d", ord))
|
||||||
|
} else {
|
||||||
|
result = string(rune(ord))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func seqFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
list := kern.NewLinkedList()
|
||||||
|
items := args[kern.ParamValue].([]any)
|
||||||
|
for _, arg := range items {
|
||||||
|
list.PushBack(arg)
|
||||||
|
}
|
||||||
|
result = list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//// import
|
||||||
|
|
||||||
|
func ImportBuiltinsFuncs(ctx kern.ExprContext) {
|
||||||
|
anyParams := []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isInt", kern.NewGolangFunctor(isIntFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isFloat", kern.NewGolangFunctor(isFloatFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isBool", kern.NewGolangFunctor(isBoolFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams)
|
||||||
|
|
||||||
|
ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams)
|
||||||
|
ctx.RegisterFunc("dec", kern.NewGolangFunctor(decFunc), kern.TypeFloat, anyParams)
|
||||||
|
ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams)
|
||||||
|
ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
kern.NewFuncParamFlagDef(ParamDenominator, kern.PfDefault, int64(1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamName),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault, nil),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamName),
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamValue),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("seq", kern.NewGolangFunctor(seqFunc), kern.TypeLinkedList, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamValue, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-fmt.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getStdout(ctx kern.ExprContext) io.Writer {
|
||||||
|
var w io.Writer
|
||||||
|
if wany, exists := ctx.GetVar(kern.ControlStdout); exists && wany != nil {
|
||||||
|
w, _ = wany.(io.Writer)
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
w = os.Stdout
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func printFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var n int = 0
|
||||||
|
if v, exists := args[kern.ParamItem]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
n, err = fmt.Fprint(getStdout(ctx), argv...)
|
||||||
|
}
|
||||||
|
result = int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLnFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var n int = 0
|
||||||
|
if v, exists := args[kern.ParamItem]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
n, err = fmt.Fprintln(getStdout(ctx), argv...)
|
||||||
|
} else {
|
||||||
|
n, err = fmt.Fprintln(getStdout(ctx))
|
||||||
|
}
|
||||||
|
result = int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportFmtFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("print", kern.NewGolangFunctor(printFunc), kern.TypeInt, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("println", kern.NewGolangFunctor(printLnFunc), kern.TypeInt, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-import.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
func importFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
CtrlEnable(ctx, kern.ControlExportAll)
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importGeneral(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
dirList := buildSearchDirList(ctx, "sources", ENV_EXPR_SOURCE_PATH)
|
||||||
|
if v, exists := args[kern.ParamFilepath]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Iterator) (result any, err error) {
|
||||||
|
var v any
|
||||||
|
var sourceFilepath string
|
||||||
|
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if err = checkStringParamExpected(name, v, int(it.Index())); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var file *os.File
|
||||||
|
if file, err = os.Open(sourceFilepath); err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
var expr *scan.Ast
|
||||||
|
scanner := scan.NewScanner(file, scan.DefaultTranslations())
|
||||||
|
parser := NewParser()
|
||||||
|
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, scan.SymEos); err == nil {
|
||||||
|
result, err = expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
result = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportImportFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("import", kern.NewGolangFunctor(importFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("importAll", kern.NewGolangFunctor(importAllFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
iterParamOperator = "operator"
|
||||||
|
iterParamVars = "vars"
|
||||||
|
iterVarStatus = "status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Iterator, op kern.Functor, err error) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if it, ok = args[kern.ParamIterator].(kern.Iterator); !ok {
|
||||||
|
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", kern.ParamIterator, args[kern.ParamIterator], kern.TypeName(args[kern.ParamIterator]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[iterParamOperator] != nil {
|
||||||
|
if op, ok = args[iterParamOperator].(kern.Functor); !ok || op == nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], kern.TypeName(args[iterParamOperator]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars *kern.DictType
|
||||||
|
if vars, ok = args[iterParamVars].(*kern.DictType); !ok && args[iterParamVars] != nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars != nil {
|
||||||
|
for key, value := range *vars {
|
||||||
|
var varName string
|
||||||
|
if varName, ok = key.(string); ok {
|
||||||
|
localCtx.UnsafeSetVar(varName, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var it kern.Iterator
|
||||||
|
var ok bool
|
||||||
|
var op kern.Functor
|
||||||
|
var v any
|
||||||
|
// var usingDefaultOp = false
|
||||||
|
var params map[string]any
|
||||||
|
var item any
|
||||||
|
|
||||||
|
localCtx := ctx.Clone()
|
||||||
|
localCtx.UnsafeSetVar(iterVarStatus, nil)
|
||||||
|
|
||||||
|
if it, op, err = parseRunArgs(localCtx, args); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
if op != nil {
|
||||||
|
params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item}
|
||||||
|
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
var success bool
|
||||||
|
if success, ok = kern.ToBool(v); !success || !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if op == nil {
|
||||||
|
ctx.UnsafeSetVar(iterVarStatus, it.Count())
|
||||||
|
}
|
||||||
|
result, _ = localCtx.GetVar(iterVarStatus)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportIterFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("run", kern.NewGolangFunctor(runFunc), kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamIterator),
|
||||||
|
kern.NewFuncParamFlag(iterParamOperator, kern.PfOptional),
|
||||||
|
kern.NewFuncParamFlag(iterParamVars, kern.PfOptional),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-math-arith.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
|
||||||
|
if !(kern.IsNumber(paramValue) || kern.IsFraction(paramValue)) /*|| isList(paramValue)*/ {
|
||||||
|
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
|
||||||
|
funcName, paramPos+1, subPos+1, level, paramValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doAdd(ctx kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
|
||||||
|
var sumAsFloat, sumAsFract bool
|
||||||
|
var floatSum float64 = 0.0
|
||||||
|
var intSum int64 = 0
|
||||||
|
var fractSum *kern.FractionType
|
||||||
|
var v any
|
||||||
|
|
||||||
|
level++
|
||||||
|
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if list, ok := v.(*kern.ListType); ok {
|
||||||
|
v = NewListIterator(list, nil)
|
||||||
|
}
|
||||||
|
if subIter, ok := v.(kern.Iterator); ok {
|
||||||
|
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
|
||||||
|
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
if !sumAsFloat {
|
||||||
|
if kern.IsFloat(v) {
|
||||||
|
sumAsFloat = true
|
||||||
|
if sumAsFract {
|
||||||
|
floatSum = fractSum.ToFloat()
|
||||||
|
} else {
|
||||||
|
floatSum = float64(intSum)
|
||||||
|
}
|
||||||
|
} else if !sumAsFract && kern.IsFraction(v) {
|
||||||
|
fractSum = kern.NewFraction(intSum, 1)
|
||||||
|
sumAsFract = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sumAsFloat {
|
||||||
|
floatSum += kern.NumAsFloat(v)
|
||||||
|
} else if sumAsFract {
|
||||||
|
var item *kern.FractionType
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*kern.FractionType); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = kern.NewFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractSum = kern.SumFract(fractSum, item)
|
||||||
|
} else {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
intSum += iv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil || err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
if sumAsFloat {
|
||||||
|
result = floatSum
|
||||||
|
} else if sumAsFract {
|
||||||
|
result = fractSum
|
||||||
|
} else {
|
||||||
|
result = intSum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
argv := args[kern.ParamValue].([]any)
|
||||||
|
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
|
||||||
|
var mulAsFloat, mulAsFract bool
|
||||||
|
var floatProd float64 = 1.0
|
||||||
|
var intProd int64 = 1
|
||||||
|
var fractProd *kern.FractionType
|
||||||
|
var v any
|
||||||
|
|
||||||
|
level++
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if list, ok := v.(*kern.ListType); ok {
|
||||||
|
v = NewListIterator(list, nil)
|
||||||
|
}
|
||||||
|
if subIter, ok := v.(kern.Iterator); ok {
|
||||||
|
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
|
||||||
|
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
|
||||||
|
if !mulAsFloat {
|
||||||
|
if kern.IsFloat(v) {
|
||||||
|
mulAsFloat = true
|
||||||
|
if mulAsFract {
|
||||||
|
floatProd = fractProd.ToFloat()
|
||||||
|
} else {
|
||||||
|
floatProd = float64(intProd)
|
||||||
|
}
|
||||||
|
} else if !mulAsFract && kern.IsFraction(v) {
|
||||||
|
fractProd = kern.NewFraction(intProd, 1)
|
||||||
|
mulAsFract = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mulAsFloat {
|
||||||
|
floatProd *= kern.NumAsFloat(v)
|
||||||
|
} else if mulAsFract {
|
||||||
|
var item *kern.FractionType
|
||||||
|
var ok bool
|
||||||
|
if item, ok = v.(*kern.FractionType); !ok {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
item = kern.NewFraction(iv, 1)
|
||||||
|
}
|
||||||
|
fractProd = kern.MulFract(fractProd, item)
|
||||||
|
} else {
|
||||||
|
iv, _ := v.(int64)
|
||||||
|
intProd *= iv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil || err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
if mulAsFloat {
|
||||||
|
result = floatProd
|
||||||
|
} else if mulAsFract {
|
||||||
|
result = fractProd
|
||||||
|
} else {
|
||||||
|
result = intProd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mulFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
argv := args[kern.ParamValue].([]any)
|
||||||
|
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportMathFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("add", kern.NewGolangFunctor(addFunc), kern.TypeNumber, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", kern.NewGolangFunctor(mulFunc), kern.TypeNumber, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(1)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-os-file.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/file"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileByteIteratorType = "fileByteIterator"
|
||||||
|
|
||||||
|
type fileFileByteIterator struct {
|
||||||
|
fileIterBase
|
||||||
|
b byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileByteIterator(r *file.Reader, autoClose bool) *fileFileByteIterator {
|
||||||
|
return &fileFileByteIterator{
|
||||||
|
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
|
||||||
|
b: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) TypeName() string {
|
||||||
|
return fileByteIteratorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) String() string {
|
||||||
|
return it.repr(fileByteIteratorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
if it.b, err = it.reader.ReadByte(); err == nil {
|
||||||
|
it.increment()
|
||||||
|
item = it.b
|
||||||
|
} else if it.autoClose {
|
||||||
|
it.Clean()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) Current() (item any, err error) {
|
||||||
|
item = it.b
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) Reset() (err error) {
|
||||||
|
if err = it.reader.Reset(); err == nil {
|
||||||
|
it.reset()
|
||||||
|
it.b = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) Clean() (err error) {
|
||||||
|
if it.reader.Valid() {
|
||||||
|
if err = it.reader.GetFile().Close(); err == nil {
|
||||||
|
it.reader = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.reset()
|
||||||
|
it.b = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileByteIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileByteIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle *file.Reader
|
||||||
|
var invalidFileHandle any
|
||||||
|
var autoClose bool
|
||||||
|
|
||||||
|
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
|
||||||
|
if handle != nil {
|
||||||
|
result = newFileByteIterator(handle, autoClose)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-os-file.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/file"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileLineIteratorType = "fileLineIterator"
|
||||||
|
|
||||||
|
type fileFileLineIterator struct {
|
||||||
|
fileIterBase
|
||||||
|
line string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileLineIterator(r *file.Reader, autoClose bool) *fileFileLineIterator {
|
||||||
|
return &fileFileLineIterator{
|
||||||
|
fileIterBase: fileIterBase{reader: r, index: -1, count: 0, autoClose: autoClose},
|
||||||
|
line: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) TypeName() string {
|
||||||
|
return fileLineIteratorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) String() string {
|
||||||
|
return it.repr(fileLineIteratorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
if it.line, err = it.reader.ReadString('\n'); err == nil {
|
||||||
|
it.increment()
|
||||||
|
item = it.line[0 : len(it.line)-1]
|
||||||
|
} else if it.autoClose {
|
||||||
|
it.Clean()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) Current() (item any, err error) {
|
||||||
|
if len(it.line) > 0 {
|
||||||
|
item = it.line[0 : len(it.line)-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) Reset() (err error) {
|
||||||
|
if err = it.reader.Reset(); err == nil {
|
||||||
|
it.reset()
|
||||||
|
it.line = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) Clean() (err error) {
|
||||||
|
if it.reader != nil {
|
||||||
|
if err = it.reader.Close(); err == nil {
|
||||||
|
it.reader = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.reset()
|
||||||
|
it.line = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileFileLineIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileLineIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle *file.Reader
|
||||||
|
var invalidFileHandle any
|
||||||
|
var autoClose bool
|
||||||
|
|
||||||
|
if handle, invalidFileHandle, autoClose, err = initFileHandle(ctx, name, args); err == nil {
|
||||||
|
if handle != nil {
|
||||||
|
result = newFileLineIterator(handle, autoClose)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-os-file.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/file"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const paramHandleOrPath = "handle-or-path"
|
||||||
|
|
||||||
|
type fileIterBase struct {
|
||||||
|
reader *file.Reader
|
||||||
|
index int64
|
||||||
|
count int64
|
||||||
|
autoClose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) HasOperation(name string) bool {
|
||||||
|
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) reset() {
|
||||||
|
it.index = -1
|
||||||
|
it.count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) increment() {
|
||||||
|
it.index++
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *fileIterBase) repr(typeName string) string {
|
||||||
|
if it.reader.Valid() {
|
||||||
|
return fmt.Sprintf("$(%s@%q)", typeName, it.reader.GetName())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$(%s@<nil>)", typeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFileHandle(ctx kern.ExprContext, name string, args map[string]any) (handle *file.Reader, invalidFileHandle any, autoClose bool, err error) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if handle, ok = args[paramHandleOrPath].(*file.Reader); !ok {
|
||||||
|
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
|
||||||
|
var handleAny any
|
||||||
|
if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if handleAny != nil {
|
||||||
|
handle = handleAny.(*file.Reader)
|
||||||
|
autoClose = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = args[paramHandleOrPath]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-os-file.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/file"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
osLimitCh = "limitCh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func errMissingFilePath(funcName string) error {
|
||||||
|
return fmt.Errorf("%s(): missing or invalid file path", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidFileHandle(funcName string, v any) error {
|
||||||
|
if v != nil {
|
||||||
|
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%s(): invalid file handle", funcName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
|
||||||
|
// var fh *os.File
|
||||||
|
// if fh, err = os.Open(filePath); err == nil {
|
||||||
|
// result = file.NewReader(fh)
|
||||||
|
// }
|
||||||
|
result, err = file.OpenReader(filePath)
|
||||||
|
} else {
|
||||||
|
err = errMissingFilePath(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
|
||||||
|
// var fh *os.File
|
||||||
|
// if fh, err = os.Create(filePath); err == nil {
|
||||||
|
// result = file.NewWriter(fh)
|
||||||
|
// }
|
||||||
|
result, err = file.CreateWriter(filePath)
|
||||||
|
} else {
|
||||||
|
err = errMissingFilePath(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[kern.ParamFilepath].(string); ok && len(filePath) > 0 {
|
||||||
|
// var fh *os.File
|
||||||
|
// if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
|
||||||
|
// result = file.NewWriter(fh)
|
||||||
|
// }
|
||||||
|
result, err = file.AppendWriter(filePath)
|
||||||
|
} else {
|
||||||
|
err = errMissingFilePath(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle file.Handle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
|
||||||
|
invalidFileHandle = args[kern.ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if fh := handle.GetFile(); fh != nil {
|
||||||
|
if w, ok := handle.(*file.Writer); ok {
|
||||||
|
err = w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = fh.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, handle)
|
||||||
|
}
|
||||||
|
result = err == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle file.Handle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok {
|
||||||
|
invalidFileHandle = args[kern.ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if w, ok := handle.(*file.Writer); ok {
|
||||||
|
if v, exists := args[kern.ParamItem]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
// result, err = fmt.Fprint(w.writer, argv...)
|
||||||
|
result, err = w.Write(argv...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle file.Handle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = nil
|
||||||
|
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
|
||||||
|
invalidFileHandle = args[kern.ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if r, ok := handle.(*file.Reader); ok {
|
||||||
|
var limit byte = '\n'
|
||||||
|
var v string
|
||||||
|
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
|
||||||
|
limit = s[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = r.ReadString(limit)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if len(v) > 0 {
|
||||||
|
if v[len(v)-1] == limit {
|
||||||
|
result = v[0 : len(v)-1]
|
||||||
|
} else {
|
||||||
|
result = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle file.Handle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = nil
|
||||||
|
if handle, ok = args[kern.ParamHandle].(file.Handle); !ok || args[kern.ParamHandle] == nil {
|
||||||
|
invalidFileHandle = args[kern.ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if r, ok := handle.(*file.Reader); ok {
|
||||||
|
var b []byte
|
||||||
|
b, err = r.ReadAll()
|
||||||
|
result = string(b)
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportOsFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("fileOpen", kern.NewGolangFunctor(openFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileAppend", kern.NewGolangFunctor(appendFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileCreate", kern.NewGolangFunctor(createFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileClose", kern.NewGolangFunctor(closeFileFunc), kern.TypeBoolean, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamHandle),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileWriteText", kern.NewGolangFunctor(fileWriteTextFunc), kern.TypeInt, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamHandle),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamItem, kern.PfDefault|kern.PfRepeat, ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadText", kern.NewGolangFunctor(fileReadTextFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamHandle),
|
||||||
|
kern.NewFuncParamFlagDef(osLimitCh, kern.PfDefault, "\n"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadTextAll", kern.NewGolangFunctor(fileReadTextAllFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamHandle),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileLineIterator", kern.NewGolangFunctor(fileLineIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(paramHandleOrPath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileByteIterator", kern.NewGolangFunctor(fileByteIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(paramHandleOrPath),
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-string.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
strParamOther = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Start of function definitions
|
||||||
|
func doJoinStr(funcName string, sep string, it kern.Iterator) (result any, err error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var v any
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if it.Index() > 0 {
|
||||||
|
sb.WriteString(sep)
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
sb.WriteString(s)
|
||||||
|
} else {
|
||||||
|
err = kern.ErrExpectedGot(funcName, kern.TypeString, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
result = sb.String()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if sep, ok := args[kern.ParamSeparator].(string); ok {
|
||||||
|
if v, exists := args[kern.ParamItem]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
if len(argv) == 1 {
|
||||||
|
if ls, ok := argv[0].(*kern.ListType); ok {
|
||||||
|
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
|
||||||
|
} else if it, ok := argv[0].(kern.Iterator); ok {
|
||||||
|
result, err = doJoinStr(name, sep, it)
|
||||||
|
} else if s, ok := argv[0].(string); ok {
|
||||||
|
result = s
|
||||||
|
} else {
|
||||||
|
err = kern.ErrInvalidParameterValue(name, kern.ParamItem, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = doJoinStr(name, sep, NewArrayIterator(argv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = kern.ErrWrongParamType(name, kern.ParamSeparator, kern.TypeString, args[kern.ParamSeparator])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var start = 0
|
||||||
|
var count = -1
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[kern.ParamSource].(string); !ok {
|
||||||
|
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if start, err = kern.ToGoInt(args[kern.ParamStart], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if count, err = kern.ToGoInt(args[kern.ParamCount], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < 0 {
|
||||||
|
start = len(source) + start
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < 0 {
|
||||||
|
count = len(source) - start
|
||||||
|
}
|
||||||
|
end := min(start+count, len(source))
|
||||||
|
result = source[start:end]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[kern.ParamSource].(string); !ok {
|
||||||
|
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
result = strings.TrimSpace(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, prefix string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[kern.ParamSource].(string); !ok {
|
||||||
|
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, ok = args[kern.ParamPrefix].(string); !ok {
|
||||||
|
return result, kern.ErrWrongParamType(name, kern.ParamPrefix, kern.TypeString, args[kern.ParamPrefix])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(source, prefix) {
|
||||||
|
result = true
|
||||||
|
} else if v, exists := args[strParamOther]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
for i, targetSpec := range argv {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasPrefix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func endsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, suffix string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[kern.ParamSource].(string); !ok {
|
||||||
|
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if suffix, ok = args[kern.ParamSuffix].(string); !ok {
|
||||||
|
return result, kern.ErrWrongParamType(name, kern.ParamSuffix, kern.TypeString, args[kern.ParamSuffix])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(source, suffix) {
|
||||||
|
result = true
|
||||||
|
} else if v, exists := args[strParamOther]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
for i, targetSpec := range argv {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasSuffix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, sep string
|
||||||
|
var count int = -1
|
||||||
|
var parts []string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[kern.ParamSource].(string); !ok {
|
||||||
|
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if sep, ok = args[kern.ParamSeparator].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("separator param must be string, got %s (%v)", kern.TypeName(args[kern.ParamSeparator]), args[kern.ParamSeparator])
|
||||||
|
}
|
||||||
|
|
||||||
|
if count64, ok := args[kern.ParamCount].(int64); ok { // TODO replace type assertion with toInt()
|
||||||
|
count = int(count64)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("part count must be integer, got %s (%v)", kern.TypeName(args[kern.ParamCount]), args[kern.ParamCount])
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
parts = strings.SplitN(source, sep, count)
|
||||||
|
} else if count < 0 {
|
||||||
|
parts = strings.Split(source, sep)
|
||||||
|
} else {
|
||||||
|
parts = []string{}
|
||||||
|
}
|
||||||
|
list := make(kern.ListType, len(parts))
|
||||||
|
for i, part := range parts {
|
||||||
|
list[i] = part
|
||||||
|
}
|
||||||
|
result = &list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func upperStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if source, ok := args[kern.ParamSource].(string); ok {
|
||||||
|
result = strings.ToUpper(source)
|
||||||
|
} else {
|
||||||
|
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if source, ok := args[kern.ParamSource].(string); ok {
|
||||||
|
result = strings.ToLower(source)
|
||||||
|
} else {
|
||||||
|
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End of function definitions
|
||||||
|
|
||||||
|
// Import above functions in the context
|
||||||
|
func ImportStringFuncs(ctx kern.ExprContext) {
|
||||||
|
ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSeparator),
|
||||||
|
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamStart, kern.PfDefault, int64(0)),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamSeparator, kern.PfDefault, ""),
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
kern.NewFuncParam(kern.ParamPrefix),
|
||||||
|
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
kern.NewFuncParam(kern.ParamSuffix),
|
||||||
|
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParam(kern.ParamSource),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the import function in the import-register.
|
||||||
|
// That will allow to import all function of this module by the "builtin" operator."
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtins-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type builtinModule struct {
|
||||||
|
importFunc func(kern.ExprContext)
|
||||||
|
description string
|
||||||
|
imported bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuiltinModule(importFunc func(kern.ExprContext), description string) *builtinModule {
|
||||||
|
return &builtinModule{importFunc, description, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinModuleRegister map[string]*builtinModule
|
||||||
|
|
||||||
|
func RegisterBuiltinModule(name string, importFunc func(kern.ExprContext), description string) {
|
||||||
|
if builtinModuleRegister == nil {
|
||||||
|
builtinModuleRegister = make(map[string]*builtinModule)
|
||||||
|
}
|
||||||
|
if _, exists := builtinModuleRegister[name]; exists {
|
||||||
|
panic(fmt.Errorf("module %q already registered", name))
|
||||||
|
}
|
||||||
|
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
|
||||||
|
if op != nil {
|
||||||
|
for name, mod := range builtinModuleRegister {
|
||||||
|
if !op(name, mod.description, mod.imported) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
func init() {
|
||||||
|
if builtinModuleRegister == nil {
|
||||||
|
builtinModuleRegister = make(map[string]*builtinModule)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// byte-slider.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
type ByteSlider struct {
|
|
||||||
buf []byte
|
|
||||||
length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewByteSlider(size int) *ByteSlider {
|
|
||||||
return &ByteSlider{
|
|
||||||
buf: make([]byte, max(1, size)),
|
|
||||||
length: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ByteSlider) PushEnd(b byte) {
|
|
||||||
if self.length == cap(self.buf) {
|
|
||||||
self.length--
|
|
||||||
for i := 0; i < self.length; i++ {
|
|
||||||
self.buf[i] = self.buf[i+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.buf[self.length] = b
|
|
||||||
self.length++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ByteSlider) Equal(target []byte) bool {
|
|
||||||
return target != nil && bytes.Equal(self.buf, target)
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
// context.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
type exprFunc interface {
|
|
||||||
Name() string
|
|
||||||
MinArgs() int
|
|
||||||
MaxArgs() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type exprContext interface {
|
|
||||||
GetValue(varName string) (value any, exists bool)
|
|
||||||
SetValue(varName string, value any)
|
|
||||||
GetFuncInfo(name string) exprFunc
|
|
||||||
Call(name string, args []any) (result any, err error)
|
|
||||||
}
|
|
||||||
+307
@@ -0,0 +1,307 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// data-cursors.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dataCursor struct {
|
||||||
|
ds map[string]kern.Functor
|
||||||
|
ctx kern.ExprContext
|
||||||
|
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
|
||||||
|
// cursorValid bool // true if resource is nil or if clean has not yet been called
|
||||||
|
index int64
|
||||||
|
count int64
|
||||||
|
current any
|
||||||
|
lastErr error
|
||||||
|
resource any
|
||||||
|
nextFunc kern.Functor
|
||||||
|
cleanFunc kern.Functor
|
||||||
|
resetFunc kern.Functor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataCursor(ctx kern.ExprContext, ds map[string]kern.Functor, resource any) (dc *dataCursor) {
|
||||||
|
dc = &dataCursor{
|
||||||
|
ds: ds,
|
||||||
|
initState: true,
|
||||||
|
// cursorValid: true,
|
||||||
|
index: -1,
|
||||||
|
count: 0,
|
||||||
|
current: nil,
|
||||||
|
lastErr: nil,
|
||||||
|
resource: resource,
|
||||||
|
ctx: ctx.Clone(),
|
||||||
|
nextFunc: ds[kern.NextName],
|
||||||
|
cleanFunc: ds[kern.CleanName],
|
||||||
|
resetFunc: ds[kern.ResetName],
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Context() kern.ExprContext {
|
||||||
|
return dc.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) TypeName() string {
|
||||||
|
return "DataCursor"
|
||||||
|
}
|
||||||
|
|
||||||
|
// func mapToString(m map[string]Functor) string {
|
||||||
|
// var sb strings.Builder
|
||||||
|
// sb.WriteByte('{')
|
||||||
|
// for key, _ := range m {
|
||||||
|
// if sb.Len() > 1 {
|
||||||
|
// sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
|
||||||
|
// } else {
|
||||||
|
// sb.WriteString(fmt.Sprintf("%q: func(){}", key))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// sb.WriteByte('}')
|
||||||
|
// return sb.String()
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) String() string {
|
||||||
|
return "$()"
|
||||||
|
/*
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(fmt.Sprintf(`$(
|
||||||
|
index: %d,
|
||||||
|
ds: %s,
|
||||||
|
ctx: `, dc.index, mapToString(dc.ds)))
|
||||||
|
CtxToBuilder(&sb, dc.ctx, 1)
|
||||||
|
sb.WriteByte(')')
|
||||||
|
return sb.String()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
||||||
|
exists = slices.Contains([]string{kern.CleanName, kern.ResetName, kern.CurrentName, kern.IndexName}, name)
|
||||||
|
if !exists {
|
||||||
|
f, ok := dc.ds[name]
|
||||||
|
exists = ok && kern.IsFunctor(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
|
||||||
|
if name == kern.IndexName {
|
||||||
|
value = int64(dc.Index())
|
||||||
|
} else if name == kern.CleanName {
|
||||||
|
err = dc.Clean()
|
||||||
|
} else if name == kern.ResetName {
|
||||||
|
err = dc.Reset()
|
||||||
|
} else if functor, ok := dc.ds[name]; ok && kern.IsFunctor(functor) {
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
value, err = functor.InvokeNamed(ctx, name, args)
|
||||||
|
kern.ExportObjects(dc.ctx, ctx)
|
||||||
|
} else {
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) Reset() (err error) {
|
||||||
|
// if dc.resetFunc != nil {
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
|
||||||
|
// _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// dc.index = -1
|
||||||
|
// dc.count = 0
|
||||||
|
// dc.initState = true
|
||||||
|
// dc.current = nil
|
||||||
|
// dc.lastErr = nil
|
||||||
|
// } else {
|
||||||
|
// err = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = errNoOperation(ResetName)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) Reset() (err error) {
|
||||||
|
if dc.resetFunc != nil {
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
actualParams := kern.BindActualParams(dc.resetFunc, []any{dc.resource})
|
||||||
|
_, err = dc.resetFunc.InvokeNamed(ctx, kern.ResetName, actualParams)
|
||||||
|
kern.ExportObjects(dc.ctx, ctx)
|
||||||
|
}
|
||||||
|
dc.index = -1
|
||||||
|
dc.count = 0
|
||||||
|
dc.initState = true
|
||||||
|
dc.current = nil
|
||||||
|
dc.lastErr = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Clean() (err error) {
|
||||||
|
if dc.cleanFunc != nil {
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
actualParams := kern.BindActualParams(dc.cleanFunc, []any{dc.resource})
|
||||||
|
_, err = dc.cleanFunc.InvokeNamed(ctx, kern.CleanName, actualParams)
|
||||||
|
kern.ExportObjects(dc.ctx, ctx)
|
||||||
|
}
|
||||||
|
dc.lastErr = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) Clean() (err error) {
|
||||||
|
// if dc.cleanFunc != nil {
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
|
||||||
|
// _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// } else {
|
||||||
|
// err = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = errNoOperation(CleanName)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||||
|
dc.init()
|
||||||
|
|
||||||
|
if dc.current != nil {
|
||||||
|
item = dc.current
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) checkFilter(filter kern.Functor, item any) (accepted bool, err error) {
|
||||||
|
var v any
|
||||||
|
var ok bool
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
|
||||||
|
actualParams := kern.BindActualParams(filter, []any{item, dc.index})
|
||||||
|
if v, err = filter.InvokeNamed(ctx, kern.FilterName, actualParams); err == nil && v != nil {
|
||||||
|
if accepted, ok = v.(bool); !ok {
|
||||||
|
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) mapItem(mapper kern.Functor, item any) (mappedItem any, err error) {
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
actualParams := kern.BindActualParams(mapper, []any{item, dc.index})
|
||||||
|
mappedItem, err = mapper.InvokeNamed(ctx, kern.MapName, actualParams)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) init() {
|
||||||
|
if dc.initState {
|
||||||
|
dc.initState = false
|
||||||
|
dc.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
|
||||||
|
if dc.initState {
|
||||||
|
dc.init()
|
||||||
|
} else if err = dc.lastErr; err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current = dc.current
|
||||||
|
filter := dc.ds[kern.FilterName]
|
||||||
|
mapper := dc.ds[kern.MapName]
|
||||||
|
var item any
|
||||||
|
for item == nil && dc.lastErr == nil {
|
||||||
|
ctx := kern.CloneContext(dc.ctx)
|
||||||
|
dc.index++
|
||||||
|
|
||||||
|
actualParams := kern.BindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
|
||||||
|
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, kern.NextName, actualParams); dc.lastErr == nil {
|
||||||
|
if item == nil {
|
||||||
|
dc.lastErr = io.EOF
|
||||||
|
} else {
|
||||||
|
accepted := true
|
||||||
|
if filter != nil {
|
||||||
|
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
|
||||||
|
item = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accepted {
|
||||||
|
dc.count++
|
||||||
|
}
|
||||||
|
if item != nil && mapper != nil {
|
||||||
|
item, dc.lastErr = dc.mapItem(mapper, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kern.ExportObjects(dc.ctx, ctx)
|
||||||
|
}
|
||||||
|
dc.current = item
|
||||||
|
if dc.lastErr != nil {
|
||||||
|
dc.index--
|
||||||
|
dc.Clean()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
|
||||||
|
// if dc.initState {
|
||||||
|
// dc.init()
|
||||||
|
// } else if err = dc.lastErr; err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// current = dc.current
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// filter := dc.ds[FilterName]
|
||||||
|
// mapper := dc.ds[MapName]
|
||||||
|
// var item any
|
||||||
|
// for item == nil && dc.lastErr == nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// dc.index++
|
||||||
|
|
||||||
|
// actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
|
||||||
|
// if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
|
||||||
|
// if item == nil {
|
||||||
|
// dc.lastErr = io.EOF
|
||||||
|
// } else {
|
||||||
|
// accepted := true
|
||||||
|
// if filter != nil {
|
||||||
|
// if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
|
||||||
|
// item = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if accepted {
|
||||||
|
// dc.count++
|
||||||
|
// }
|
||||||
|
// if item != nil && mapper != nil {
|
||||||
|
// item, dc.lastErr = dc.mapItem(mapper, item)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// }
|
||||||
|
// dc.current = item
|
||||||
|
// if dc.lastErr != nil {
|
||||||
|
// dc.index--
|
||||||
|
// dc.Clean()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// dc.lastErr = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) Index() int64 {
|
||||||
|
return dc.index - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Count() int64 {
|
||||||
|
return dc.count
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dictIterMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
dictIterModeKeys dictIterMode = iota
|
||||||
|
dictIterModeValues
|
||||||
|
dictIterModeItems
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictIterator struct {
|
||||||
|
a *kern.DictType
|
||||||
|
count int64
|
||||||
|
index int64
|
||||||
|
keys []any
|
||||||
|
iterMode dictIterMode
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
sortTypeNone sortType = iota
|
||||||
|
sortTypeAsc
|
||||||
|
sortTypeDesc
|
||||||
|
sortTypeDefault = sortTypeAsc
|
||||||
|
)
|
||||||
|
|
||||||
|
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
|
||||||
|
it.keys = make([]any, 0, len(m))
|
||||||
|
|
||||||
|
if sort == sortTypeNone {
|
||||||
|
for keyAny := range m {
|
||||||
|
it.keys = append(it.keys, keyAny)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scalarMap := make(map[string]any, len(m))
|
||||||
|
scalerKeys := make([]string, 0, len(m))
|
||||||
|
for keyAny := range m {
|
||||||
|
keyStr := fmt.Sprint(keyAny)
|
||||||
|
scalarMap[keyStr] = keyAny
|
||||||
|
scalerKeys = append(scalerKeys, keyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sort {
|
||||||
|
case sortTypeAsc:
|
||||||
|
slices.Sort(scalerKeys)
|
||||||
|
case sortTypeDesc:
|
||||||
|
slices.Sort(scalerKeys)
|
||||||
|
slices.Reverse(scalerKeys)
|
||||||
|
}
|
||||||
|
for _, keyStr := range scalerKeys {
|
||||||
|
it.keys = append(it.keys, scalarMap[keyStr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err error) {
|
||||||
|
var sortType = sortTypeNone
|
||||||
|
var s string
|
||||||
|
var argAny any
|
||||||
|
|
||||||
|
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
|
||||||
|
if len(args) > 0 {
|
||||||
|
argAny = args[0]
|
||||||
|
} else {
|
||||||
|
argAny = "default"
|
||||||
|
}
|
||||||
|
if s, err = kern.ToGoString(argAny, "sort type"); err == nil {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "a", "asc":
|
||||||
|
sortType = sortTypeAsc
|
||||||
|
case "d", "desc":
|
||||||
|
sortType = sortTypeDesc
|
||||||
|
case "n", "none", "nosort", "no-sort":
|
||||||
|
sortType = sortTypeNone
|
||||||
|
case "", "default":
|
||||||
|
sortType = sortTypeDefault
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid sort type %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if len(args) > 1 {
|
||||||
|
argAny = args[1]
|
||||||
|
} else {
|
||||||
|
argAny = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, err = kern.ToGoString(argAny, "iteration mode"); err == nil {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "k", "key", "keys":
|
||||||
|
dictIt.iterMode = dictIterModeKeys
|
||||||
|
case "v", "value", "values":
|
||||||
|
dictIt.iterMode = dictIterModeValues
|
||||||
|
case "i", "item", "items":
|
||||||
|
dictIt.iterMode = dictIterModeItems
|
||||||
|
case "", "default":
|
||||||
|
dictIt.iterMode = dictIterModeKeys
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid iteration mode %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
dictIt.makeKeys(*dict, sortType)
|
||||||
|
it = dictIt
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapIterator(m map[any]any) (it *DictIterator) {
|
||||||
|
it = &DictIterator{a: (*kern.DictType)(&m), count: 0, index: -1, keys: nil}
|
||||||
|
it.makeKeys(m, sortTypeNone)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) String() string {
|
||||||
|
var l = int64(0)
|
||||||
|
if it.a != nil {
|
||||||
|
l = int64(len(*it.a))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$({#%d})", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) TypeName() string {
|
||||||
|
return "DictIterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) HasOperation(name string) bool {
|
||||||
|
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name)
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
case kern.KeyName:
|
||||||
|
if it.index >= 0 && it.index < int64(len(it.keys)) {
|
||||||
|
v = it.keys[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
case kern.ValueName:
|
||||||
|
if it.index >= 0 && it.index < int64(len(it.keys)) {
|
||||||
|
a := *(it.a)
|
||||||
|
v = a[it.keys[it.index]]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Current() (item any, err error) {
|
||||||
|
if it.index >= 0 && it.index < int64(len(it.keys)) {
|
||||||
|
switch it.iterMode {
|
||||||
|
case dictIterModeKeys:
|
||||||
|
item = it.keys[it.index]
|
||||||
|
case dictIterModeValues:
|
||||||
|
a := *(it.a)
|
||||||
|
item = a[it.keys[it.index]]
|
||||||
|
case dictIterModeItems:
|
||||||
|
a := *(it.a)
|
||||||
|
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
|
||||||
|
item = kern.NewList(pair)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Next() (item any, err error) {
|
||||||
|
it.index++
|
||||||
|
if item, err = it.Current(); err != io.EOF {
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Reset() error {
|
||||||
|
it.index = -1
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *DictIterator) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+1973
File diff suppressed because it is too large
Load Diff
+4047
File diff suppressed because one or more lines are too long
@@ -0,0 +1,94 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2024-04-03T13:08:06.112Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0" etag="aiwjz2Mg7uovMJtfXvHm" version="24.2.1" type="device">
|
||||||
|
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||||
|
<mxGraphModel dx="989" dy="570" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-7">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="scanner" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
||||||
|
<mxGeometry x="180" y="39" width="100" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.13;entryY=0.475;entryDx=0;entryDy=0;endArrow=classic;endFill=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-15">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
||||||
|
<mxGeometry x="320" y="14" width="286" height="90" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-0" target="WIyWlLk6GJQsqaUBKTNV-3">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-0" value="input stream" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="30" y="39" width="110" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-2" target="q_7kt0Q1lEUOPZf56y0L-3">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-2" value="<div>syntax</div><div>analyzer<br></div>" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="356" y="39" width="90" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-3" value="<div>static semanthic</div><div>analyzer<br></div>" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="477" y="39" width="100" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-4" value="Parser" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="433" y="10" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-2">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="356" y="80" as="sourcePoint" />
|
||||||
|
<mxPoint x="406" y="30" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-3" target="WIyWlLk6GJQsqaUBKTNV-7">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="356" y="80" as="sourcePoint" />
|
||||||
|
<mxPoint x="406" y="30" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-13" target="q_7kt0Q1lEUOPZf56y0L-29">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-13" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="785" y="12" width="255" height="95" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-15" target="q_7kt0Q1lEUOPZf56y0L-13">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-15" value="AST" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="634" y="40" width="110" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-18" value="<div>semanthic</div><div>analyzer<br></div>" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="814" y="40.5" width="100" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-19" value="Eval" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="946" y="40.5" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" target="q_7kt0Q1lEUOPZf56y0L-18">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="781" y="60" as="sourcePoint" />
|
||||||
|
<mxPoint x="790" y="70" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-18" target="q_7kt0Q1lEUOPZf56y0L-19">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="750" y="70" as="sourcePoint" />
|
||||||
|
<mxPoint x="790" y="70" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-19" target="q_7kt0Q1lEUOPZf56y0L-13">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="1020" y="62" as="sourcePoint" />
|
||||||
|
<mxPoint x="1070" y="12" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-29" value="<div>Computed</div><div>Value</div>" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="1060" y="39.5" width="110" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="q_7kt0Q1lEUOPZf56y0L-31" value="Excecuter" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||||
|
<mxGeometry x="872.5" y="9" width="80" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,322 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>file: Go Coverage Report</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: black;
|
||||||
|
color: rgb(80, 80, 80);
|
||||||
|
}
|
||||||
|
body, pre, #legend span {
|
||||||
|
font-family: Menlo, monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#topbar {
|
||||||
|
background: black;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
height: 42px;
|
||||||
|
border-bottom: 1px solid rgb(80, 80, 80);
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
#nav, #legend {
|
||||||
|
float: left;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
#legend {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
#nav {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
#legend span {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
.cov0 { color: rgb(192, 0, 0) }
|
||||||
|
.cov1 { color: rgb(128, 128, 128) }
|
||||||
|
.cov2 { color: rgb(116, 140, 131) }
|
||||||
|
.cov3 { color: rgb(104, 152, 134) }
|
||||||
|
.cov4 { color: rgb(92, 164, 137) }
|
||||||
|
.cov5 { color: rgb(80, 176, 140) }
|
||||||
|
.cov6 { color: rgb(68, 188, 143) }
|
||||||
|
.cov7 { color: rgb(56, 200, 146) }
|
||||||
|
.cov8 { color: rgb(44, 212, 149) }
|
||||||
|
.cov9 { color: rgb(32, 224, 152) }
|
||||||
|
.cov10 { color: rgb(20, 236, 155) }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="topbar">
|
||||||
|
<div id="nav">
|
||||||
|
<select id="files">
|
||||||
|
|
||||||
|
<option value="file0">git.portale-stac.it/go-pkg/expr/file/file.go (88.9%)</option>
|
||||||
|
|
||||||
|
<option value="file1">git.portale-stac.it/go-pkg/expr/file/reader.go (77.8%)</option>
|
||||||
|
|
||||||
|
<option value="file2">git.portale-stac.it/go-pkg/expr/file/writer.go (100.0%)</option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="legend">
|
||||||
|
<span>not tracked</span>
|
||||||
|
|
||||||
|
<span class="cov0">not covered</span>
|
||||||
|
<span class="cov8">covered</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<pre class="file" id="file0" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// file.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handle interface {
|
||||||
|
kern.Typer
|
||||||
|
GetFile() *os.File
|
||||||
|
GetName() string
|
||||||
|
Valid() bool
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type handleBase struct {
|
||||||
|
fh *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) GetFile() *os.File <span class="cov0" title="0">{
|
||||||
|
return h.fh
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (h *handleBase) GetName() (name string) <span class="cov8" title="1">{
|
||||||
|
if h.fh != nil </span><span class="cov8" title="1">{
|
||||||
|
name = h.fh.Name()
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) Valid() bool <span class="cov8" title="1">{
|
||||||
|
return h.fh != nil
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (h *handleBase) Close() (err error) <span class="cov8" title="1">{
|
||||||
|
if h.fh != nil </span><span class="cov8" title="1">{
|
||||||
|
err = h.fh.Close()
|
||||||
|
h.fh = nil
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="file" id="file1" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// reader.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
// fh *os.File
|
||||||
|
handleBase
|
||||||
|
reader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(fh *os.File) *Reader <span class="cov8" title="1">{
|
||||||
|
return &Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func OpenReader(filePath string) (r *Reader, err error) <span class="cov8" title="1">{
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err == nil </span><span class="cov8" title="1">{
|
||||||
|
r = NewReader(fh)
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) TypeName() string <span class="cov8" title="1">{
|
||||||
|
return "fileReader"
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (h *Reader) String() string <span class="cov8" title="1">{
|
||||||
|
return "reader"
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (h *Reader) Valid() bool <span class="cov8" title="1">{
|
||||||
|
return h.handleBase.Valid() && h.reader != nil
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (w *Reader) Close() (err error) <span class="cov8" title="1">{
|
||||||
|
w.reader = nil
|
||||||
|
err = w.handleBase.Close()
|
||||||
|
return
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (h *Reader) ReadByte() (b byte, err error) <span class="cov8" title="1">{
|
||||||
|
if h.reader != nil </span><span class="cov8" title="1">{
|
||||||
|
b, err = h.reader.ReadByte()
|
||||||
|
}</span> else<span class="cov0" title="0"> {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) ReadAll() (p []byte, err error) <span class="cov8" title="1">{
|
||||||
|
if h.reader != nil </span><span class="cov8" title="1">{
|
||||||
|
p, err = io.ReadAll(h.reader)
|
||||||
|
}</span> else<span class="cov0" title="0"> {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) ReadString(delim byte) (s string, err error) <span class="cov0" title="0">{
|
||||||
|
if h.reader != nil </span><span class="cov0" title="0">{
|
||||||
|
s, err = h.reader.ReadString(delim)
|
||||||
|
}</span> else<span class="cov0" title="0"> {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}</span>
|
||||||
|
<span class="cov0" title="0">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) Reset() (err error) <span class="cov8" title="1">{
|
||||||
|
if h.fh != nil </span><span class="cov8" title="1">{
|
||||||
|
if _, err = h.fh.Seek(0, 0); err == nil </span><span class="cov8" title="1">{
|
||||||
|
h.reader.Reset(h.fh)
|
||||||
|
}</span>
|
||||||
|
}
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="file" id="file2" style="display: none">// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// writer.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
// fh *os.File
|
||||||
|
handleBase
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriter(fh *os.File) *Writer <span class="cov8" title="1">{
|
||||||
|
return &Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func CreateWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Create(filePath); err == nil </span><span class="cov8" title="1">{
|
||||||
|
w = NewWriter(fh)
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendWriter(filePath string) (w *Writer, err error) <span class="cov8" title="1">{
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil </span><span class="cov8" title="1">{
|
||||||
|
w = NewWriter(fh)
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) TypeName() string <span class="cov8" title="1">{
|
||||||
|
return "fileWriter"
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (w *Writer) String() string <span class="cov8" title="1">{
|
||||||
|
return "writer"
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (w *Writer) Valid() bool <span class="cov8" title="1">{
|
||||||
|
return w.handleBase.Valid() && w.writer != nil
|
||||||
|
}</span>
|
||||||
|
|
||||||
|
func (w *Writer) Close() (err error) <span class="cov8" title="1">{
|
||||||
|
var err1 error
|
||||||
|
if w.writer != nil </span><span class="cov8" title="1">{
|
||||||
|
err1 = w.Flush()
|
||||||
|
w.writer = nil
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">if err = w.handleBase.Close(); err == nil </span><span class="cov8" title="1">{
|
||||||
|
err = err1
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Flush() (err error) <span class="cov8" title="1">{
|
||||||
|
if w.writer != nil </span><span class="cov8" title="1">{
|
||||||
|
err = w.writer.Flush()
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(args ...any) (n int, err error) <span class="cov8" title="1">{
|
||||||
|
if w.writer != nil </span><span class="cov8" title="1">{
|
||||||
|
n, err = fmt.Fprint(w.writer, args...)
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Writef(format string, args ...any) (n int, err error) <span class="cov8" title="1">{
|
||||||
|
if w.writer != nil </span><span class="cov8" title="1">{
|
||||||
|
n, err = fmt.Fprintf(w.writer, format, args...)
|
||||||
|
}</span>
|
||||||
|
<span class="cov8" title="1">return</span>
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var files = document.getElementById('files');
|
||||||
|
var visible;
|
||||||
|
files.addEventListener('change', onChange, false);
|
||||||
|
function select(part) {
|
||||||
|
if (visible)
|
||||||
|
visible.style.display = 'none';
|
||||||
|
visible = document.getElementById(part);
|
||||||
|
if (!visible)
|
||||||
|
return;
|
||||||
|
files.value = part;
|
||||||
|
visible.style.display = 'block';
|
||||||
|
location.hash = part;
|
||||||
|
}
|
||||||
|
function onChange() {
|
||||||
|
select(files.value);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
if (location.hash != "") {
|
||||||
|
select(location.hash.substr(1));
|
||||||
|
}
|
||||||
|
if (!visible) {
|
||||||
|
select("file0");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// file.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handle interface {
|
||||||
|
kern.Typer
|
||||||
|
GetFile() *os.File
|
||||||
|
GetName() string
|
||||||
|
Valid() bool
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type handleBase struct {
|
||||||
|
fh *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) GetFile() *os.File {
|
||||||
|
return h.fh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) GetName() (name string) {
|
||||||
|
if h.fh != nil {
|
||||||
|
name = h.fh.Name()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) Valid() bool {
|
||||||
|
return h.fh != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handleBase) Close() (err error) {
|
||||||
|
if h.fh != nil {
|
||||||
|
err = h.fh.Close()
|
||||||
|
h.fh = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// reader.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
// fh *os.File
|
||||||
|
handleBase
|
||||||
|
reader *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(fh *os.File) *Reader {
|
||||||
|
return &Reader{handleBase: handleBase{fh: fh}, reader: bufio.NewReader(fh)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenReader(filePath string) (r *Reader, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err == nil {
|
||||||
|
r = NewReader(fh)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) TypeName() string {
|
||||||
|
return "fileReader"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) String() string {
|
||||||
|
return "reader"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) Valid() bool {
|
||||||
|
return h.handleBase.Valid() && h.reader != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Reader) Close() (err error) {
|
||||||
|
w.reader = nil
|
||||||
|
err = w.handleBase.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) ReadByte() (b byte, err error) {
|
||||||
|
if h.reader != nil {
|
||||||
|
b, err = h.reader.ReadByte()
|
||||||
|
} else {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) ReadAll() (p []byte, err error) {
|
||||||
|
if h.reader != nil {
|
||||||
|
p, err = io.ReadAll(h.reader)
|
||||||
|
} else {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) ReadString(delim byte) (s string, err error) {
|
||||||
|
if h.reader != nil {
|
||||||
|
s, err = h.reader.ReadString(delim)
|
||||||
|
} else {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Reader) Reset() (err error) {
|
||||||
|
if h.fh != nil {
|
||||||
|
if _, err = h.fh.Seek(0, 0); err == nil {
|
||||||
|
h.reader.Reset(h.fh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// reader.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestOpenReader(t *testing.T) {
|
||||||
|
r, err := OpenReader("t_reader_test.go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenReader failed: %v", err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
if !r.Valid() {
|
||||||
|
t.Fatal("Reader should be valid after opening")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.TypeName() != "fileReader" {
|
||||||
|
t.Fatalf("Expected TypeName 'fileReader', got '%s'", r.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.String() != "reader" {
|
||||||
|
t.Fatalf("Expected String 'reader', got '%s'", r.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName may return either "t_reader_test.go" or "./t_reader_test.go" depending on the environment
|
||||||
|
name := r.GetName()
|
||||||
|
if (name != "t_reader_test.go") && (name != "./t_reader_test.go") {
|
||||||
|
t.Fatalf("Expected GetName 't_reader_test.go' or './t_reader_test.go', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reading a byte
|
||||||
|
b, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadByte failed: %v", err)
|
||||||
|
}
|
||||||
|
if b == 0 {
|
||||||
|
t.Fatal("ReadByte should not return zero byte")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.Reset()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Reset failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, err := r.ReadString('\n'); err != nil {
|
||||||
|
t.Fatalf("ReadString failed: %v", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("ReadString: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reading all content
|
||||||
|
content, err := r.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadAll failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(content) == 0 {
|
||||||
|
t.Fatal("ReadAll should return non-empty content")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// writer.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCreateWriter(t *testing.T) {
|
||||||
|
w, err := CreateWriter("/tmp/test_writer.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateWriter failed: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
if !w.Valid() {
|
||||||
|
t.Fatal("Writer should be valid after creation")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.TypeName() != "fileWriter" {
|
||||||
|
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.String() != "writer" {
|
||||||
|
t.Fatalf("Expected String 'writer', got '%s'", w.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
name := w.GetName()
|
||||||
|
if name != "/tmp/test_writer.txt" {
|
||||||
|
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := w.Write("Hello, World!\n"); err != nil {
|
||||||
|
t.Fatalf("Write failed: %v", err)
|
||||||
|
} else if n != len("Hello, World!\n") {
|
||||||
|
t.Fatalf("Expected to write %d bytes, wrote %d", len("Hello, World!\n"), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := w.Writef("This is a %s.\n", "test"); err != nil {
|
||||||
|
t.Fatalf("Writef failed: %v", err)
|
||||||
|
} else if n != len("This is a test.\n") {
|
||||||
|
t.Fatalf("Expected to write %d bytes, wrote %d", len("This is a test.\n"), n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendWriter(t *testing.T) {
|
||||||
|
w, err := AppendWriter("/tmp/test_writer.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AppendWriter failed: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
if !w.Valid() {
|
||||||
|
t.Fatal("Writer should be valid after opening for append")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.TypeName() != "fileWriter" {
|
||||||
|
t.Fatalf("Expected TypeName 'fileWriter', got '%s'", w.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.String() != "writer" {
|
||||||
|
t.Fatalf("Expected String 'writer', got '%s'", w.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
name := w.GetName()
|
||||||
|
if name != "/tmp/test_writer.txt" {
|
||||||
|
t.Fatalf("Expected GetName '/tmp/test_writer.txt', got '%s'", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// writer.go
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
// fh *os.File
|
||||||
|
handleBase
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriter(fh *os.File) *Writer {
|
||||||
|
return &Writer{handleBase: handleBase{fh: fh}, writer: bufio.NewWriter(fh)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateWriter(filePath string) (w *Writer, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Create(filePath); err == nil {
|
||||||
|
w = NewWriter(fh)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendWriter(filePath string) (w *Writer, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644); err == nil {
|
||||||
|
w = NewWriter(fh)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) TypeName() string {
|
||||||
|
return "fileWriter"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) String() string {
|
||||||
|
return "writer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Valid() bool {
|
||||||
|
return w.handleBase.Valid() && w.writer != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Close() (err error) {
|
||||||
|
var err1 error
|
||||||
|
if w.writer != nil {
|
||||||
|
err1 = w.Flush()
|
||||||
|
w.writer = nil
|
||||||
|
}
|
||||||
|
if err = w.handleBase.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Flush() (err error) {
|
||||||
|
if w.writer != nil {
|
||||||
|
err = w.writer.Flush()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(args ...any) (n int, err error) {
|
||||||
|
if w.writer != nil {
|
||||||
|
n, err = fmt.Fprint(w.writer, args...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Writef(format string, args ...any) (n int, err error) {
|
||||||
|
if w.writer != nil {
|
||||||
|
n, err = fmt.Fprintf(w.writer, format, args...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// global-context.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
//var globalCtx *SimpleStore
|
||||||
|
|
||||||
|
func ImportInContext(ctx kern.ExprContext, name string) (exists bool) {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
var mod *builtinModule
|
||||||
|
if mod, exists = builtinModuleRegister[name]; exists {
|
||||||
|
mod.importFunc(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(ctx kern.ExprContext, pattern string) (count int, err error) {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
var matched bool
|
||||||
|
for name, mod := range builtinModuleRegister {
|
||||||
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
|
if matched {
|
||||||
|
count++
|
||||||
|
mod.importFunc(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixCtrlVar(name string) string {
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
name = fixCtrlVar(name)
|
||||||
|
currentValue, _ = globalCtx.GetVar(name)
|
||||||
|
globalCtx.SetVar(name, newValue)
|
||||||
|
}
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
name = fixCtrlVar(name)
|
||||||
|
currentValue, _ = globalCtx.GetVar(name)
|
||||||
|
}
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
|
||||||
|
name = fixCtrlVar(name)
|
||||||
|
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
|
||||||
|
currentStatus, _ = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetVar(name, true)
|
||||||
|
return currentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) {
|
||||||
|
name = fixCtrlVar(name)
|
||||||
|
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
|
||||||
|
currentStatus, _ = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetVar(name, false)
|
||||||
|
return currentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
|
||||||
|
// var v any
|
||||||
|
// var exists bool
|
||||||
|
|
||||||
|
// if !strings.HasPrefix(name, "_") {
|
||||||
|
// name = "_" + name
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if v, exists = ctx.GetVar(name); !exists {
|
||||||
|
// v, exists = globalCtx.GetVar(name)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if exists {
|
||||||
|
// if b, ok := v.(bool); ok {
|
||||||
|
// status = b
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func getControlString(ctx kern.ExprContext, name string) (s string, exists bool) {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
var v any
|
||||||
|
if v, exists = globalCtx.GetVar(name); exists {
|
||||||
|
s, exists = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitGlobal() (ctx kern.ExprContext) {
|
||||||
|
ctx = NewSimpleStoreWithoutGlobalContext()
|
||||||
|
kern.InitDefaultVars(ctx)
|
||||||
|
ImportBuiltinsFuncs(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
module git.portale-stac.it/go-pkg/expr
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
|
|
||||||
|
toolchain go1.23.3
|
||||||
|
|
||||||
|
require golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
|
||||||
|
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
|
||||||
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
//go:build graph
|
||||||
|
|
||||||
|
// graph.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildGraph(root *term) (g string) {
|
||||||
|
//var sb strings.Builder
|
||||||
|
g = fmt.Sprintf("tree: %v", root)
|
||||||
|
|
||||||
|
r := NewReticle(root)
|
||||||
|
fmt.Println(r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeRef struct {
|
||||||
|
node *term
|
||||||
|
pos int
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level []*NodeRef
|
||||||
|
|
||||||
|
type Reticle struct {
|
||||||
|
levels []Level
|
||||||
|
left, right int
|
||||||
|
colsWidth []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExprReticle(e Expr) *Reticle {
|
||||||
|
tree, _ := e.(*ast)
|
||||||
|
return NewReticle(tree.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReticle(tree *term) *Reticle {
|
||||||
|
r := &Reticle{levels: make([]Level, 0)}
|
||||||
|
r.build(tree, 0, 0)
|
||||||
|
r.computeCharWidth()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reticle) computeCharWidth() {
|
||||||
|
numCol := r.right - r.left + 1
|
||||||
|
r.colsWidth = make([]int, numCol)
|
||||||
|
for _, level := range r.levels {
|
||||||
|
for _, ref := range level {
|
||||||
|
c := ref.pos - r.left
|
||||||
|
if v := ref.node.value(); v != nil {
|
||||||
|
ref.label = fmt.Sprintf("%v", v)
|
||||||
|
} else {
|
||||||
|
ref.label = ref.node.Source()
|
||||||
|
}
|
||||||
|
r.colsWidth[c] = max(r.colsWidth[c], len(ref.label)+2) // +2 to make room for brakets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reticle) build(node *term, depth, pos int) {
|
||||||
|
var level Level
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(r.levels) == depth {
|
||||||
|
level = make(Level, 0)
|
||||||
|
r.levels = append(r.levels, level)
|
||||||
|
} else {
|
||||||
|
level = r.levels[depth]
|
||||||
|
}
|
||||||
|
ref := &NodeRef{node: node, pos: pos}
|
||||||
|
level = append(level, ref)
|
||||||
|
if r.left > pos {
|
||||||
|
r.left = pos
|
||||||
|
}
|
||||||
|
if r.right < pos {
|
||||||
|
r.right = pos
|
||||||
|
}
|
||||||
|
if node.children != nil {
|
||||||
|
halfPos := len(node.children) / 2
|
||||||
|
childPos := pos - halfPos - 1
|
||||||
|
for _, child := range node.children {
|
||||||
|
childPos++
|
||||||
|
if childPos == pos && (len(node.children)&1) == 0 {
|
||||||
|
childPos++
|
||||||
|
}
|
||||||
|
r.build(child, depth+1, childPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.levels[depth] = level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reticle) nodeInLevel(level []*NodeRef, index int) (node *NodeRef) {
|
||||||
|
for _, ref := range level {
|
||||||
|
if ref.pos-r.left == index {
|
||||||
|
node = ref
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reticle) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, level := range r.levels {
|
||||||
|
for j, w := range r.colsWidth {
|
||||||
|
var label string
|
||||||
|
if ref := r.nodeInLevel(level, j); ref != nil {
|
||||||
|
s := "(" + ref.label + ")"
|
||||||
|
label = fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(s))/2, s))
|
||||||
|
} else {
|
||||||
|
label = strings.Repeat(" ", w)
|
||||||
|
}
|
||||||
|
sb.WriteString(label)
|
||||||
|
}
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// helpers.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EvalString(ctx kern.ExprContext, source string) (result any, err error) {
|
||||||
|
var tree *scan.Ast
|
||||||
|
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
scanner := scan.NewScanner(r, scan.DefaultTranslations())
|
||||||
|
parser := NewParser()
|
||||||
|
|
||||||
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
|
result, err = tree.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Arg struct {
|
||||||
|
Name string
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalStringA(source string, args ...Arg) (result any, err error) {
|
||||||
|
return EvalStringV(source, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||||
|
ctx := NewSimpleStoreWithoutGlobalContext()
|
||||||
|
for _, arg := range args {
|
||||||
|
if util.IsFunc(arg.Value) {
|
||||||
|
if f, ok := arg.Value.(kern.FuncTemplate); ok {
|
||||||
|
functor := kern.NewGolangFunctor(f)
|
||||||
|
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(arg.Name, functor, kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, 0),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||||
|
}
|
||||||
|
} else if integer, ok := kern.AnyInteger(arg.Value); ok {
|
||||||
|
ctx.SetVar(arg.Name, integer)
|
||||||
|
} else if float, ok := kern.AnyFloat(arg.Value); ok {
|
||||||
|
ctx.SetVar(arg.Name, float)
|
||||||
|
} else if _, ok := arg.Value.(string); ok {
|
||||||
|
ctx.SetVar(arg.Name, arg.Value)
|
||||||
|
} else if _, ok := arg.Value.(bool); ok {
|
||||||
|
ctx.SetVar(arg.Name, arg.Value)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unsupported type %T specified for item %q", arg.Value, arg.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
result, err = EvalString(ctx, source)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalStream(ctx kern.ExprContext, r io.Reader) (result any, err error) {
|
||||||
|
var tree *scan.Ast
|
||||||
|
scanner := scan.NewScanner(r, scan.DefaultTranslations())
|
||||||
|
parser := NewParser()
|
||||||
|
|
||||||
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
|
result, err = tree.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalFile(ctx kern.ExprContext, filePath string) (result any, err error) {
|
||||||
|
var fh *os.File
|
||||||
|
if fh, err = os.Open(filePath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
result, err = EvalStream(ctx, fh)
|
||||||
|
return
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// import-utils.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENV_EXPR_SOURCE_PATH = "EXPR_PATH"
|
||||||
|
ENV_EXPR_PLUGIN_PATH = "EXPR_PLUGIN_PATH"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||||
|
if !(kern.IsString(paramValue) /*|| isList(paramValue)*/) {
|
||||||
|
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, kern.TypeName(paramValue))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func addSourceEnvImportDirs(varName string, dirList []string) []string {
|
||||||
|
// return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func addPluginEnvImportDirs(varName string, dirList []string) []string {
|
||||||
|
// return addEnvImportDirs(ENV_EXPR_PLUGIN_PATH, dirList)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func addEnvImportDirs(envVarName string, dirList []string) []string {
|
||||||
|
if dirSpec, exists := os.LookupEnv(envVarName); exists {
|
||||||
|
dirs := strings.Split(dirSpec, ":")
|
||||||
|
if dirList == nil {
|
||||||
|
dirList = dirs
|
||||||
|
} else {
|
||||||
|
dirList = append(dirList, dirs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirList
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSearchDirs(ctx kern.ExprContext, endingPath string, dirList []string) []string {
|
||||||
|
if dirSpec, exists := getControlString(ctx, kern.ControlSearchPath); exists {
|
||||||
|
dirs := strings.Split(dirSpec, ":")
|
||||||
|
if dirList == nil {
|
||||||
|
dirList = dirs
|
||||||
|
} else {
|
||||||
|
if len(endingPath) > 0 {
|
||||||
|
for _, d := range dirs {
|
||||||
|
dirList = append(dirList, path.Join(d, endingPath))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dirList = append(dirList, dirs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirList
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSearchDirList(ctx kern.ExprContext, endingPath, envVarName string) (dirList []string) {
|
||||||
|
dirList = addEnvImportDirs(envVarName, dirList)
|
||||||
|
dirList = addSearchDirs(ctx, endingPath, dirList)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFile(filePath string) bool {
|
||||||
|
info, err := os.Stat(filePath)
|
||||||
|
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchAmongPath(filename string, dirList []string) (filePath string) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
suffix := SHAREDLIBRARY_EXTENSION
|
||||||
|
if strings.HasSuffix(filename, ".debug") {
|
||||||
|
suffix += ".debug"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dir := range dirList {
|
||||||
|
if dir, err = util.ExpandPath(dir); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fullPath := path.Join(dir, filename); isFile(fullPath) {
|
||||||
|
filePath = fullPath
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
subdir := strings.TrimSuffix(filename, suffix)
|
||||||
|
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
|
||||||
|
filePath = fullPath
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPathRelative(filePath string) bool {
|
||||||
|
unixPath := filepath.ToSlash(filePath)
|
||||||
|
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
|
||||||
|
if filename, err = util.ExpandPath(filename); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.IsAbs(filename) || isPathRelative(filename) {
|
||||||
|
if isFile(filename) {
|
||||||
|
filePath = filename
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath = searchAmongPath(filename, dirList)
|
||||||
|
}
|
||||||
|
if len(filePath) == 0 {
|
||||||
|
err = fmt.Errorf("file %q not found", filename)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// int-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntIterator struct {
|
||||||
|
count int64
|
||||||
|
index int64
|
||||||
|
start int64
|
||||||
|
stop int64
|
||||||
|
step int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntIteratorA(args ...any) (it *IntIterator, err error) {
|
||||||
|
return NewIntIterator(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntIterator(args []any) (it *IntIterator, err error) {
|
||||||
|
var argc int = 0
|
||||||
|
if args != nil {
|
||||||
|
argc = len(args)
|
||||||
|
}
|
||||||
|
it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1}
|
||||||
|
if argc >= 1 {
|
||||||
|
if it.stop, err = kern.ToGoInt64(args[0], "start index"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if argc >= 2 {
|
||||||
|
it.start = it.stop
|
||||||
|
if it.stop, err = kern.ToGoInt64(args[1], "stop index"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if argc >= 3 {
|
||||||
|
if it.step, err = kern.ToGoInt64(args[2], "step"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if it.start > it.stop {
|
||||||
|
it.step = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if it.step == 0 {
|
||||||
|
err = fmt.Errorf("step cannot be zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if it.start < it.stop && it.step < 0 {
|
||||||
|
err = fmt.Errorf("step cannot be negative when start < stop")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if it.start > it.stop && it.step > 0 {
|
||||||
|
err = fmt.Errorf("step cannot be positive when start > stop")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
it.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) String() string {
|
||||||
|
return fmt.Sprintf("$(%d..%d..%d)", it.start, it.stop, it.step)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) TypeName() string {
|
||||||
|
return "IntIterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) HasOperation(name string) bool {
|
||||||
|
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Current() (item any, err error) {
|
||||||
|
if it.start <= it.stop {
|
||||||
|
if it.index >= it.start && it.index < it.stop {
|
||||||
|
item = it.index
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if it.index > it.stop && it.index <= it.start {
|
||||||
|
item = it.index
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Next() (item any, err error) {
|
||||||
|
it.index += it.step
|
||||||
|
if item, err = it.Current(); err != io.EOF {
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Reset() error {
|
||||||
|
it.index = it.start - it.step
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IntIterator) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// it-range.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type RangeIterator struct {
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
step int64
|
||||||
|
current int64
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRangeIterator(start, end, step int64) *RangeIterator {
|
||||||
|
if step == 0 {
|
||||||
|
panic("Range step must be not zero")
|
||||||
|
}
|
||||||
|
if step < 0 && start < end {
|
||||||
|
panic("When the range's step is less than zero, start must be greater than end")
|
||||||
|
}
|
||||||
|
if step > 0 && start > end {
|
||||||
|
panic("When the range's step is greater than zero, start must be less than end")
|
||||||
|
}
|
||||||
|
return &RangeIterator{start: start, end: end, step: step, current: start, index: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *RangeIterator) Reset() {
|
||||||
|
it.index = 0
|
||||||
|
it.current = it.start
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *RangeIterator) Next() (item any, err error) {
|
||||||
|
if it.step > 0 {
|
||||||
|
if it.current < it.end {
|
||||||
|
item = it.current
|
||||||
|
it.current += it.step
|
||||||
|
it.index++
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if it.current > it.end {
|
||||||
|
item = it.current
|
||||||
|
it.current += it.step
|
||||||
|
it.index++
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *RangeIterator) Index() int {
|
||||||
|
return it.index - 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// iter-factory.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFormalIterator(value any) (it kern.Iterator) {
|
||||||
|
if value == nil {
|
||||||
|
it = NewArrayIterator([]any{})
|
||||||
|
} else {
|
||||||
|
it = NewArrayIterator([]any{value})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIterator(ctx kern.ExprContext, value any, ops []*scan.Term) (it kern.Iterator, err error) {
|
||||||
|
if value == nil {
|
||||||
|
return NewArrayIterator([]any{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case *kern.ListType:
|
||||||
|
it = NewListIterator(v, nil)
|
||||||
|
case *kern.LinkedList:
|
||||||
|
it = NewLinkedListIterator(v, nil)
|
||||||
|
case *kern.DictType:
|
||||||
|
it, err = NewDictIterator(v, nil)
|
||||||
|
case []any:
|
||||||
|
it = NewArrayIterator(v)
|
||||||
|
case kern.Iterator:
|
||||||
|
// var exprs []*scan.Term
|
||||||
|
it, err = NewIterIter(v, ctx, ops)
|
||||||
|
default:
|
||||||
|
it = NewArrayIterator([]any{value})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasIterStandardOperations(name string) bool {
|
||||||
|
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func HasIterOperations(name string, ops ...string) bool {
|
||||||
|
// return slices.Contains([]string{
|
||||||
|
// kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName,
|
||||||
|
// }, name) ||
|
||||||
|
// slices.Contains(ops, name)
|
||||||
|
// }
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// iter-iter.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
const iterIterType = "IterIter"
|
||||||
|
|
||||||
|
type IterIter struct {
|
||||||
|
it kern.Iterator
|
||||||
|
count int64
|
||||||
|
index int64
|
||||||
|
ctx kern.ExprContext
|
||||||
|
exprList []*scan.Term
|
||||||
|
current any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIterIter(it kern.Iterator, ctx kern.ExprContext, exprs []*scan.Term) (iter kern.Iterator, err error) {
|
||||||
|
if ctx == nil {
|
||||||
|
err = fmt.Errorf("context is required for %s", iterIterType)
|
||||||
|
} else if it == nil {
|
||||||
|
err = fmt.Errorf("source iterator is required for %s", iterIterType)
|
||||||
|
} else {
|
||||||
|
iter = &IterIter{it: it, count: 0, index: -1, ctx: ctx, exprList: exprs, current: nil}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) String() string {
|
||||||
|
return fmt.Sprintf("$(%s)", it.it)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) TypeName() string {
|
||||||
|
return iterIterType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) HasOperation(name string) bool {
|
||||||
|
return HasIterStandardOperations(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Current() (item any, err error) {
|
||||||
|
if it.current != nil {
|
||||||
|
item = it.current
|
||||||
|
} else if len(it.exprList) > 0 {
|
||||||
|
// Evaluate the expression list and use the result as the current item
|
||||||
|
var exprValue any
|
||||||
|
for _, expr := range it.exprList {
|
||||||
|
if exprValue, err = expr.Compute(it.ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
it.ctx.UnsafeSetVar(kern.ControlLastResult, exprValue)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
item = exprValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var exists bool
|
||||||
|
if it.current, exists = it.ctx.GetVar("_"); !exists {
|
||||||
|
err = fmt.Errorf("current item not available")
|
||||||
|
} else {
|
||||||
|
item = it.current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Next() (item any, err error) {
|
||||||
|
var src any
|
||||||
|
|
||||||
|
it.current = nil
|
||||||
|
ctx := it.ctx
|
||||||
|
for src, err = it.it.Next(); src == nil && err == nil; src, err = it.it.Next() {
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if src == nil {
|
||||||
|
err = io.EOF
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar("_", src)
|
||||||
|
ctx.UnsafeSetVar("__", it.it.Index())
|
||||||
|
ctx.UnsafeSetVar("_#", it.it.Count())
|
||||||
|
item, err = it.Current()
|
||||||
|
ctx.DeleteVar("_#")
|
||||||
|
ctx.DeleteVar("__")
|
||||||
|
ctx.DeleteVar("_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Reset() error {
|
||||||
|
it.index = -1
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IterIter) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// bind-go-function.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
// ---- Linking with Go functions
|
||||||
|
type GolangFunctor struct {
|
||||||
|
BaseFunctor
|
||||||
|
f FuncTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGolangFunctor(f FuncTemplate) *GolangFunctor {
|
||||||
|
return &GolangFunctor{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *GolangFunctor) TypeName() string {
|
||||||
|
return "GoFunctor"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *GolangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// string.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
func IsBool(v any) (ok bool) {
|
||||||
|
_, ok = v.(bool)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToBool(v any) (b bool, ok bool) {
|
||||||
|
ok = true
|
||||||
|
switch x := v.(type) {
|
||||||
|
case string:
|
||||||
|
b = len(x) > 0
|
||||||
|
case float64:
|
||||||
|
b = x != 0.0
|
||||||
|
case int64:
|
||||||
|
b = x != 0
|
||||||
|
case bool:
|
||||||
|
b = x
|
||||||
|
default:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// clone-value.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
func Clone(v any) (c any) {
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch unboxed := v.(type) {
|
||||||
|
case int64:
|
||||||
|
c = unboxed
|
||||||
|
case float64:
|
||||||
|
c = unboxed
|
||||||
|
case string:
|
||||||
|
c = unboxed
|
||||||
|
case bool:
|
||||||
|
c = unboxed
|
||||||
|
case *ListType:
|
||||||
|
c = unboxed.Clone()
|
||||||
|
case *DictType:
|
||||||
|
c = unboxed.Clone()
|
||||||
|
case *LinkedList:
|
||||||
|
c = unboxed.Clone()
|
||||||
|
default:
|
||||||
|
c = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-errors.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrMissingParams(funcName string, missing []string) (err error) {
|
||||||
|
return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
||||||
|
if maxArgs < 0 {
|
||||||
|
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
|
||||||
|
err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- General errors
|
||||||
|
|
||||||
|
func ErrCantConvert(funcName string, value any, kind string) error {
|
||||||
|
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrExpectedGot(funcName string, kind string, value any) error {
|
||||||
|
return fmt.Errorf("%s(): expected %s, got %s (%#v)", funcName, kind, TypeName(value), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrFuncDivisionByZero(funcName string) error {
|
||||||
|
return fmt.Errorf("%s(): division by zero", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func ErrDivisionByZero() error {
|
||||||
|
// return fmt.Errorf("division by zero")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// --- Parameter errors
|
||||||
|
|
||||||
|
// func ErrMissingRequiredParameter(funcName, paramName string) error {
|
||||||
|
// return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s(): invalid value %s (%#v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func undefArticle(s string) (article string) {
|
||||||
|
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
|
||||||
|
article = "an"
|
||||||
|
} else {
|
||||||
|
article = "a"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func prependUndefArticle(s string) (result string) {
|
||||||
|
return undefArticle(s) + " " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
|
var artWantType, artGotType string
|
||||||
|
gotType := TypeName(paramValue)
|
||||||
|
artGotType = prependUndefArticle(gotType)
|
||||||
|
artWantType = prependUndefArticle(paramType)
|
||||||
|
return fmt.Errorf("%s(): the %q parameter must be %s, got %s (%v)", funcName, paramName, artWantType, artGotType, paramValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrUnknownParam(funcName, paramName string) error {
|
||||||
|
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrUnknownVar(funcName, varName string) error {
|
||||||
|
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrFuncInvalidArg(funcName, details string) error {
|
||||||
|
return fmt.Errorf("%s(): invalid argument -- %s", funcName, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Operator errors
|
||||||
|
|
||||||
|
func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error {
|
||||||
|
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.Source())
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-params.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
const (
|
||||||
|
ParamArgs = "args"
|
||||||
|
ParamCount = "count"
|
||||||
|
ParamItem = "item"
|
||||||
|
ParamIndex = "index"
|
||||||
|
ParamParts = "parts"
|
||||||
|
ParamSeparator = "separator"
|
||||||
|
ParamSource = "source"
|
||||||
|
ParamSuffix = "suffix"
|
||||||
|
ParamPrefix = "prefix"
|
||||||
|
ParamStart = "start"
|
||||||
|
ParamEnd = "end"
|
||||||
|
ParamValue = "value"
|
||||||
|
ParamName = "name"
|
||||||
|
ParamEllipsis = "..."
|
||||||
|
ParamFilepath = "filepath"
|
||||||
|
ParamDirpath = "dirpath"
|
||||||
|
ParamHandle = "handle"
|
||||||
|
ParamResource = "resource"
|
||||||
|
ParamIterator = "iterator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// to be moved in its own source file
|
||||||
|
const (
|
||||||
|
ConstLastIndex = 0xFFFF_FFFF
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// common-type-names.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeAny = "any"
|
||||||
|
TypeNil = "nil"
|
||||||
|
TypeBoolean = "boolean"
|
||||||
|
TypeFloat = "float"
|
||||||
|
TypeFraction = "fraction"
|
||||||
|
TypeFileHandle = "file-handle"
|
||||||
|
TypeInt = "integer"
|
||||||
|
TypeItem = "item"
|
||||||
|
TypeIterator = "iterator"
|
||||||
|
TypeNumber = "number"
|
||||||
|
TypePair = "pair"
|
||||||
|
TypeString = "string"
|
||||||
|
TypeDict = "dict"
|
||||||
|
TypeListOf = "list-of-"
|
||||||
|
TypeListOfStrings = "list-of-strings"
|
||||||
|
TypeLinkedList = "linked-list"
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func Equal(value1, value2 any) (equal bool) {
|
||||||
|
if value1 == nil && value2 == nil {
|
||||||
|
equal = true
|
||||||
|
} else if value1 == nil || value2 == nil {
|
||||||
|
equal = false
|
||||||
|
} else if IsBool(value1) && IsBool(value2) {
|
||||||
|
equal = value1.(bool) == value2.(bool)
|
||||||
|
} else if IsList(value1) && IsList(value2) {
|
||||||
|
ls1 := value1.(*ListType)
|
||||||
|
ls2 := value2.(*ListType)
|
||||||
|
equal = ls1.Equals(*ls2)
|
||||||
|
} else if IsDict(value1) && IsDict(value2) {
|
||||||
|
d1 := value1.(*DictType)
|
||||||
|
d2 := value2.(*DictType)
|
||||||
|
equal = d1.Equals(*d2)
|
||||||
|
} else if IsLinkedList(value1) && IsLinkedList(value2) {
|
||||||
|
ll1 := value1.(*LinkedList)
|
||||||
|
ll2 := value2.(*LinkedList)
|
||||||
|
equal = ll1.Equals(ll2)
|
||||||
|
} else if IsInteger(value1) && IsInteger(value2) {
|
||||||
|
equal = value1.(int64) == value2.(int64)
|
||||||
|
} else if IsString(value1) && IsString(value2) {
|
||||||
|
equal = value1.(string) == value2.(string)
|
||||||
|
} else if IsFloat(value1) && IsFloat(value2) {
|
||||||
|
equal = value1.(float64) == value2.(float64)
|
||||||
|
} else if IsNumOrFract(value1) && IsNumOrFract(value2) {
|
||||||
|
if eq, err := CmpAnyFract(value1, value2); err == nil {
|
||||||
|
equal = eq == 0
|
||||||
|
}
|
||||||
|
} else if !reflect.DeepEqual(value1, value2) {
|
||||||
|
equal = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// context-helpers.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
func CloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
|
||||||
|
if sourceCtx != nil {
|
||||||
|
clonedCtx = sourceCtx.Clone()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportVar(ctx ExprContext, name string, value any) {
|
||||||
|
if name[0] == '@' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
ctx.UnsafeSetVar(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||||
|
if name[0] == '@' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
|
exportAll := CtrlIsEnabled(sourceCtx, ControlExportAll)
|
||||||
|
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||||
|
// Export variables
|
||||||
|
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return (exportAll || name[0] == '@') && !(name[0] == '_') }) {
|
||||||
|
// fmt.Printf("\tExporting %q\n", refName)
|
||||||
|
refValue, _ := sourceCtx.GetVar(refName)
|
||||||
|
exportVar(destCtx, refName, refValue)
|
||||||
|
}
|
||||||
|
// Export functions
|
||||||
|
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
|
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
|
||||||
|
exportFunc(destCtx, refName, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportObjectsToParent(sourceCtx ExprContext) {
|
||||||
|
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
|
||||||
|
ExportObjects(parentCtx, sourceCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// control.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Preset control variables
|
||||||
|
const (
|
||||||
|
ControlPreset = "_preset"
|
||||||
|
ControlLastResult = "last"
|
||||||
|
ControlBoolShortcut = "_bool_shortcut"
|
||||||
|
ControlSearchPath = "_search_path"
|
||||||
|
ControlParentContext = "_parent_context"
|
||||||
|
ControlStdout = "_stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Other control variables
|
||||||
|
const (
|
||||||
|
ControlExportAll = "_export_all"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initial values
|
||||||
|
const (
|
||||||
|
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
|
||||||
|
var v any
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, exists = ctx.GetVar(name); !exists {
|
||||||
|
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
|
||||||
|
v, exists = globalCtx.GetVar(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
if b, ok := v.(bool); ok {
|
||||||
|
status = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
|
||||||
|
current, _ = ctx.GetVar(name)
|
||||||
|
ctx.UnsafeSetVar(name, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDefaultVars(ctx ExprContext) {
|
||||||
|
if _, exists := ctx.GetVar(ControlPreset); exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.UnsafeSetVar(ControlPreset, true)
|
||||||
|
ctx.UnsafeSetVar(ControlBoolShortcut, true)
|
||||||
|
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict-type.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DictTypeName = "dict"
|
||||||
|
|
||||||
|
type DictType map[any]any
|
||||||
|
|
||||||
|
func IsDict(v any) (ok bool) {
|
||||||
|
_, ok = v.(*DictType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeDict() (dict *DictType) {
|
||||||
|
d := make(DictType)
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDict(dictAny map[any]any) (dict *DictType) {
|
||||||
|
var d DictType
|
||||||
|
if dictAny != nil {
|
||||||
|
d = make(DictType, len(dictAny))
|
||||||
|
for i, item := range dictAny {
|
||||||
|
d[i] = item
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = make(DictType)
|
||||||
|
}
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDict(dictAny map[any]Term) (dict *DictType) {
|
||||||
|
// TODO Change with a call to NewDict()
|
||||||
|
var d DictType
|
||||||
|
if dictAny != nil {
|
||||||
|
d = make(DictType, len(dictAny))
|
||||||
|
for i, item := range dictAny {
|
||||||
|
d[i] = item
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d = make(DictType)
|
||||||
|
}
|
||||||
|
dict = &d
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
|
||||||
|
indent := GetFormatIndent(opt)
|
||||||
|
flags := GetFormatFlags(opt)
|
||||||
|
//sb.WriteString(strings.Repeat(" ", indent))
|
||||||
|
sb.WriteByte('{')
|
||||||
|
|
||||||
|
if len(*dict) > 0 {
|
||||||
|
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||||
|
nest := strings.Repeat(" ", indent+1)
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
|
||||||
|
first := true
|
||||||
|
for name, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(nest)
|
||||||
|
if key, ok := name.(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(key)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(sb, "%v", name)
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(f.ToString(innerOpt))
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
sb.WriteString("func(){}")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(sb, "%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(strings.Repeat(" ", indent))
|
||||||
|
}
|
||||||
|
sb.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
flags := GetFormatFlags(opt)
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
dict.toMultiLine(&sb, opt)
|
||||||
|
} else {
|
||||||
|
sb.WriteByte('{')
|
||||||
|
first := true
|
||||||
|
for key, value := range *dict {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
if s, ok := key.(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&sb, "%v", key)
|
||||||
|
}
|
||||||
|
sb.WriteString(": ")
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(opt))
|
||||||
|
} else if t, ok := value.(Term); ok {
|
||||||
|
sb.WriteString(t.String())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&sb, "%#v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte('}')
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) String() string {
|
||||||
|
return dict.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) TypeName() string {
|
||||||
|
return DictTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) HasKey(target any) (ok bool) {
|
||||||
|
for key := range *dict {
|
||||||
|
if ok = reflect.DeepEqual(key, target); ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) SetItem(key any, value any) {
|
||||||
|
(*dict)[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) GetItem(key any) (value any, exists bool) {
|
||||||
|
value, exists = (*dict)[key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) Clone() (c *DictType) {
|
||||||
|
c = newDict(nil)
|
||||||
|
for k, v := range *dict {
|
||||||
|
(*c)[k] = Clone(v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) Merge(second *DictType) {
|
||||||
|
if second != nil {
|
||||||
|
for k, v := range *second {
|
||||||
|
(*dict)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dict *DictType) Equals(dict2 DictType) (answer bool) {
|
||||||
|
if dict2 != nil && len(*dict) == len(dict2) {
|
||||||
|
answer = true
|
||||||
|
for key, value1 := range *dict {
|
||||||
|
if value2, exists := dict2.GetItem(key); exists {
|
||||||
|
if !Equal(value1, value2) {
|
||||||
|
answer = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
answer = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
type DictFormat interface {
|
||||||
|
ToDict() *DictType
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-context.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
// ----Expression Context
|
||||||
|
type ExprContext interface {
|
||||||
|
Clone() ExprContext
|
||||||
|
SetParent(ctx ExprContext)
|
||||||
|
GetParent() (ctx ExprContext)
|
||||||
|
GetGlobal() (ctx ExprContext)
|
||||||
|
GetVar(varName string) (value any, exists bool)
|
||||||
|
GetLast() any
|
||||||
|
SetVar(varName string, value any)
|
||||||
|
UnsafeSetVar(varName string, value any)
|
||||||
|
|
||||||
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
|
VarCount() int
|
||||||
|
DeleteVar(varName string)
|
||||||
|
|
||||||
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
|
FuncCount() int
|
||||||
|
DeleteFunc(funcName string)
|
||||||
|
|
||||||
|
GetLocalFuncInfo(name string) (info ExprFunc, exists bool)
|
||||||
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
|
Call(name string, args map[string]any) (result any, err error)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
|
||||||
|
|
||||||
|
ToDict() (dict *DictType)
|
||||||
|
ToString(opt FmtOpt) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextToDict(ctx ExprContext) (dict *DictType) {
|
||||||
|
var keys []string
|
||||||
|
// Variables
|
||||||
|
keys = ctx.EnumVars(nil)
|
||||||
|
vars := MakeDict()
|
||||||
|
for _, key := range keys {
|
||||||
|
value, _ := ctx.GetVar(key)
|
||||||
|
vars.SetItem(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
keys = ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
funcs := MakeDict()
|
||||||
|
for _, key := range keys {
|
||||||
|
funcInfo, _ := ctx.GetFuncInfo(key)
|
||||||
|
funcs.SetItem(key, funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
dict = MakeDict()
|
||||||
|
dict.SetItem("vars", vars)
|
||||||
|
dict.SetItem("funcs", funcs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextToString(ctx ExprContext, opt FmtOpt) string {
|
||||||
|
dict := ctx.ToDict()
|
||||||
|
return dict.ToString(opt)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-function.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
// ---- Functor interface
|
||||||
|
type Functor interface {
|
||||||
|
Typer
|
||||||
|
InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error)
|
||||||
|
SetFunc(info ExprFunc)
|
||||||
|
GetFunc() ExprFunc
|
||||||
|
GetParams() []ExprFuncParam
|
||||||
|
GetDefinitionContext() ExprContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Param Info
|
||||||
|
type ExprFuncParam interface {
|
||||||
|
Name() string
|
||||||
|
Type() string
|
||||||
|
IsDefault() bool
|
||||||
|
IsOptional() bool
|
||||||
|
IsRepeat() bool
|
||||||
|
DefaultValue() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Info
|
||||||
|
type ExprFunc interface {
|
||||||
|
Formatter
|
||||||
|
Name() string
|
||||||
|
MinArgs() int
|
||||||
|
MaxArgs() int
|
||||||
|
Functor() Functor
|
||||||
|
Params() []ExprFuncParam
|
||||||
|
ParamSpec(paramName string) ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
|
PrepareCall(name string, actualParams map[string]any) (err error)
|
||||||
|
AllocContext(parentCtx ExprContext) (ctx ExprContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFunctor(v any) (ok bool) {
|
||||||
|
_, ok = v.(Functor)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
// ----Expression interface
|
||||||
|
type Expr interface {
|
||||||
|
Typer
|
||||||
|
Eval(ctx ExprContext) (result any, err error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// float.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
func IsFloat(v any) (ok bool) {
|
||||||
|
_, ok = v.(float64)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func AnyFloat(v any) (float float64, ok bool) {
|
||||||
|
ok = true
|
||||||
|
switch floatval := v.(type) {
|
||||||
|
case float32:
|
||||||
|
float = float64(floatval)
|
||||||
|
case float64:
|
||||||
|
float = floatval
|
||||||
|
default:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// formatter.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
|
||||||
|
|
||||||
|
const (
|
||||||
|
TTY FmtOpt = 1 << iota
|
||||||
|
MultiLine
|
||||||
|
Truncate
|
||||||
|
Base2
|
||||||
|
Base8
|
||||||
|
Base10
|
||||||
|
Base16
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TruncateEllipsis = "(...)"
|
||||||
|
MinTruncateSize = 10
|
||||||
|
TruncateSize = MinTruncateSize + 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateString(s string) (trunc string) {
|
||||||
|
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||||
|
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
|
||||||
|
return FmtOpt(indent<<16) | flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatFlags(opt FmtOpt) FmtOpt {
|
||||||
|
return opt & 0xFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatIndent(opt FmtOpt) int {
|
||||||
|
return int(opt >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
ToString(options FmtOpt) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Format(sb *strings.Builder, item any, opt FmtOpt) {
|
||||||
|
if s, ok := item.(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else if formatter, ok := item.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(opt))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(sb, "%v", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatted(v any, opt FmtOpt) (text string) {
|
||||||
|
if v == nil {
|
||||||
|
text = "(nil)"
|
||||||
|
} else if s, ok := v.(string); ok {
|
||||||
|
text = s
|
||||||
|
} else if formatter, ok := v.(Formatter); ok {
|
||||||
|
text = formatter.ToString(opt)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Typer interface {
|
||||||
|
TypeName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TypeName(v any) (name string) {
|
||||||
|
if v == nil {
|
||||||
|
name = "nil"
|
||||||
|
} else if typer, ok := v.(Typer); ok {
|
||||||
|
name = typer.TypeName()
|
||||||
|
} else if IsInteger(v) {
|
||||||
|
name = "integer"
|
||||||
|
} else if IsFloat(v) {
|
||||||
|
name = "float"
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%T", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// fraction-type.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FractionType struct {
|
||||||
|
num, den int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFraction(num, den int64) *FractionType {
|
||||||
|
num, den = simplifyIntegers(num, den)
|
||||||
|
return &FractionType{num, den}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Float64ToFraction(f float64) (fract *FractionType, err error) {
|
||||||
|
var sign string
|
||||||
|
intPart, decPart := math.Modf(f)
|
||||||
|
if decPart < 0.0 {
|
||||||
|
sign = "-"
|
||||||
|
intPart = -intPart
|
||||||
|
decPart = -decPart
|
||||||
|
}
|
||||||
|
dec := fmt.Sprintf("%.12f", decPart)
|
||||||
|
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
|
||||||
|
return MakeGeneratingFraction(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
|
||||||
|
func MakeGeneratingFraction(s string) (f *FractionType, err error) {
|
||||||
|
var num, den int64
|
||||||
|
var sign int64 = 1
|
||||||
|
var parts []string
|
||||||
|
if len(s) == 0 {
|
||||||
|
goto exit
|
||||||
|
}
|
||||||
|
switch s[0] {
|
||||||
|
case '-':
|
||||||
|
sign = int64(-1)
|
||||||
|
s = s[1:]
|
||||||
|
case '+':
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
// if strings.HasSuffix(s, "()") {
|
||||||
|
// s = s[0 : len(s)-2]
|
||||||
|
// }
|
||||||
|
s = strings.TrimSuffix(s, "()")
|
||||||
|
parts = strings.SplitN(s, ".", 2)
|
||||||
|
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
f = NewFraction(sign*num, 1)
|
||||||
|
} else if len(parts) == 2 {
|
||||||
|
subParts := strings.SplitN(parts[1], "(", 2)
|
||||||
|
if len(subParts) == 1 {
|
||||||
|
den = 1
|
||||||
|
dec := parts[1]
|
||||||
|
lsd := len(dec)
|
||||||
|
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
||||||
|
lsd--
|
||||||
|
}
|
||||||
|
for _, c := range dec[0:lsd] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
den = den * 10
|
||||||
|
}
|
||||||
|
f = NewFraction(sign*num, den)
|
||||||
|
} else if len(subParts) == 2 {
|
||||||
|
sub := num
|
||||||
|
mul := int64(1)
|
||||||
|
for _, c := range subParts[0] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
sub = sub*10 + int64(c-'0')
|
||||||
|
mul *= 10
|
||||||
|
}
|
||||||
|
if len(subParts) == 2 {
|
||||||
|
if s[len(s)-1] != ')' {
|
||||||
|
goto exit
|
||||||
|
}
|
||||||
|
p := subParts[1][0 : len(subParts[1])-1]
|
||||||
|
for _, c := range p {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return nil, ErrExpectedGot("fract", "digit", c)
|
||||||
|
}
|
||||||
|
num = num*10 + int64(c-'0')
|
||||||
|
den = den*10 + 9
|
||||||
|
}
|
||||||
|
den *= mul
|
||||||
|
}
|
||||||
|
num -= sub
|
||||||
|
f = NewFraction(sign*num, den)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
if f == nil {
|
||||||
|
err = errors.New("bad syntax")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) N() int64 {
|
||||||
|
return f.num
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) D() int64 {
|
||||||
|
return f.den
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) ToFloat() float64 {
|
||||||
|
return float64(f.num) / float64(f.den)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) String() string {
|
||||||
|
return f.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if opt&MultiLine == 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("%d:%d", f.num, f.den))
|
||||||
|
} else {
|
||||||
|
var sign, num string
|
||||||
|
if f.num < 0 && opt&TTY == 0 {
|
||||||
|
num = strconv.FormatInt(-f.num, 10)
|
||||||
|
sign = "-"
|
||||||
|
} else {
|
||||||
|
num = strconv.FormatInt(f.num, 10)
|
||||||
|
}
|
||||||
|
den := strconv.FormatInt(f.den, 10)
|
||||||
|
size := max(len(num), len(den))
|
||||||
|
if opt&TTY != 0 {
|
||||||
|
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
|
||||||
|
sb.WriteString(sNum)
|
||||||
|
} else {
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(sign)
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("-", size))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
|
||||||
|
sb.WriteString(sDen)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FractionType) TypeName() string {
|
||||||
|
return "fraction"
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- fraction utility functions
|
||||||
|
|
||||||
|
// greatest common divider
|
||||||
|
func Gcd(a, b int64) (g int64) {
|
||||||
|
if a < 0 {
|
||||||
|
a = -a
|
||||||
|
}
|
||||||
|
if b < 0 {
|
||||||
|
b = -b
|
||||||
|
}
|
||||||
|
if a < b {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
r := a % b
|
||||||
|
for r > 0 {
|
||||||
|
a, b = b, r
|
||||||
|
r = a % b
|
||||||
|
}
|
||||||
|
g = b
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower common multiple
|
||||||
|
func lcm(a, b int64) (l int64) {
|
||||||
|
g := Gcd(a, b)
|
||||||
|
l = a * b / g
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum two fractions
|
||||||
|
func SumFract(f1, f2 *FractionType) (sum *FractionType) {
|
||||||
|
m := lcm(f1.den, f2.den)
|
||||||
|
sum = NewFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply two fractions
|
||||||
|
func MulFract(f1, f2 *FractionType) (prod *FractionType) {
|
||||||
|
prod = NewFraction(f1.num*f2.num, f1.den*f2.den)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyToFract(v any) (f *FractionType, err error) {
|
||||||
|
var ok bool
|
||||||
|
if f, ok = v.(*FractionType); !ok {
|
||||||
|
if n, ok := v.(int64); ok {
|
||||||
|
f = intToFraction(n)
|
||||||
|
} else if dec, ok := v.(float64); ok {
|
||||||
|
f, err = Float64ToFraction(dec)
|
||||||
|
} else {
|
||||||
|
err = ErrExpectedGot("fract", TypeFraction, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) {
|
||||||
|
if f1, err = anyToFract(v1); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f2, err = anyToFract(v2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumAnyFract(af1, af2 any) (sum any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := SumFract(f1, f2)
|
||||||
|
if f.num == 0 {
|
||||||
|
sum = 0
|
||||||
|
} else {
|
||||||
|
sum = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns
|
||||||
|
//
|
||||||
|
// <0 if af1 < af2
|
||||||
|
// =0 if af1 == af2
|
||||||
|
// >0 if af1 > af2
|
||||||
|
// err if af1 or af2 is not convertible to fraction
|
||||||
|
func CmpAnyFract(af1, af2 any) (result int, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = cmpFract(f1, f2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns
|
||||||
|
//
|
||||||
|
// <0 if af1 < af2
|
||||||
|
// =0 if af1 == af2
|
||||||
|
// >0 if af1 > af2
|
||||||
|
func cmpFract(f1, f2 *FractionType) (result int) {
|
||||||
|
if f1 != nil && f2 != nil {
|
||||||
|
f2.num = -f2.num
|
||||||
|
f := SumFract(f1, f2)
|
||||||
|
if f.num < 0 {
|
||||||
|
result = -1
|
||||||
|
} else if f.num > 0 {
|
||||||
|
result = 1
|
||||||
|
} else {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubAnyFract(af1, af2 any) (sum any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f2.num = -f2.num
|
||||||
|
f := SumFract(f1, f2)
|
||||||
|
if f.num == 0 {
|
||||||
|
sum = 0
|
||||||
|
} else {
|
||||||
|
sum = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func MulAnyFract(af1, af2 any) (prod any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f1.num == 0 || f2.num == 0 {
|
||||||
|
prod = 0
|
||||||
|
} else {
|
||||||
|
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
|
||||||
|
prod = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DivAnyFract(af1, af2 any) (quot any, err error) {
|
||||||
|
var f1, f2 *FractionType
|
||||||
|
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f2.num == 0 {
|
||||||
|
err = errors.New("division by zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f1.num == 0 || f2.den == 0 {
|
||||||
|
quot = 0
|
||||||
|
} else {
|
||||||
|
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
|
||||||
|
quot = simplifyFraction(f)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyFraction(f *FractionType) (v any) {
|
||||||
|
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||||
|
if f.den == 1 {
|
||||||
|
v = f.num
|
||||||
|
} else {
|
||||||
|
v = &FractionType{f.num, f.den}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func simplifyIntegers(num, den int64) (a, b int64) {
|
||||||
|
if num == 0 {
|
||||||
|
return 0, 1
|
||||||
|
}
|
||||||
|
if den == 0 {
|
||||||
|
panic("fraction with denominator == 0")
|
||||||
|
}
|
||||||
|
if den < 0 {
|
||||||
|
den = -den
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
g := Gcd(num, den)
|
||||||
|
a = num / g
|
||||||
|
b = den / g
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToFraction(n int64) *FractionType {
|
||||||
|
return &FractionType{n, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFraction(v any) (ok bool) {
|
||||||
|
_, ok = v.(*FractionType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// func IsFract(v any) (ok bool) {
|
||||||
|
// _, ok = v.(*FractionType)
|
||||||
|
// return ok
|
||||||
|
// }
|
||||||
|
|
||||||
|
func IsRational(v any) (ok bool) {
|
||||||
|
if _, ok = v.(*FractionType); !ok {
|
||||||
|
_, ok = v.(int64)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// func-info.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Functions
|
||||||
|
|
||||||
|
// FuncInfo implements expr.ExprFunc
|
||||||
|
type FuncInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
formalParams []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *FuncInfo, err error) {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsDefault() || p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
minArgs--
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info = &FuncInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
|
||||||
|
}
|
||||||
|
functor.SetFunc(info)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) Params() []ExprFuncParam {
|
||||||
|
return info.formalParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) ReturnType() string {
|
||||||
|
return info.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if len(info.Name()) == 0 {
|
||||||
|
sb.WriteString("func")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(info.Name())
|
||||||
|
}
|
||||||
|
sb.WriteByte('(')
|
||||||
|
if info.formalParams != nil {
|
||||||
|
for i, p := range info.formalParams {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(p.Name())
|
||||||
|
|
||||||
|
if p.IsDefault() {
|
||||||
|
sb.WriteByte('=')
|
||||||
|
if s, ok := p.DefaultValue().(string); ok {
|
||||||
|
sb.WriteByte('"')
|
||||||
|
sb.WriteString(s)
|
||||||
|
sb.WriteByte('"')
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.maxArgs < 0 {
|
||||||
|
sb.WriteString(" ...")
|
||||||
|
}
|
||||||
|
sb.WriteString("):")
|
||||||
|
if len(info.returnType) > 0 {
|
||||||
|
sb.WriteString(info.returnType)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(TypeAny)
|
||||||
|
}
|
||||||
|
sb.WriteString("{}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) MinArgs() int {
|
||||||
|
return info.minArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) MaxArgs() int {
|
||||||
|
return info.maxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) Functor() Functor {
|
||||||
|
return info.functor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
|
||||||
|
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
|
||||||
|
ctx = defCtx.Clone()
|
||||||
|
ctx.SetParent(defCtx)
|
||||||
|
} else {
|
||||||
|
ctx = parentCtx.Clone()
|
||||||
|
ctx.SetParent(parentCtx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) ParamSpec(paramName string) ExprFuncParam {
|
||||||
|
for _, spec := range info.formalParams {
|
||||||
|
if spec.Name() == paramName {
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *FuncInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
|
||||||
|
passedCount := len(actualParams)
|
||||||
|
if info.MinArgs() > passedCount {
|
||||||
|
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if passedCount < len(info.formalParams) {
|
||||||
|
for _, p := range info.formalParams {
|
||||||
|
if _, exists := actualParams[p.Name()]; !exists {
|
||||||
|
if !p.IsDefault() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
varArgs := make([]any, 1)
|
||||||
|
varArgs[0] = p.DefaultValue()
|
||||||
|
actualParams[p.Name()] = varArgs
|
||||||
|
} else {
|
||||||
|
actualParams[p.Name()] = p.DefaultValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
|
||||||
|
err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Function templates
|
||||||
|
type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error)
|
||||||
|
|
||||||
|
type DeepFuncTemplate func(a, b any) (eq bool, err error)
|
||||||
|
|
||||||
|
// ---- Common functor definition
|
||||||
|
type BaseFunctor struct {
|
||||||
|
info ExprFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *BaseFunctor) ToString(opt FmtOpt) (s string) {
|
||||||
|
if functor.info != nil {
|
||||||
|
s = functor.info.ToString(opt)
|
||||||
|
} else {
|
||||||
|
s = "func(){}"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *BaseFunctor) GetParams() (params []ExprFuncParam) {
|
||||||
|
if functor.info != nil {
|
||||||
|
return functor.info.Params()
|
||||||
|
} else {
|
||||||
|
return []ExprFuncParam{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *BaseFunctor) SetFunc(info ExprFunc) {
|
||||||
|
functor.info = info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *BaseFunctor) GetFunc() ExprFunc {
|
||||||
|
return functor.info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *BaseFunctor) GetDefinitionContext() ExprContext {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Parameters
|
||||||
|
type FuncParamFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
PfDefault FuncParamFlags = 1 << iota
|
||||||
|
PfOptional
|
||||||
|
PfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags FuncParamFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFuncParam(name string) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFuncParamFlag(name string, flags FuncParamFlags) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name, flags: flags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFuncParamFlagDef(name string, flags FuncParamFlags, defValue any) *funcParamInfo {
|
||||||
|
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Name() string {
|
||||||
|
return param.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) Type() string {
|
||||||
|
return TypeAny
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsDefault() bool {
|
||||||
|
return (param.flags & PfDefault) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsOptional() bool {
|
||||||
|
return (param.flags & PfOptional) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) IsRepeat() bool {
|
||||||
|
return (param.flags & PfRepeat) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (param *funcParamInfo) DefaultValue() any {
|
||||||
|
return param.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func initActualParams(ctx ExprContext, info ExprFunc, callTerm Term) (actualParams map[string]any, err error) {
|
||||||
|
var varArgs []any
|
||||||
|
var varName string
|
||||||
|
|
||||||
|
namedParamsStarted := false
|
||||||
|
|
||||||
|
formalParams := info.Params()
|
||||||
|
actualParams = make(map[string]any, len(formalParams))
|
||||||
|
if callTerm == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
childCount := callTerm.GetChildCount()
|
||||||
|
for i := range childCount {
|
||||||
|
tree := callTerm.GetChild(i)
|
||||||
|
// for i, tree := range callTerm.Children() {
|
||||||
|
var paramValue any
|
||||||
|
paramCtx := ctx.Clone()
|
||||||
|
if paramValue, err = tree.Compute(paramCtx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if paramName, namedParam := GetAssignVarName(tree); namedParam {
|
||||||
|
if info.ParamSpec(paramName) == nil {
|
||||||
|
err = fmt.Errorf("%s(): unknown param %q", info.Name(), paramName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
actualParams[paramName] = paramValue
|
||||||
|
namedParamsStarted = true
|
||||||
|
} else if !namedParamsStarted {
|
||||||
|
if varArgs != nil {
|
||||||
|
varArgs = append(varArgs, paramValue)
|
||||||
|
} else if i < len(formalParams) {
|
||||||
|
spec := formalParams[i]
|
||||||
|
if spec.IsRepeat() {
|
||||||
|
varArgs = make([]any, 0, childCount-i)
|
||||||
|
varArgs = append(varArgs, paramValue)
|
||||||
|
varName = spec.Name()
|
||||||
|
} else {
|
||||||
|
actualParams[spec.Name()] = paramValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrTooManyParams(info.Name(), len(formalParams), childCount)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", info.Name(), i+1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if varArgs != nil {
|
||||||
|
actualParams[varName] = varArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (info *funcInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
|
||||||
|
// passedCount := len(actualParams)
|
||||||
|
// if info.MinArgs() > passedCount {
|
||||||
|
// err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if passedCount < len(info.formalParams) {
|
||||||
|
// for _, p := range info.formalParams {
|
||||||
|
// if _, exists := actualParams[p.Name()]; !exists {
|
||||||
|
// if !p.IsDefault() {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// if p.IsRepeat() {
|
||||||
|
// varArgs := make([]any, 1)
|
||||||
|
// varArgs[0] = p.DefaultValue()
|
||||||
|
// actualParams[p.Name()] = varArgs
|
||||||
|
// } else {
|
||||||
|
// actualParams[p.Name()] = p.DefaultValue()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
|
||||||
|
// err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ----- Call a function ---
|
||||||
|
|
||||||
|
// func getAssignVarName(t *term) (name string, ok bool) {
|
||||||
|
// if ok = t.symbol() == SymEqual; ok {
|
||||||
|
// name = t.children[0].source()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func GetAssignVarName(t Term) (name string, ok bool) {
|
||||||
|
if ok = t.IsAssign(); ok {
|
||||||
|
name = t.GetChildSource(0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByTerm(parentCtx ExprContext, name string, callTerm Term) (result any, err error) {
|
||||||
|
var actualParams map[string]any
|
||||||
|
if info, exists := parentCtx.GetFuncInfo(name); exists {
|
||||||
|
if actualParams, err = initActualParams(parentCtx, info, callTerm); err == nil {
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
functor := info.Functor()
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByArgs(parentCtx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var actualParams map[string]any
|
||||||
|
if info, exists := parentCtx.GetFuncInfo(name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
actualParams = BindActualParams(functor, args)
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByParams(parentCtx ExprContext, name string, actualParams map[string]any) (result any, err error) {
|
||||||
|
//var actualParams map[string]any
|
||||||
|
if info, exists := parentCtx.GetFuncInfo(name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParam(args map[string]any, paramName string, paramNum int) (value any, exists bool) {
|
||||||
|
if value, exists = args[paramName]; !exists {
|
||||||
|
if paramNum > 0 && paramNum <= len(args) {
|
||||||
|
value, exists = args["arg"+strconv.Itoa(paramNum)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func BindActualParams(functor Functor, args []any) (actualParams map[string]any) {
|
||||||
|
formalParams := functor.GetParams()
|
||||||
|
actualParams = make(map[string]any, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
if i < len(formalParams) {
|
||||||
|
actualParams[formalParams[i].Name()] = arg
|
||||||
|
} else {
|
||||||
|
actualParams["arg"+strconv.Itoa(i+1)] = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// iterator.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operator names
|
||||||
|
|
||||||
|
const (
|
||||||
|
InitName = "init"
|
||||||
|
CleanName = "clean"
|
||||||
|
ResetName = "reset"
|
||||||
|
NextName = "next"
|
||||||
|
CurrentName = "current"
|
||||||
|
IndexName = "index"
|
||||||
|
CountName = "count"
|
||||||
|
FilterName = "filter"
|
||||||
|
MapName = "map"
|
||||||
|
KeyName = "key"
|
||||||
|
ValueName = "value"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Iterator interface {
|
||||||
|
Typer
|
||||||
|
fmt.Stringer
|
||||||
|
Next() (item any, err error) // must return io.EOF after the last item
|
||||||
|
Current() (item any, err error)
|
||||||
|
Index() int64
|
||||||
|
Count() int64
|
||||||
|
HasOperation(name string) bool
|
||||||
|
CallOperation(name string, args map[string]any) (value any, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtIterator interface {
|
||||||
|
Iterator
|
||||||
|
Reset() error
|
||||||
|
Clean() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrNoOperation(name string) error {
|
||||||
|
return fmt.Errorf("no %s() function defined in the data-source", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsIterator(v any) (ok bool) {
|
||||||
|
_, ok = v.(Iterator)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type IteratorBase struct {
|
||||||
|
ItemIndex int64
|
||||||
|
ItemCount int64
|
||||||
|
current any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Current() (item any, err error) {
|
||||||
|
return it.current, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Index() int64 {
|
||||||
|
return it.ItemIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Count() int64 {
|
||||||
|
return it.ItemCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) HasOperation(name string) bool {
|
||||||
|
return slices.Contains([]string{NextName, IndexName, CountName, CurrentName}, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Clean() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Reset() (err error) {
|
||||||
|
it.ItemIndex = -1
|
||||||
|
it.ItemCount = 0
|
||||||
|
it.current = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) Increment() {
|
||||||
|
it.ItemIndex++
|
||||||
|
it.ItemCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *IteratorBase) SetCurrent(v any) {
|
||||||
|
it.current = v
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// linked-list-type.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LinkedListTypeName = "lisked-list"
|
||||||
|
const MaxUint64Allowed = uint64(9_223_372_036_854_775_807)
|
||||||
|
|
||||||
|
func IsLinkedList(v any) (ok bool) {
|
||||||
|
_, ok = v.(*LinkedList)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinkedListA(listAny ...any) (list *LinkedList) {
|
||||||
|
if listAny == nil {
|
||||||
|
listAny = []any{}
|
||||||
|
}
|
||||||
|
list = NewLinkedList()
|
||||||
|
for _, item := range listAny {
|
||||||
|
list.PushBack(FixAnyNumber(item))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func LinkedListFromStrings(stringList []string) (list *LinkedList) {
|
||||||
|
list = NewLinkedList()
|
||||||
|
for _, s := range stringList {
|
||||||
|
list.PushBack(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) ToString(opt FmtOpt) (s string) {
|
||||||
|
indent := GetFormatIndent(opt)
|
||||||
|
flags := GetFormatFlags(opt)
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("[<")
|
||||||
|
if ls.Len() > 0 {
|
||||||
|
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||||
|
nest := strings.Repeat(" ", indent+1)
|
||||||
|
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(nest)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for item := ls.FirstNode(); item != nil; item = item.Next() {
|
||||||
|
if i > 0 {
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteString(",\n")
|
||||||
|
sb.WriteString(nest)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// data := item.Data()
|
||||||
|
// if s, ok := data.(string); ok {
|
||||||
|
// sb.WriteByte('"')
|
||||||
|
// sb.WriteString(s)
|
||||||
|
// sb.WriteByte('"')
|
||||||
|
// } else if formatter, ok := data.(Formatter); ok {
|
||||||
|
// sb.WriteString(formatter.ToString(innerOpt))
|
||||||
|
// } else {
|
||||||
|
// fmt.Fprintf(&sb, "%v", data)
|
||||||
|
// }
|
||||||
|
Format(&sb, item.Data(), innerOpt)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(strings.Repeat(" ", indent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(">]")
|
||||||
|
s = sb.String()
|
||||||
|
if flags&Truncate != 0 && len(s) > TruncateSize {
|
||||||
|
s = TruncateString(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) String() string {
|
||||||
|
return ls.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) TypeName() string {
|
||||||
|
return LinkedListTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (ls *LinkedList) Contains(t *ListType) (answer bool) {
|
||||||
|
// if len(*ls) >= len(*t) {
|
||||||
|
// answer = true
|
||||||
|
// for _, item := range *t {
|
||||||
|
// if answer = ls.IndexDeepSameCmp(item) >= 0; !answer {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (ls1 *LinkedList) Equals(ls2 *LinkedList) (answer bool) {
|
||||||
|
if ls2 != nil && ls1.Len() == ls2.Len() {
|
||||||
|
answer = true
|
||||||
|
i2 := ls2.FirstNode()
|
||||||
|
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
|
||||||
|
if !Equal(i1.Data(), i2.Data()) {
|
||||||
|
answer = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i2 = i2.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls1 *LinkedList) Clone() (ls2 *LinkedList) {
|
||||||
|
ls2 = NewLinkedListA()
|
||||||
|
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
|
||||||
|
ls2.PushBack(Clone(i1.Data()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
// simple-list project slist.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListNode struct {
|
||||||
|
data any
|
||||||
|
next *ListNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *ListNode) Next() *ListNode {
|
||||||
|
return node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ListNode) Data() any {
|
||||||
|
return self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkedList struct {
|
||||||
|
count int
|
||||||
|
first *ListNode
|
||||||
|
last *ListNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinkedList() (list *LinkedList) {
|
||||||
|
list = &LinkedList{0, nil, nil}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) Len() int {
|
||||||
|
return ls.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) Empty() bool {
|
||||||
|
return ls.count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) PushFront(data any) *ListNode {
|
||||||
|
ls.first = &ListNode{data, ls.first}
|
||||||
|
if ls.last == nil {
|
||||||
|
ls.last = ls.first
|
||||||
|
}
|
||||||
|
ls.count++
|
||||||
|
return ls.first
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) SeqPushFront(data any) (result *LinkedList) {
|
||||||
|
switch seq := data.(type) {
|
||||||
|
case Iterator:
|
||||||
|
result = ls.IterPushFront(seq)
|
||||||
|
default:
|
||||||
|
ls.PushFront(data)
|
||||||
|
result = ls
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) IterPushFront(it Iterator) *LinkedList {
|
||||||
|
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
|
||||||
|
ls.PushFront(item)
|
||||||
|
}
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) PushBack(data any) (node *ListNode) {
|
||||||
|
node = &ListNode{data, nil}
|
||||||
|
if ls.last != nil {
|
||||||
|
ls.last.next = node
|
||||||
|
}
|
||||||
|
ls.last = node
|
||||||
|
if ls.first == nil {
|
||||||
|
ls.first = node
|
||||||
|
}
|
||||||
|
ls.count++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) SeqPushBack(data any) (result *LinkedList) {
|
||||||
|
switch seq := data.(type) {
|
||||||
|
case Iterator:
|
||||||
|
result = ls.IterPushBack(seq)
|
||||||
|
default:
|
||||||
|
ls.PushBack(data)
|
||||||
|
result = ls
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) IterPushBack(it Iterator) *LinkedList {
|
||||||
|
for item, err1 := it.Next(); err1 == nil; item, err1 = it.Next() {
|
||||||
|
ls.PushBack(item)
|
||||||
|
}
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) FirstNode() *ListNode {
|
||||||
|
return ls.first
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) First() (data any, err error) {
|
||||||
|
if ls.first != nil {
|
||||||
|
data = ls.first.data
|
||||||
|
} else {
|
||||||
|
err = errorListEmpty()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) LastNode() *ListNode {
|
||||||
|
return ls.last
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) Last() (data interface{}, err error) {
|
||||||
|
if ls.last != nil {
|
||||||
|
data = ls.last.data
|
||||||
|
} else {
|
||||||
|
err = errorListEmpty()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) NodeAt(index int) (node *ListNode, err error) {
|
||||||
|
if ls.count == 0 {
|
||||||
|
err = errorListEmpty()
|
||||||
|
} else if index > ls.count {
|
||||||
|
err = errorOutOfRange()
|
||||||
|
} else if index == ls.count-1 {
|
||||||
|
node = ls.last
|
||||||
|
} else {
|
||||||
|
current := ls.first
|
||||||
|
for range index {
|
||||||
|
current = current.next
|
||||||
|
}
|
||||||
|
node = current
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) At(index int) (data interface{}, err error) {
|
||||||
|
node, err := ls.NodeAt(index)
|
||||||
|
if err == nil {
|
||||||
|
data = node.data
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) Insert(index int, data any) *ListNode {
|
||||||
|
var prev *ListNode
|
||||||
|
|
||||||
|
prev = nil
|
||||||
|
current := ls.first
|
||||||
|
for pos := 0; current != nil && pos < index; pos++ {
|
||||||
|
prev = current
|
||||||
|
current = current.next
|
||||||
|
}
|
||||||
|
|
||||||
|
node := &ListNode{data, current}
|
||||||
|
|
||||||
|
if prev != nil {
|
||||||
|
prev.next = node
|
||||||
|
}
|
||||||
|
|
||||||
|
if ls.first == current {
|
||||||
|
ls.first = node
|
||||||
|
}
|
||||||
|
if node.next == nil {
|
||||||
|
ls.last = node
|
||||||
|
}
|
||||||
|
ls.count++
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) PushBackStringArray(items []string) {
|
||||||
|
for _, v := range items {
|
||||||
|
ls.PushBack(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) Sub(start, end int) (subList *LinkedList) {
|
||||||
|
subList = NewLinkedList()
|
||||||
|
if node, err := ls.NodeAt(start); err == nil {
|
||||||
|
for i := start; i < end && node != nil; i++ {
|
||||||
|
subList.PushBack(node.data)
|
||||||
|
node = node.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// type TraverseOperator func(index int, elem interface{}, userData interface{}) (err error)
|
||||||
|
|
||||||
|
// func (self *LinkedList) Traverse(op TraverseOperator, user_data interface{}) (err error) {
|
||||||
|
// index := int(0)
|
||||||
|
// for current := self.first; current != nil; current = current.next {
|
||||||
|
// err = op(index, current.data, user_data)
|
||||||
|
// if err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// index++
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (self *LinkedList) Traverse2(observer Observer, abortOnError bool) (err error) {
|
||||||
|
// index := int(0)
|
||||||
|
// for current := self.first; current != nil; current = current.next {
|
||||||
|
// err = observer.Observe(current, index)
|
||||||
|
// if err != nil && abortOnError {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// index++
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
type EqualFunc func(current, target any) bool
|
||||||
|
|
||||||
|
func (ls *LinkedList) FindFirst(eqFunc EqualFunc, targetData any) (targetNode *ListNode) {
|
||||||
|
for current := ls.first; current != nil && targetNode == nil; current = current.next {
|
||||||
|
if eqFunc(current.data, targetData) {
|
||||||
|
targetNode = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LinkedList) FindNext(eqFunc EqualFunc, startNode *ListNode) (targetNode *ListNode) {
|
||||||
|
if startNode != nil {
|
||||||
|
for current := startNode.next; current != nil && targetNode == nil; current = current.next {
|
||||||
|
if eqFunc(current.data, startNode.Data()) {
|
||||||
|
targetNode = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// type DataFeeder func(user_data interface{}) interface{}
|
||||||
|
// type NodeObserver func(node *ListNode, index int, userData interface{})
|
||||||
|
|
||||||
|
// func (self *LinkedList) FeedTail(feeder DataFeeder, feederUserData interface{}, observer NodeObserver, observerUserData interface{}) (count int) {
|
||||||
|
// count = 0
|
||||||
|
// for item := feeder(feederUserData); item != nil; item = feeder(feederUserData) {
|
||||||
|
// // fmt.Println("Item", count, item)
|
||||||
|
// node := self.PushBack(item)
|
||||||
|
// if observer != nil {
|
||||||
|
// observer(node, count, observerUserData)
|
||||||
|
// }
|
||||||
|
// count++
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (self *LinkedList) FeedTail2(feeder Feeder, observer Observer, abortOnError bool) (count int, err error) {
|
||||||
|
// count = 0
|
||||||
|
// // item := feeder.Next()
|
||||||
|
// for item, err1 := feeder.Next(); item != nil; item, err1 = feeder.Next() {
|
||||||
|
// if err1 != nil {
|
||||||
|
// if err == nil {
|
||||||
|
// err = err1
|
||||||
|
// }
|
||||||
|
// if abortOnError {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // fmt.Println("Item", count, item)
|
||||||
|
// node := self.PushBack(item)
|
||||||
|
// if observer != nil {
|
||||||
|
// observer.Observe(node, count)
|
||||||
|
// }
|
||||||
|
// count++
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func errorListEmpty() error {
|
||||||
|
return fmt.Errorf("List is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorOutOfRange() error {
|
||||||
|
return fmt.Errorf("Out of range")
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// list-type.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListType []any
|
||||||
|
|
||||||
|
func IsList(v any) (ok bool) {
|
||||||
|
_, ok = v.(*ListType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListA(listAny ...any) (list *ListType) {
|
||||||
|
if listAny == nil {
|
||||||
|
listAny = []any{}
|
||||||
|
}
|
||||||
|
return NewList(listAny)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewList(listAny []any) (list *ListType) {
|
||||||
|
if listAny != nil {
|
||||||
|
ls := make(ListType, len(listAny))
|
||||||
|
copy(ls, listAny)
|
||||||
|
list = &ls
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeList(length, capacity int) (list *ListType) {
|
||||||
|
if capacity < length {
|
||||||
|
capacity = length
|
||||||
|
}
|
||||||
|
ls := make(ListType, length, capacity)
|
||||||
|
list = &ls
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListFromStrings(stringList []string) (list *ListType) {
|
||||||
|
list = MakeList(len(stringList), 0)
|
||||||
|
for i, s := range stringList {
|
||||||
|
(*list)[i] = s
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
||||||
|
indent := GetFormatIndent(opt)
|
||||||
|
flags := GetFormatFlags(opt)
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteByte('[')
|
||||||
|
if len(*ls) > 0 {
|
||||||
|
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||||
|
nest := strings.Repeat(" ", indent+1)
|
||||||
|
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(nest)
|
||||||
|
}
|
||||||
|
for i, item := range []any(*ls) {
|
||||||
|
if i > 0 {
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteString(",\n")
|
||||||
|
sb.WriteString(nest)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Format(&sb, item, innerOpt)
|
||||||
|
}
|
||||||
|
if flags&MultiLine != 0 {
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
sb.WriteString(strings.Repeat(" ", indent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteByte(']')
|
||||||
|
s = sb.String()
|
||||||
|
if flags&Truncate != 0 && len(s) > TruncateSize {
|
||||||
|
s = TruncateString(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) String() string {
|
||||||
|
return ls.ToString(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) TypeName() string {
|
||||||
|
return "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) Contains(t *ListType) (answer bool) {
|
||||||
|
if len(*ls) >= len(*t) {
|
||||||
|
answer = true
|
||||||
|
for _, item := range *t {
|
||||||
|
if answer = ls.IndexDeepSameCmp(item) >= 0; !answer {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) Equals(ls2 ListType) (answer bool) {
|
||||||
|
if ls2 != nil && len(*ls) == len(ls2) {
|
||||||
|
answer = true
|
||||||
|
for index, i1 := range *ls {
|
||||||
|
// if !reflect.DeepEqual(i1, ls2[index]) {
|
||||||
|
// answer = false
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
if !Equal(i1, ls2[index]) {
|
||||||
|
answer = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls1 *ListType) Clone() (ls2 *ListType) {
|
||||||
|
ls := make(ListType, len(*ls1))
|
||||||
|
for i, item := range *ls1 {
|
||||||
|
ls[i] = Clone(item)
|
||||||
|
}
|
||||||
|
ls2 = &ls
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) IndexDeepSameCmp(target any) (index int) {
|
||||||
|
var eq bool
|
||||||
|
var err error
|
||||||
|
index = -1
|
||||||
|
for i, item := range *ls {
|
||||||
|
if eq, err = deepSame(item, target, SameContent); err != nil {
|
||||||
|
break
|
||||||
|
} else if eq {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SameContent(a, b any) (same bool, err error) {
|
||||||
|
la, _ := a.(*ListType)
|
||||||
|
lb, _ := b.(*ListType)
|
||||||
|
if len(*la) == len(*lb) {
|
||||||
|
same = true
|
||||||
|
for _, item := range *la {
|
||||||
|
if pos := lb.IndexDeepSameCmp(item); pos < 0 {
|
||||||
|
same = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepSame(a, b any, deepCmp DeepFuncTemplate) (eq bool, err error) {
|
||||||
|
if IsNumOrFract(a) && IsNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
eq = li == ri
|
||||||
|
} else {
|
||||||
|
eq = NumAsFloat(a) == NumAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = CmpAnyFract(a, b); err == nil {
|
||||||
|
eq = cmp == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||||
|
eq, err = deepCmp(a, b)
|
||||||
|
} else {
|
||||||
|
eq = reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) SetItem(index int64, value any) (err error) {
|
||||||
|
if index >= 0 && index < int64(len(*ls)) {
|
||||||
|
(*ls)[index] = value
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*ls)-1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *ListType) AppendItem(value any) {
|
||||||
|
*ls = append(*ls, value)
|
||||||
|
}
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// number.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsInteger(v any) (ok bool) {
|
||||||
|
_, ok = v.(int64)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumber(v any) (ok bool) {
|
||||||
|
return IsFloat(v) || IsInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumOrFract(v any) (ok bool) {
|
||||||
|
return IsFloat(v) || IsInteger(v) || IsFraction(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNumberString(v any) (ok bool) {
|
||||||
|
return IsString(v) || IsNumber(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NumAsFloat(v any) (f float64) {
|
||||||
|
var ok bool
|
||||||
|
if f, ok = v.(float64); !ok {
|
||||||
|
if fract, ok := v.(*FractionType); ok {
|
||||||
|
f = fract.ToFloat()
|
||||||
|
} else {
|
||||||
|
i, _ := v.(int64)
|
||||||
|
f = float64(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func AnyInteger(v any) (i int64, ok bool) {
|
||||||
|
ok = true
|
||||||
|
switch intval := v.(type) {
|
||||||
|
case int:
|
||||||
|
i = int64(intval)
|
||||||
|
case uint8:
|
||||||
|
i = int64(intval)
|
||||||
|
case uint16:
|
||||||
|
i = int64(intval)
|
||||||
|
case uint64:
|
||||||
|
i = int64(intval)
|
||||||
|
case uint32:
|
||||||
|
i = int64(intval)
|
||||||
|
case int8:
|
||||||
|
i = int64(intval)
|
||||||
|
case int16:
|
||||||
|
i = int64(intval)
|
||||||
|
case int32:
|
||||||
|
i = int64(intval)
|
||||||
|
case int64:
|
||||||
|
i = intval
|
||||||
|
default:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func FixAnyNumber(v any) (fixed any) {
|
||||||
|
switch unboxed := v.(type) {
|
||||||
|
case int:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case int8:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case int16:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case int32:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case uint:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case uint8:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case uint16:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case uint32:
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
case uint64:
|
||||||
|
if unboxed <= MaxUint64Allowed {
|
||||||
|
fixed = int64(unboxed)
|
||||||
|
} else {
|
||||||
|
fixed = float64(unboxed)
|
||||||
|
}
|
||||||
|
case float32:
|
||||||
|
fixed = float64(unboxed)
|
||||||
|
default:
|
||||||
|
fixed = v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToGoInt(value any, description string) (i int, err error) {
|
||||||
|
if valueInt64, ok := value.(int64); ok {
|
||||||
|
i = int(valueInt64)
|
||||||
|
} else if valueInt, ok := value.(int); ok {
|
||||||
|
i = valueInt
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToGoInt64(value any, description string) (i int64, err error) {
|
||||||
|
if valueInt64, ok := value.(int64); ok {
|
||||||
|
i = valueInt64
|
||||||
|
} else if valueInt, ok := value.(int); ok {
|
||||||
|
i = int64(valueInt)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// string.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsString(v any) (ok bool) {
|
||||||
|
_, ok = v.(string)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToGoString(value any, description string) (s string, err error) {
|
||||||
|
if s, ok := value.(string); ok {
|
||||||
|
return s, nil
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// term.go
|
||||||
|
package kern
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Term interface {
|
||||||
|
fmt.Stringer
|
||||||
|
// Children() []Term
|
||||||
|
Source() string
|
||||||
|
GetChildCount() (count int)
|
||||||
|
GetChild(index int) Term
|
||||||
|
GetChildSource(index int) string
|
||||||
|
Compute(ctx ExprContext) (result any, err error)
|
||||||
|
IsAssign() bool
|
||||||
|
Errorf(template string, args ...any) (err error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// lib-ext-darwin.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const SHAREDLIBRARY_EXTENSION = ".dylib"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// lib-ext-linux.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const SHAREDLIBRARY_EXTENSION = ".so"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// lib-ext-windows.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
const SHAREDLIBRARY_EXTENSION = ".dll"
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// list-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LinkedListIterator struct {
|
||||||
|
a *kern.LinkedList
|
||||||
|
count int64
|
||||||
|
index int64
|
||||||
|
current *kern.ListNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinkedListIterator(list *kern.LinkedList, args []any) (it *LinkedListIterator) {
|
||||||
|
it = &LinkedListIterator{a: list, count: 0, index: -1, current: list.FirstNode()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) String() string {
|
||||||
|
var l = int64(0)
|
||||||
|
if it.a != nil {
|
||||||
|
l = int64(it.a.Len())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$([<#%d>])", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) TypeName() string {
|
||||||
|
return "LinkedListIterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) HasOperation(name string) bool {
|
||||||
|
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Current() (item any, err error) {
|
||||||
|
if it.current != nil {
|
||||||
|
item = it.current.Data()
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Next() (item any, err error) {
|
||||||
|
if it.current != nil {
|
||||||
|
item = it.current.Data()
|
||||||
|
it.current = it.current.Next()
|
||||||
|
it.index++
|
||||||
|
it.count++
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Reset() error {
|
||||||
|
it.current = it.a.FirstNode()
|
||||||
|
it.index = -1
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *LinkedListIterator) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// list-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListIterator struct {
|
||||||
|
a *kern.ListType
|
||||||
|
count int64
|
||||||
|
index int64
|
||||||
|
start int64
|
||||||
|
stop int64
|
||||||
|
step int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
|
||||||
|
var argc int = 0
|
||||||
|
listLen := int64(len(([]any)(*list)))
|
||||||
|
if args != nil {
|
||||||
|
argc = len(args)
|
||||||
|
}
|
||||||
|
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
|
||||||
|
if argc >= 1 {
|
||||||
|
if i, err := kern.ToGoInt64(args[0], "start index"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = listLen + i
|
||||||
|
}
|
||||||
|
it.start = i
|
||||||
|
}
|
||||||
|
if argc >= 2 {
|
||||||
|
if i, err := kern.ToGoInt64(args[1], "stop index"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = listLen + i
|
||||||
|
}
|
||||||
|
it.stop = i
|
||||||
|
}
|
||||||
|
if argc >= 3 {
|
||||||
|
if i, err := kern.ToGoInt64(args[2], "step"); err == nil {
|
||||||
|
if i < 0 {
|
||||||
|
i = -i
|
||||||
|
}
|
||||||
|
if it.start > it.stop {
|
||||||
|
it.step = -i
|
||||||
|
} else {
|
||||||
|
it.step = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.index = it.start - it.step
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArrayIterator(array []any) (it *ListIterator) {
|
||||||
|
it = &ListIterator{a: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: int64(len(array)) - 1, step: 1}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) String() string {
|
||||||
|
var l = int64(0)
|
||||||
|
if it.a != nil {
|
||||||
|
l = int64(len(*it.a))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("$([#%d])", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) TypeName() string {
|
||||||
|
return "ListIterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) HasOperation(name string) bool {
|
||||||
|
//yes := name == expr.NextName || name == expr.ResetName || name == expr.IndexName || name == expr.CountName || name == expr.CurrentName
|
||||||
|
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
|
||||||
|
return yes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
case kern.ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case kern.CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = int64(it.Index())
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.count
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Current() (item any, err error) {
|
||||||
|
a := *(it.a)
|
||||||
|
if it.start <= it.stop {
|
||||||
|
if it.stop < int64(len(a)) && it.index >= it.start && it.index <= it.stop {
|
||||||
|
item = a[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if it.start < int64(len(a)) && it.index >= it.stop && it.index <= it.start {
|
||||||
|
item = a[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Next() (item any, err error) {
|
||||||
|
it.index += it.step
|
||||||
|
if item, err = it.Current(); err != io.EOF {
|
||||||
|
it.count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Index() int64 {
|
||||||
|
return it.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Count() int64 {
|
||||||
|
return it.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Reset() error {
|
||||||
|
it.index = it.start - it.step
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Clean() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// operand-const.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
// -------- bool const term
|
|
||||||
func newBoolTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
class: classConst,
|
|
||||||
kind: kindBool,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- integer const term
|
|
||||||
func newIntegerTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
class: classConst,
|
|
||||||
kind: kindInteger,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- float const term
|
|
||||||
func newFloatTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
class: classConst,
|
|
||||||
kind: kindFloat,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- string const term
|
|
||||||
func newStringTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
class: classConst,
|
|
||||||
kind: kindString,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- eval func
|
|
||||||
func evalConst(ctx exprContext, self *term) (v any, err error) {
|
|
||||||
v = self.tk.Value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
func init() {
|
|
||||||
registerTermConstructor(SymString, newStringTerm)
|
|
||||||
registerTermConstructor(SymInteger, newIntegerTerm)
|
|
||||||
registerTermConstructor(SymFloat, newFloatTerm)
|
|
||||||
registerTermConstructor(SymBool, newBoolTerm)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-dict.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- dict term
|
||||||
|
func newDictTerm(args map[any]*scan.Term) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *scan.NewValueToken(0, 0, scan.SymDict, "{}", args),
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalDict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- dict func
|
||||||
|
func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
dict, _ := opTerm.Value().(map[any]*scan.Term)
|
||||||
|
items := make(kern.DictType, len(dict))
|
||||||
|
for key, tree := range dict {
|
||||||
|
var param any
|
||||||
|
if param, err = tree.Compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var keyValue any
|
||||||
|
if keyValue, err = (key.(*scan.Term)).Compute(ctx); err == nil {
|
||||||
|
if kern.IsInteger(keyValue) || kern.IsString(keyValue) {
|
||||||
|
items[keyValue] = param
|
||||||
|
} else {
|
||||||
|
err = key.(*scan.Term).Errorf("dict key can be integer or string, got %s", kern.TypeName(keyValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = &items
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-expr.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- expr term
|
||||||
|
func newExprTerm(root *scan.Term) *scan.Term {
|
||||||
|
tk := scan.NewValueToken(root.Tk.Row(), root.Tk.Col(), scan.SymExpression, root.Source(), root)
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalExpr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval expr
|
||||||
|
func evalExpr(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
if ast, ok := opTerm.Value().(*scan.Term); ok {
|
||||||
|
v, err = ast.Compute(ctx)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("expression expected, got %T", opTerm.Value())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+77
-22
@@ -1,33 +1,88 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// operand-func.go
|
// operand-func.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// -------- function term
|
import (
|
||||||
func newFuncTerm(tk *Token, args []*term) *term {
|
"errors"
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
class: classVar,
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
kind: kindUnknown,
|
)
|
||||||
parent: nil,
|
|
||||||
children: args,
|
// -------- function call term
|
||||||
position: posLeaf,
|
func newFuncCallTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
|
||||||
priority: priValue,
|
var pos scan.TermPosition = scan.PosLeaf
|
||||||
evalFunc: evalFunc,
|
if len(args) > 0 {
|
||||||
|
pos = scan.PosMultifix
|
||||||
|
}
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Parent: nil,
|
||||||
|
Children: args,
|
||||||
|
Position: pos,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalFuncCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func call
|
||||||
func evalFunc(ctx exprContext, self *term) (v any, err error) {
|
// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
name, _ := self.tk.Value.(string)
|
// name, _ := opTerm.Tk.Value.(string)
|
||||||
params := make([]any, len(self.children))
|
// params := make([]any, len(opTerm.Children), len(opTerm.Children)+5)
|
||||||
for i, tree := range self.children {
|
// for i, tree := range opTerm.Children {
|
||||||
var param any
|
// var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
// if param, err = tree.Compute(ctx); err != nil {
|
||||||
break
|
// break
|
||||||
|
// }
|
||||||
|
// params[i] = param
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err == nil {
|
||||||
|
// v, err = CallFunction(ctx, name, params)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalFuncCall(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
name, _ := opTerm.Tk.Value.(string)
|
||||||
|
v, err = kern.CallFunctionByTerm(ctx, name, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- function definition term
|
||||||
|
func newFuncDefTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk, // value is the expression body
|
||||||
|
Parent: nil,
|
||||||
|
Children: args, // function params
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalFuncDef,
|
||||||
}
|
}
|
||||||
params[i] = param
|
}
|
||||||
|
|
||||||
|
// -------- eval func def
|
||||||
|
func evalFuncDef(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
bodySpec := opTerm.Value()
|
||||||
|
if ast, ok := bodySpec.(*scan.Ast); ok {
|
||||||
|
paramList := make([]kern.ExprFuncParam, 0, len(opTerm.Children))
|
||||||
|
for _, param := range opTerm.Children {
|
||||||
|
var defValue any
|
||||||
|
flags := kern.FuncParamFlags(0)
|
||||||
|
if len(param.Children) > 0 {
|
||||||
|
flags |= kern.PfDefault
|
||||||
|
if defValue, err = param.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if err == nil {
|
}
|
||||||
v, err = ctx.Call(name, params)
|
info := kern.NewFuncParamFlagDef(param.Source(), flags, defValue)
|
||||||
|
paramList = append(paramList, info)
|
||||||
|
}
|
||||||
|
v = newExprFunctor(ast, paramList, ctx)
|
||||||
|
} else {
|
||||||
|
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- iterator term
|
||||||
|
|
||||||
|
func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
|
||||||
|
tk.Sym = scan.SymIterator
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Parent: nil,
|
||||||
|
Children: args,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalIterator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval iterator
|
||||||
|
|
||||||
|
func evalTermArray(ctx kern.ExprContext, terms []*scan.Term) (values []any, err error) {
|
||||||
|
values = make([]any, len(terms))
|
||||||
|
for i, t := range terms {
|
||||||
|
var value any
|
||||||
|
if value, err = t.Compute(ctx); err == nil {
|
||||||
|
values[i] = value
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalFirstChild(ctx kern.ExprContext, iteratorTerm *scan.Term) (value any, err error) {
|
||||||
|
if len(iteratorTerm.Children) < 1 || iteratorTerm.Children[0] == nil {
|
||||||
|
err = iteratorTerm.Errorf("missing the data-source parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err = iteratorTerm.Children[0].Compute(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataSourceDict(iteratorTerm *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) {
|
||||||
|
if dictAny, ok := firstChildValue.(*kern.DictType); ok {
|
||||||
|
requiredFields := []string{kern.NextName}
|
||||||
|
fieldsMask := 0b1
|
||||||
|
foundFields := 0
|
||||||
|
ds = make(map[string]kern.Functor)
|
||||||
|
for keyAny, item := range *dictAny {
|
||||||
|
if key, ok := keyAny.(string); ok {
|
||||||
|
if functor, ok := item.(kern.Functor); ok {
|
||||||
|
ds[key] = functor
|
||||||
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
|
foundFields |= 1 << index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check required functions
|
||||||
|
if foundFields != fieldsMask {
|
||||||
|
missingFields := make([]string, 0, len(requiredFields))
|
||||||
|
for index, field := range requiredFields {
|
||||||
|
if (foundFields & (1 << index)) == 0 {
|
||||||
|
missingFields = append(missingFields, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = iteratorTerm.Children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIterator(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var firstChildValue any
|
||||||
|
var ds map[string]kern.Functor
|
||||||
|
|
||||||
|
if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
if ds != nil {
|
||||||
|
if len(ds) > 0 {
|
||||||
|
var dc *dataCursor
|
||||||
|
dcCtx := ctx.Clone()
|
||||||
|
if initFunc, exists := ds[kern.InitName]; exists && initFunc != nil {
|
||||||
|
var args []any
|
||||||
|
var resource any
|
||||||
|
if len(opTerm.Children) > 1 {
|
||||||
|
if args, err = evalTermArray(ctx, opTerm.Children[1:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = []any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
actualParams := kern.BindActualParams(initFunc, args)
|
||||||
|
|
||||||
|
initCtx := ctx.Clone()
|
||||||
|
if resource, err = initFunc.InvokeNamed(initCtx, kern.InitName, actualParams); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kern.ExportObjects(dcCtx, initCtx)
|
||||||
|
dc = NewDataCursor(dcCtx, ds, resource)
|
||||||
|
} else {
|
||||||
|
dc = NewDataCursor(dcCtx, ds, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
v = dc
|
||||||
|
} else {
|
||||||
|
if dictIt, ok := firstChildValue.(*kern.DictType); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
|
||||||
|
v, err = NewDictIterator(dictIt, args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.Children[0].Errorf("the data-source must be a dictionary")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if list, ok := firstChildValue.(*kern.ListType); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
|
||||||
|
v = NewListIterator(list, args)
|
||||||
|
}
|
||||||
|
} else if list, ok := firstChildValue.(*kern.LinkedList); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
|
||||||
|
v = NewLinkedListIterator(list, args)
|
||||||
|
}
|
||||||
|
} else if intVal, ok := firstChildValue.(int64); ok {
|
||||||
|
var args []any
|
||||||
|
if args, err = evalSiblings(ctx, opTerm.Children, intVal); err == nil {
|
||||||
|
v, err = NewIntIterator(args)
|
||||||
|
}
|
||||||
|
} else if it, ok := firstChildValue.(kern.Iterator); ok {
|
||||||
|
v, err = NewIterIter(it, ctx, opTerm.Children[1:])
|
||||||
|
} else {
|
||||||
|
var siblings []any
|
||||||
|
if siblings, err = evalSiblings(ctx, opTerm.Children, firstChildValue); err == nil {
|
||||||
|
v = NewArrayIterator(siblings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func evalIterIter(ctx kern.ExprContext, firstChildValue any, siblings []any) (v any, err error) {
|
||||||
|
// var op kern.Functor
|
||||||
|
// var args map[string]any
|
||||||
|
|
||||||
|
// if it, ok := firstChildValue.(kern.Iterator); ok {
|
||||||
|
// if len(siblings) > 1 {
|
||||||
|
// if op, ok = siblings[1].(kern.Functor); ok {
|
||||||
|
// args = make(map[string]any, len(siblings)-2)
|
||||||
|
// for i, arg := range siblings[2:] {
|
||||||
|
// switch a := arg.(type) {
|
||||||
|
// case *kern.DictType:
|
||||||
|
// for keyAny, item := range *a {
|
||||||
|
// if key, ok := keyAny.(string); ok {
|
||||||
|
// args[key] = item
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// args["arg"+strconv.Itoa(i+1)] = arg
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else if op == nil {
|
||||||
|
// return nil, fmt.Errorf("the first sibling parameter must be a functor to be used as operation for the iterator")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// v, err = NewIterIter(it, ctx, op, args)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalSiblings(ctx kern.ExprContext, terms []*scan.Term, firstChildValue any) (list []any, err error) {
|
||||||
|
items := make([]any, 0, len(terms))
|
||||||
|
for i, tree := range terms {
|
||||||
|
var param any
|
||||||
|
if i == 0 {
|
||||||
|
if firstChildValue == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
param = firstChildValue
|
||||||
|
} else if param, err = tree.Compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items = append(items, param)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
list = items
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-list.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- list term
|
||||||
|
// func newLinkedListTermA(args ...*scan.Term) *scan.Term {
|
||||||
|
// return newLinkedListTerm(0, 0, args)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func newLinkedListTerm(row, col int, args []*scan.Term) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *scan.NewValueToken(row, col, scan.SymLinkedList, "[<>]", args),
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalLinkedList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- list func
|
||||||
|
func evalLinkedList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
list, _ := opTerm.Value().([]*scan.Term)
|
||||||
|
items := kern.NewLinkedList()
|
||||||
|
for _, tree := range list {
|
||||||
|
var param any
|
||||||
|
if param, err = tree.Compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items.PushBack(param)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = items
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-list.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- list term
|
||||||
|
func newListTermA(args ...*scan.Term) *scan.Term {
|
||||||
|
return newListTerm(0, 0, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListTerm(row, col int, args []*scan.Term) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *scan.NewValueToken(row, col, scan.SymList, "[]", args),
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- list func
|
||||||
|
func evalList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
list, _ := opTerm.Value().([]*scan.Term)
|
||||||
|
items := make(kern.ListType, len(list))
|
||||||
|
for i, tree := range list {
|
||||||
|
var param any
|
||||||
|
if param, err = tree.Compute(ctx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items[i] = param
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = &items
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-literal.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- literal term
|
||||||
|
func newLiteralTerm(tk *scan.Token) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalLiteral,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval func
|
||||||
|
func evalLiteral(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
v = opTerm.Tk.Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymString, newLiteralTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymInteger, newLiteralTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymFloat, newLiteralTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymFraction, newLiteralTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymBool, newLiteralTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwNil, newLiteralTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-selector-case.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- selector case term
|
||||||
|
|
||||||
|
type selectorCase struct {
|
||||||
|
filterList *scan.Term
|
||||||
|
caseExpr kern.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *selectorCase) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if sc.filterList != nil {
|
||||||
|
sc.filterList.ToString(&sb)
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
sb.WriteByte('{')
|
||||||
|
sb.WriteString(sc.caseExpr.String())
|
||||||
|
sb.WriteByte('}')
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSelectorCaseTerm(row, col int, filterList *scan.Term, caseExpr kern.Expr) *scan.Term {
|
||||||
|
tk := scan.NewValueToken(row, col, scan.SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Parent: nil,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalSelectorCase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval selector case
|
||||||
|
func evalSelectorCase(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var ok bool
|
||||||
|
if v, ok = opTerm.Value().(*selectorCase); !ok {
|
||||||
|
err = fmt.Errorf("selector-case expected, got %T", opTerm.Value())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+28
-15
@@ -1,32 +1,45 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// operand-var.go
|
// operand-var.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
// -------- variable term
|
// -------- variable term
|
||||||
func newVarTerm(tk *Token) *term {
|
func newVarTerm(tk *scan.Token) *scan.Term {
|
||||||
return &term{
|
t := &scan.Term{
|
||||||
tk: *tk,
|
Tk: *tk,
|
||||||
class: classVar,
|
Parent: nil,
|
||||||
kind: kindUnknown,
|
Children: nil,
|
||||||
parent: nil,
|
Position: scan.PosLeaf,
|
||||||
children: nil,
|
Priority: scan.PriValue,
|
||||||
position: posLeaf,
|
EvalFunc: evalVar,
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalVar,
|
|
||||||
}
|
}
|
||||||
|
t.Tk.Sym = scan.SymVariable
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
func evalVar(ctx exprContext, self *term) (v any, err error) {
|
func evalVar(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
if v, exists = ctx.GetValue(self.tk.source); !exists {
|
name := opTerm.Source()
|
||||||
err = fmt.Errorf("undefined variable %q", self.tk.source)
|
if v, exists = ctx.GetVar(name); !exists {
|
||||||
|
if info, exists := ctx.GetFuncInfo(name); exists {
|
||||||
|
v = info.Functor()
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("undefined variable or function %q", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymIdentifier, newVarTerm)
|
scan.RegisterTermConstructor(scan.SymIdentifier, newVarTerm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserightChilded.
|
||||||
|
|
||||||
|
// operator-assign.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- assign term
|
||||||
|
|
||||||
|
func newAssignTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriAssign,
|
||||||
|
EvalFunc: evalAssign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
v, err = generalEvalAssign(ctx, opTerm, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeepCopyAssignTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriAssign,
|
||||||
|
EvalFunc: evalDeepCopyAssign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDeepCopyAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
v, err = generalEvalAssign(ctx, opTerm, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) {
|
||||||
|
var collectionValue, keyListValue, keyValue any
|
||||||
|
var keyList *kern.ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 {
|
||||||
|
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if keyValue = (*keyList)[0]; keyValue == nil {
|
||||||
|
err = keyListTerm.Errorf("index/key is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch collection := collectionValue.(type) {
|
||||||
|
case *kern.ListType:
|
||||||
|
if index, ok := keyValue.(int64); ok {
|
||||||
|
err = collection.SetItem(index, value)
|
||||||
|
} else {
|
||||||
|
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
|
||||||
|
}
|
||||||
|
case *kern.DictType:
|
||||||
|
collection.SetItem(keyValue, value)
|
||||||
|
default:
|
||||||
|
err = collectionTerm.Errorf("collection expected")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any, deepCopy bool) (err error) {
|
||||||
|
if leftTerm.Symbol() == scan.SymIndex {
|
||||||
|
err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v)
|
||||||
|
} else {
|
||||||
|
if deepCopy {
|
||||||
|
v = kern.Clone(v)
|
||||||
|
}
|
||||||
|
ctx.UnsafeSetVar(leftTerm.Source(), v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAssignDictItem(ctx kern.ExprContext, dotTerm *scan.Term, valueTerm kern.Term) (v any, err error) {
|
||||||
|
var ok bool
|
||||||
|
var dictAny, dotKey any
|
||||||
|
var dict *kern.DictType
|
||||||
|
|
||||||
|
if err = dotTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dotLeftTerm := dotTerm.GetChild(0)
|
||||||
|
if dictAny, err = dotLeftTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dict, ok = dictAny.(*kern.DictType); !ok {
|
||||||
|
err = dotTerm.Tk.ErrorExpectedGot(kern.DictTypeName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dotRightTerm := dotTerm.Children[1]
|
||||||
|
dotRightSym := dotRightTerm.Symbol()
|
||||||
|
if dotRightSym == scan.SymVariable || dotRightSym == scan.SymString {
|
||||||
|
dotKey = util.UnquoteString(dotRightTerm.Source())
|
||||||
|
} else if dotKey, err = dotRightTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err = valueTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dict.SetItem(dotKey, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (v any, err error) {
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.Children[0]
|
||||||
|
leftSym := leftTerm.Symbol()
|
||||||
|
if leftSym == scan.SymDot {
|
||||||
|
return evalAssignDictItem(ctx, opTerm.Children[0], opTerm.GetChild(1))
|
||||||
|
} else if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
|
||||||
|
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rightChild := opTerm.Children[1]
|
||||||
|
|
||||||
|
if v, err = rightChild.Compute(ctx); err == nil {
|
||||||
|
if functor, ok := v.(kern.Functor); ok {
|
||||||
|
if leftSym == scan.SymVariable {
|
||||||
|
if info := functor.GetFunc(); info != nil {
|
||||||
|
ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params())
|
||||||
|
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||||
|
paramSpecs := util.ForAll(funcDef.params, func(p kern.ExprFuncParam) kern.ExprFuncParam { return p })
|
||||||
|
|
||||||
|
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("unknown function %s()", rightChild.Source())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = assignValue(ctx, leftTerm, v, deepCopy)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = assignValue(ctx, leftTerm, v, deepCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
v = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- assign term
|
||||||
|
|
||||||
|
func newOpAssignTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriAssign,
|
||||||
|
EvalFunc: evalOpAssign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term) (value any, err error) {
|
||||||
|
var collectionValue, keyListValue, keyValue any
|
||||||
|
var keyList *kern.ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 {
|
||||||
|
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if keyValue = (*keyList)[0]; keyValue == nil {
|
||||||
|
err = keyListTerm.Errorf("index/key is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch collection := collectionValue.(type) {
|
||||||
|
case *kern.ListType:
|
||||||
|
if index, ok := keyValue.(int64); ok {
|
||||||
|
value = (*collection)[index]
|
||||||
|
} else {
|
||||||
|
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
|
||||||
|
}
|
||||||
|
case *kern.DictType:
|
||||||
|
value = (*collection)[keyValue]
|
||||||
|
default:
|
||||||
|
err = collectionTerm.Errorf("collection expected")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAssignValue(ctx kern.ExprContext, leftTerm *scan.Term) (value any, err error) {
|
||||||
|
if leftTerm.Symbol() == scan.SymIndex {
|
||||||
|
value, err = getCollectionItemValue(ctx, leftTerm.Children[0], leftTerm.Children[1])
|
||||||
|
} else {
|
||||||
|
value, _ = ctx.GetVar(leftTerm.Source())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var rightValue, leftValue any
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.Children[0]
|
||||||
|
leftSym := leftTerm.Symbol()
|
||||||
|
if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
|
||||||
|
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rightChild := opTerm.Children[1]
|
||||||
|
|
||||||
|
if rightValue, err = rightChild.Compute(ctx); err == nil {
|
||||||
|
if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
|
||||||
|
switch opTerm.Symbol() {
|
||||||
|
case scan.SymPlusEqual:
|
||||||
|
v, err = sumValues(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymMinusEqual:
|
||||||
|
v, err = diffValues(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymStarEqual:
|
||||||
|
v, err = mulValues(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymSlashEqual:
|
||||||
|
v, err = divValues(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymPercEqual:
|
||||||
|
v, err = remainderValues(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymAmpersandEqual:
|
||||||
|
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymVertBarEqual:
|
||||||
|
v, err = bitwiseOr(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymCaretEqual:
|
||||||
|
v, err = bitwiseXor(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymDoubleLessEqual:
|
||||||
|
v, err = bitLeftShift(opTerm, leftValue, rightValue)
|
||||||
|
case scan.SymDoubleGreaterEqual:
|
||||||
|
v, err = bitRightShift(opTerm, leftValue, rightValue)
|
||||||
|
default:
|
||||||
|
err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = assignValue(ctx, leftTerm, v, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymEqual, newAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymColonEqual, newDeepCopyAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymPlusEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymMinusEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymStarEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymSlashEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymPercEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymDoubleLessEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymDoubleGreaterEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymAmpersandEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymVertBarEqual, newOpAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymCaretEqual, newOpAssignTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-bitwise.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- Bitwise NOT term
|
||||||
|
|
||||||
|
func newBitwiseNotTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 1),
|
||||||
|
Position: scan.PosPrefix,
|
||||||
|
Priority: scan.PriBitwiseNot,
|
||||||
|
EvalFunc: evalBitwiseNot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var value any
|
||||||
|
|
||||||
|
if value, err = opTerm.EvalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if kern.IsInteger(value) {
|
||||||
|
i, _ := value.(int64)
|
||||||
|
v = ^i
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatiblePrefixPostfixType(value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise AND term
|
||||||
|
|
||||||
|
func newBitwiseAndTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriBitwiseAnd,
|
||||||
|
EvalFunc: evalBitwiseAnd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseAnd(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt & rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseAnd(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise OR term
|
||||||
|
|
||||||
|
func newBitwiseOrTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriBitwiseOr,
|
||||||
|
EvalFunc: evalBitwiseOr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseOr(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt | rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseOr(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitwiseOr(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise XOR term
|
||||||
|
|
||||||
|
func newBitwiseXorTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriBitwiseXor,
|
||||||
|
EvalFunc: evalBitwiseXor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseXor(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt ^ rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseXor(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitwiseXor(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymTilde, newBitwiseNotTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymAmpersand, newBitwiseAndTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymVertBar, newBitwiseOrTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymCaret, newBitwiseXorTerm)
|
||||||
|
}
|
||||||
+119
-44
@@ -1,106 +1,181 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// operator-bool.go
|
// operator-bool.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
//-------- NOT term
|
//-------- NOT term
|
||||||
|
|
||||||
func newNotTerm(tk *Token) (inst *term) {
|
func newNotTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
return &term{
|
return &scan.Term{
|
||||||
tk: *tk,
|
Tk: *tk,
|
||||||
class: classOperator,
|
Children: make([]*scan.Term, 0, 1),
|
||||||
kind: kindBool,
|
Position: scan.PosPrefix,
|
||||||
children: make([]*term, 0, 1),
|
Priority: scan.PriNot,
|
||||||
position: posPrefix,
|
EvalFunc: evalNot,
|
||||||
priority: priNot,
|
|
||||||
evalFunc: evalNot,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalNot(ctx exprContext, self *term) (v any, err error) {
|
func evalNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
var rightValue any
|
var rightValue any
|
||||||
|
|
||||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
if rightValue, err = opTerm.EvalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, ok := toBool(rightValue); ok {
|
if b, ok := kern.ToBool(rightValue); ok {
|
||||||
v = !b
|
v = !b
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = opTerm.ErrIncompatiblePrefixPostfixType(rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------- AND term
|
//-------- AND term
|
||||||
|
|
||||||
func newAndTerm(tk *Token) (inst *term) {
|
func newAndTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
return &term{
|
return &scan.Term{
|
||||||
tk: *tk,
|
Tk: *tk,
|
||||||
class: classOperator,
|
Children: make([]*scan.Term, 0, 2),
|
||||||
kind: kindBool,
|
Position: scan.PosInfix,
|
||||||
children: make([]*term, 0, 2),
|
Priority: scan.PriAnd,
|
||||||
position: posInfix,
|
EvalFunc: evalAnd,
|
||||||
priority: priAnd,
|
|
||||||
evalFunc: evalAnd,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalAnd(ctx exprContext, self *term) (v any, err error) {
|
func evalAnd(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
|
if kern.CtrlIsEnabled(ctx, kern.ControlBoolShortcut) {
|
||||||
|
v, err = evalAndWithShortcut(ctx, self)
|
||||||
|
} else {
|
||||||
|
v, err = evalAndWithoutShortcut(ctx, self)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAndWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
var leftBool, rightBool bool
|
var leftBool, rightBool bool
|
||||||
var lok, rok bool
|
var lok, rok bool
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = self.EvalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = kern.ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = kern.ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool && rightBool
|
v = leftBool && rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAndWithShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if err = self.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = self.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftBool, lok := kern.ToBool(leftValue); !lok {
|
||||||
|
// err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", expr.TypeName(leftValue))
|
||||||
|
// return
|
||||||
|
err = self.ErrIncompatibleType(leftValue, "left")
|
||||||
|
} else if !leftBool {
|
||||||
|
v = false
|
||||||
|
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
|
||||||
|
if rightBool, rok := kern.ToBool(rightValue); rok {
|
||||||
|
v = rightBool
|
||||||
|
} else {
|
||||||
|
err = self.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------- OR term
|
//-------- OR term
|
||||||
|
|
||||||
func newOrTerm(tk *Token) (inst *term) {
|
func newOrTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
return &term{
|
return &scan.Term{
|
||||||
tk: *tk,
|
Tk: *tk,
|
||||||
class: classOperator,
|
Children: make([]*scan.Term, 0, 2),
|
||||||
kind: kindBool,
|
Position: scan.PosInfix,
|
||||||
children: make([]*term, 0, 2),
|
Priority: scan.PriOr,
|
||||||
position: posInfix,
|
EvalFunc: evalOr,
|
||||||
priority: priOr,
|
|
||||||
evalFunc: evalOr,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalOr(ctx exprContext, self *term) (v any, err error) {
|
func evalOr(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
|
if kern.CtrlIsEnabled(ctx, kern.ControlBoolShortcut) {
|
||||||
|
v, err = evalOrWithShortcut(ctx, self)
|
||||||
|
} else {
|
||||||
|
v, err = evalOrWithoutShortcut(ctx, self)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalOrWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
var leftBool, rightBool bool
|
var leftBool, rightBool bool
|
||||||
var lok, rok bool
|
var lok, rok bool
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = self.EvalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = kern.ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = kern.ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool || rightBool
|
v = leftBool || rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalOrWithShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if err = self.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = self.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftBool, lok := kern.ToBool(leftValue); !lok {
|
||||||
|
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", kern.TypeName(leftValue))
|
||||||
|
return
|
||||||
|
} else if leftBool {
|
||||||
|
v = true
|
||||||
|
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
|
||||||
|
if rightBool, rok := kern.ToBool(rightValue); rok {
|
||||||
|
v = rightBool
|
||||||
|
} else {
|
||||||
|
err = self.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymNot, newNotTerm)
|
scan.RegisterTermConstructor(scan.SymNot, newNotTerm)
|
||||||
registerTermConstructor(SymAnd, newAndTerm)
|
scan.RegisterTermConstructor(scan.SymAnd, newAndTerm)
|
||||||
registerTermConstructor(SymOr, newOrTerm)
|
scan.RegisterTermConstructor(scan.SymOr, newOrTerm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-builtin.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- builtin term
|
||||||
|
|
||||||
|
func newBuiltinTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 1),
|
||||||
|
Position: scan.PosPrefix,
|
||||||
|
Priority: scan.PriSign,
|
||||||
|
EvalFunc: evalBuiltin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBuiltin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if kern.IsString(childValue) {
|
||||||
|
module, _ := childValue.(string)
|
||||||
|
count, err = ImportInContextByGlobPattern(ctx, module)
|
||||||
|
} else {
|
||||||
|
var moduleSpec any
|
||||||
|
var it kern.Iterator
|
||||||
|
if it, err = NewIterator(ctx, childValue, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
||||||
|
if module, ok := moduleSpec.(string); ok {
|
||||||
|
if ImportInContext(ctx, module) {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("unknown builtin module %q", module)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("expected string at item nr %d, got %s", it.Index()+1, kern.TypeName(moduleSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = int64(count)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwBuiltin, newBuiltinTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-but.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- but term
|
||||||
|
|
||||||
|
func newButTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriBut,
|
||||||
|
EvalFunc: evalBut,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBut(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
_, v, err = opTerm.EvalInfix(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwBut, newButTerm)
|
||||||
|
}
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-cat.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- cat term
|
||||||
|
|
||||||
|
func newCatTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriIterOp,
|
||||||
|
EvalFunc: evalCat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalCat(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var itLeft, itRight kern.Iterator
|
||||||
|
// var item any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if itLeft, ok = leftValue.(kern.Iterator); !ok {
|
||||||
|
if itLeft, err = NewIterator(ctx, leftValue, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", kern.TypeName(leftValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if itRight, ok = rightValue.(kern.Iterator); !ok {
|
||||||
|
if itRight, err = NewIterator(ctx, rightValue, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", kern.TypeName(rightValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// values := kern.NewListA()
|
||||||
|
// for _, it := range []kern.Iterator{itLeft, itRight} {
|
||||||
|
// for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
// values.AppendItem(item)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if err == io.EOF {
|
||||||
|
// err = nil
|
||||||
|
// }
|
||||||
|
// v = values
|
||||||
|
v = newCatIterator(itLeft, itRight)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const catIteratorType = "cat"
|
||||||
|
|
||||||
|
type catIterator struct {
|
||||||
|
kern.IteratorBase
|
||||||
|
itLeft kern.Iterator
|
||||||
|
itRight kern.Iterator
|
||||||
|
itCurrent kern.Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCatIterator(itLeft, itRight kern.Iterator) (it *catIterator) {
|
||||||
|
it = &catIterator{
|
||||||
|
IteratorBase: kern.IteratorBase{},
|
||||||
|
itLeft: itLeft,
|
||||||
|
itRight: itRight,
|
||||||
|
itCurrent: itLeft,
|
||||||
|
}
|
||||||
|
it.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *catIterator) TypeName() string {
|
||||||
|
return catIteratorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *catIterator) String() string {
|
||||||
|
return fmt.Sprintf("$(%s %s %s)", it.itLeft, catIteratorType, it.itRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *catIterator) Next() (item any, err error) {
|
||||||
|
if it.itCurrent == nil {
|
||||||
|
err = io.EOF
|
||||||
|
} else {
|
||||||
|
if item, err = it.itCurrent.Next(); err == nil {
|
||||||
|
it.Increment()
|
||||||
|
} else if err == io.EOF {
|
||||||
|
if it.itCurrent == it.itLeft {
|
||||||
|
it.itCurrent = it.itRight
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if item, err = it.itCurrent.Next(); err == nil {
|
||||||
|
it.Increment()
|
||||||
|
} else {
|
||||||
|
it.itCurrent = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.SetCurrent(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (it *catIterator) Reset() (err error) {
|
||||||
|
// err = it.itLeft.Reset()
|
||||||
|
// it.IteratorBase.Reset()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (it *catIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
// case kern.ResetName:
|
||||||
|
// err = it.Reset()
|
||||||
|
// case kern.CleanName:
|
||||||
|
// err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = it.Index()
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.Count()
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwCat, newCatTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-context-value.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- context term
|
||||||
|
|
||||||
|
func newContextTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 1),
|
||||||
|
Position: scan.PosPrefix,
|
||||||
|
Priority: scan.PriIncDec,
|
||||||
|
EvalFunc: evalContextValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
var sourceCtx kern.ExprContext
|
||||||
|
|
||||||
|
if len(opTerm.Children) == 0 {
|
||||||
|
sourceCtx = ctx
|
||||||
|
} else if opTerm.Children[0].Symbol() == scan.SymVariable && opTerm.Children[0].Source() == "global" {
|
||||||
|
sourceCtx = ctx.GetGlobal()
|
||||||
|
} else if childValue, err = opTerm.EvalPrefix(ctx); err == nil {
|
||||||
|
if dc, ok := childValue.(*dataCursor); ok {
|
||||||
|
sourceCtx = dc.ctx
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceCtx != nil {
|
||||||
|
v = sourceCtx.ToDict()
|
||||||
|
} else if childValue != nil {
|
||||||
|
it, ok := childValue.(kern.Iterator)
|
||||||
|
if !ok {
|
||||||
|
it, err = NewIterator(ctx, childValue, nil)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
var item any
|
||||||
|
values := kern.NewLinkedListA()
|
||||||
|
for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
values.PushBack(item)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
v = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymDoubleDollar, newContextTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-ctrl.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- export all term
|
||||||
|
|
||||||
|
func newExportAllTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: nil,
|
||||||
|
Position: scan.PosLeaf,
|
||||||
|
Priority: scan.PriValue,
|
||||||
|
EvalFunc: evalExportAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalExportAll(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
CtrlEnable(ctx, kern.ControlExportAll)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymDoubleAt, newExportAllTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-default.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- default term
|
||||||
|
|
||||||
|
func newDefaultTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriDefault,
|
||||||
|
EvalFunc: evalDefault,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.Children[0]
|
||||||
|
if leftTerm.Tk.Sym != scan.SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists {
|
||||||
|
v = leftValue
|
||||||
|
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
v = rightValue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- alternate term
|
||||||
|
|
||||||
|
func newAlternateTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriDefault,
|
||||||
|
EvalFunc: evalAlternate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAlternate(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.Children[0]
|
||||||
|
if leftTerm.Tk.Sym != scan.SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists && leftValue != nil {
|
||||||
|
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
v = rightValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = leftValue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- default assign term
|
||||||
|
|
||||||
|
func newDefaultAssignTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriDefault,
|
||||||
|
EvalFunc: evalAssignDefault,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAssignDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.Children[0]
|
||||||
|
if leftTerm.Tk.Sym != scan.SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists {
|
||||||
|
v = leftValue
|
||||||
|
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
if functor, ok := rightValue.(kern.Functor); ok {
|
||||||
|
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, []kern.ExprFuncParam{
|
||||||
|
kern.NewFuncParamFlag(kern.ParamValue, kern.PfDefault|kern.PfRepeat),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
v = rightValue
|
||||||
|
ctx.UnsafeSetVar(leftTerm.Source(), rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymDoubleQuestion, newDefaultTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymQuestionEqual, newDefaultAssignTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymQuestionExclam, newAlternateTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-digest.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- digest term
|
||||||
|
|
||||||
|
func newDigestTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriIterOp,
|
||||||
|
EvalFunc: evalDigest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDigest(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var it kern.Iterator
|
||||||
|
var item, lastValue any
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", kern.TypeName(leftValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastValue = nil
|
||||||
|
for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
ctx.SetVar("_", item)
|
||||||
|
ctx.SetVar("__", it.Index())
|
||||||
|
ctx.SetVar("#", it.Count())
|
||||||
|
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
if rightValue == nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
lastValue = rightValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.DeleteVar("#")
|
||||||
|
ctx.DeleteVar("__")
|
||||||
|
ctx.DeleteVar("_")
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
v = lastValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwDigest, newDigestTerm)
|
||||||
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-dot.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- dot term
|
||||||
|
func newDotTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriDot,
|
||||||
|
EvalFunc: evalDot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTerm := opTerm.Children[1]
|
||||||
|
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case kern.ExtIterator:
|
||||||
|
if indexTerm.Symbol() == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
|
||||||
|
opName := indexTerm.Source()
|
||||||
|
if unboxedValue.HasOperation(opName) {
|
||||||
|
v, err = unboxedValue.CallOperation(opName, map[string]any{})
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
||||||
|
v = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Tk.ErrorExpectedGot("identifier")
|
||||||
|
}
|
||||||
|
case *kern.DictType:
|
||||||
|
// var ok bool
|
||||||
|
// s := opTerm.Children[1].Symbol()
|
||||||
|
// if s == scan.SymVariable || s == scan.SymString {
|
||||||
|
// src := opTerm.Children[1].Source()
|
||||||
|
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
|
||||||
|
// src = src[1 : len(src)-1]
|
||||||
|
// }
|
||||||
|
// if v, ok = unboxedValue.GetItem(src); !ok {
|
||||||
|
// err = opTerm.Errorf("key %q not found", src)
|
||||||
|
// }
|
||||||
|
// } else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
// if v, ok = unboxedValue.GetItem(rightValue); !ok {
|
||||||
|
// err = opTerm.Errorf("key %q not found", rightValue)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
v, err = dotGetDictItemValue(ctx, unboxedValue, opTerm.Children[1])
|
||||||
|
default:
|
||||||
|
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func dotGetDictItemValue(ctx kern.ExprContext, d *kern.DictType, rightTerm *scan.Term) (v any, err error) {
|
||||||
|
var ok bool
|
||||||
|
var rightValue any
|
||||||
|
s := rightTerm.Symbol()
|
||||||
|
if s == scan.SymVariable || s == scan.SymString {
|
||||||
|
// src := rightTerm.Source()
|
||||||
|
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
|
||||||
|
// src = src[1 : len(src)-1]
|
||||||
|
// }
|
||||||
|
src := util.UnquoteString(rightTerm.Source())
|
||||||
|
if v, ok = d.GetItem(src); !ok {
|
||||||
|
err = errDictKeyNotFound(rightTerm, src)
|
||||||
|
}
|
||||||
|
} else if rightValue, err = rightTerm.Compute(ctx); err == nil {
|
||||||
|
if v, ok = d.GetItem(rightValue); !ok {
|
||||||
|
err = errDictKeyNotFound(rightTerm, rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func errDictKeyNotFound(term *scan.Term, key any) error {
|
||||||
|
if s, ok := key.(string); ok {
|
||||||
|
return term.Errorf("key %q not found", s)
|
||||||
|
}
|
||||||
|
return term.Errorf("key %v not found", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymDot, newDotTerm)
|
||||||
|
}
|
||||||
+21
-15
@@ -1,30 +1,36 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// operator-fact.go
|
// operator-fact.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
//-------- fact term
|
//-------- fact term
|
||||||
|
|
||||||
func newFactTerm(tk *Token) (inst *term) {
|
func newFactTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
return &term{
|
return &scan.Term{
|
||||||
tk: *tk,
|
Tk: *tk,
|
||||||
class: classOperator,
|
Children: make([]*scan.Term, 0, 1),
|
||||||
kind: kindInteger,
|
Position: scan.PosPostfix,
|
||||||
children: make([]*term, 0, 1),
|
Priority: scan.PriFact,
|
||||||
position: posPostfix,
|
EvalFunc: evalFact,
|
||||||
priority: priFact,
|
|
||||||
evalFunc: evalFact,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalFact(ctx exprContext, self *term) (v any, err error) {
|
func evalFact(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
var leftValue any
|
var leftValue any
|
||||||
|
|
||||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInteger(leftValue) {
|
if kern.IsInteger(leftValue) {
|
||||||
if i, _ := leftValue.(int64); i >= 0 {
|
if i, _ := leftValue.(int64); i >= 0 {
|
||||||
f := int64(1)
|
f := int64(1)
|
||||||
for k := int64(1); k <= i; k++ {
|
for k := int64(1); k <= i; k++ {
|
||||||
@@ -35,12 +41,12 @@ func evalFact(ctx exprContext, self *term) (v any, err error) {
|
|||||||
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
|
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(leftValue)
|
err = opTerm.Errorf("incompatible type for factorial: %s", kern.TypeName(leftValue))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymExclamation, newFactTerm)
|
scan.RegisterTermConstructor(scan.SymExclamation, newFactTerm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-filter.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- map term
|
||||||
|
|
||||||
|
func newFilterTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriIterOp,
|
||||||
|
EvalFunc: evalFilter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
var leftValue any
|
||||||
|
var it kern.Iterator
|
||||||
|
// var item any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok = leftValue.(kern.Iterator); !ok {
|
||||||
|
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", kern.TypeName(leftValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// values := kern.NewListA()
|
||||||
|
// for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
// ctx.SetVar("_", item)
|
||||||
|
// ctx.SetVar("__", it.Index())
|
||||||
|
// ctx.SetVar("_#", it.Count())
|
||||||
|
// if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
|
||||||
|
// if success, valid := kern.ToBool(rightValue); valid {
|
||||||
|
// if success {
|
||||||
|
// values.AppendItem(item)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ctx.DeleteVar("_#")
|
||||||
|
// ctx.DeleteVar("__")
|
||||||
|
// ctx.DeleteVar("_")
|
||||||
|
// if err != nil {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if err == io.EOF {
|
||||||
|
// err = nil
|
||||||
|
// }
|
||||||
|
// v = values
|
||||||
|
|
||||||
|
v = newFilerIterator(ctx, it, opTerm.Children[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterIteratorType = "filter"
|
||||||
|
|
||||||
|
type filterIterator struct {
|
||||||
|
kern.IteratorBase
|
||||||
|
ctx kern.ExprContext
|
||||||
|
itSrc kern.Iterator
|
||||||
|
expr *scan.Term
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilerIterator(ctx kern.ExprContext, itSrc kern.Iterator, filterExpr *scan.Term) (it *filterIterator) {
|
||||||
|
it = &filterIterator{
|
||||||
|
IteratorBase: kern.IteratorBase{},
|
||||||
|
ctx: ctx,
|
||||||
|
itSrc: itSrc,
|
||||||
|
expr: filterExpr,
|
||||||
|
}
|
||||||
|
it.IteratorBase.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *filterIterator) TypeName() string {
|
||||||
|
return filterIteratorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *filterIterator) String() string {
|
||||||
|
return fmt.Sprintf("$(%s %s bool-expr)", it.itSrc, filterIteratorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *filterIterator) Next() (item any, err error) {
|
||||||
|
var attempt, result any
|
||||||
|
|
||||||
|
ctx := it.ctx
|
||||||
|
|
||||||
|
for attempt, err = it.itSrc.Next(); err == nil; attempt, err = it.itSrc.Next() {
|
||||||
|
ctx.SetVar("_", attempt)
|
||||||
|
ctx.SetVar("__", it.Index())
|
||||||
|
ctx.SetVar("#", it.Count())
|
||||||
|
result, err = it.expr.Compute(ctx)
|
||||||
|
ctx.DeleteVar("#")
|
||||||
|
ctx.DeleteVar("__")
|
||||||
|
ctx.DeleteVar("_")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if success, valid := kern.ToBool(result); valid {
|
||||||
|
if success {
|
||||||
|
item = attempt
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", result, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it.SetCurrent(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (it *filterIterator) Reset() (err error) {
|
||||||
|
// err = it.itLeft.Reset()
|
||||||
|
// it.IteratorBase.Reset()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (it *filterIterator) CallOperation(name string, args map[string]any) (v any, err error) {
|
||||||
|
switch name {
|
||||||
|
case kern.NextName:
|
||||||
|
v, err = it.Next()
|
||||||
|
// case kern.ResetName:
|
||||||
|
// err = it.Reset()
|
||||||
|
// case kern.CleanName:
|
||||||
|
// err = it.Clean()
|
||||||
|
case kern.IndexName:
|
||||||
|
v = it.Index()
|
||||||
|
case kern.CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case kern.CountName:
|
||||||
|
v = it.Count()
|
||||||
|
default:
|
||||||
|
err = kern.ErrNoOperation(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwFilter, newFilterTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-fraction.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- fraction term
|
||||||
|
func newFractionTerm(tk *scan.Token) *scan.Term {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriFraction,
|
||||||
|
EvalFunc: evalFraction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval func
|
||||||
|
func evalFraction(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var numValue, denValue any
|
||||||
|
var num, den int64
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if numValue, denValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if num, ok = numValue.(int64); !ok {
|
||||||
|
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if den, ok = denValue.(int64); !ok {
|
||||||
|
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if den == 0 {
|
||||||
|
err = opTerm.Errorf("division by zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if den < 0 {
|
||||||
|
den = -den
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
if num != 0 {
|
||||||
|
if g := kern.Gcd(num, den); g != 1 {
|
||||||
|
num = num / g
|
||||||
|
den = den / g
|
||||||
|
}
|
||||||
|
if den == 1 {
|
||||||
|
v = num
|
||||||
|
} else {
|
||||||
|
// v = &expr.FractionType{num, den}
|
||||||
|
v = kern.NewFraction(num, den)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// v = &FractionType{0, den}
|
||||||
|
v = kern.NewFraction(0, den)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
// registerTermConstructor(SymVertBar, newFractionTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymColon, newFractionTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-groupby.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- group by term
|
||||||
|
|
||||||
|
func newGroupByTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriIterOp,
|
||||||
|
EvalFunc: evalGroupBy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var it kern.Iterator
|
||||||
|
var item any
|
||||||
|
var sKey string
|
||||||
|
var keyByIndex bool
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if err = opTerm.CheckOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok = leftValue.(kern.Iterator); !ok {
|
||||||
|
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", kern.TypeName(leftValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rightTk := opTerm.Children[1].Tk
|
||||||
|
if rightTk.IsSymbol(scan.SymVariable) && rightTk.Source() == "__" {
|
||||||
|
keyByIndex = true
|
||||||
|
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
} else if kern.IsString(rightValue) {
|
||||||
|
sKey = rightValue.(string)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("right operand of GROUPBY must be a string or identifier '__'; got %s", kern.TypeName(rightValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
values := kern.MakeDict()
|
||||||
|
for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
ctx.SetVar("_", item)
|
||||||
|
ctx.SetVar("__", it.Index())
|
||||||
|
ctx.SetVar("#", it.Count())
|
||||||
|
|
||||||
|
var sItemKey string
|
||||||
|
|
||||||
|
if d, ok := item.(*kern.DictType); ok {
|
||||||
|
if keyByIndex || len(sKey) == 0 {
|
||||||
|
sItemKey = strconv.Itoa(int(it.Index()))
|
||||||
|
} else if d.HasKey(sKey) {
|
||||||
|
if keyValue, exists := d.GetItem(sKey); exists {
|
||||||
|
if s, ok := keyValue.(string); ok {
|
||||||
|
sItemKey = s
|
||||||
|
} else {
|
||||||
|
sItemKey = fmt.Sprintf("%v", keyValue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sItemKey = "_"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sItemKey = "_"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sItemKey = strconv.Itoa(int(it.Index()))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ls *kern.ListType
|
||||||
|
if lsAny, exists := values.GetItem(sItemKey); exists && lsAny != nil {
|
||||||
|
ls = lsAny.(*kern.ListType)
|
||||||
|
}
|
||||||
|
if ls == nil {
|
||||||
|
ls = kern.NewListA()
|
||||||
|
}
|
||||||
|
ls.AppendItem(item)
|
||||||
|
values.SetItem(sItemKey, ls)
|
||||||
|
|
||||||
|
ctx.DeleteVar("#")
|
||||||
|
ctx.DeleteVar("__")
|
||||||
|
ctx.DeleteVar("_")
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
v = values
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwGroupBy, newGroupByTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-in.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- in term
|
||||||
|
|
||||||
|
func newInTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriRelational,
|
||||||
|
EvalFunc: evalIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
|
// _, ok = d[target]
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalIn(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if kern.IsList(rightValue) {
|
||||||
|
list, _ := rightValue.(*kern.ListType)
|
||||||
|
v = list.IndexDeepSameCmp(leftValue) >= 0
|
||||||
|
} else if kern.IsDict(rightValue) {
|
||||||
|
dict, _ := rightValue.(*kern.DictType)
|
||||||
|
v = dict.HasKey(leftValue)
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwIn, newInTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-include.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- include term
|
||||||
|
|
||||||
|
func newIncludeTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 1),
|
||||||
|
Position: scan.PosPrefix,
|
||||||
|
Priority: scan.PriSign,
|
||||||
|
EvalFunc: evalInclude,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalInclude(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if kern.IsList(childValue) {
|
||||||
|
list, _ := childValue.(*kern.ListType)
|
||||||
|
for i, filePathSpec := range *list {
|
||||||
|
if filePath, ok := filePathSpec.(string); ok {
|
||||||
|
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("can't load file %q", filePath)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if kern.IsString(childValue) {
|
||||||
|
filePath, _ := childValue.(string)
|
||||||
|
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
//v = count
|
||||||
|
v = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymKwInclude, newIncludeTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-index.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------- index term
|
||||||
|
func newIndexTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriDot,
|
||||||
|
EvalFunc: evalIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyKey(indexList *kern.ListType) (index any, err error) {
|
||||||
|
index = (*indexList)[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIndex(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (index int, err error) {
|
||||||
|
var v int
|
||||||
|
|
||||||
|
if v, err = kern.ToGoInt((*indexList)[0], "index expression"); err == nil {
|
||||||
|
if v < 0 && v >= -maxValue {
|
||||||
|
v = maxValue + v
|
||||||
|
}
|
||||||
|
if v >= 0 && v < maxValue {
|
||||||
|
index = v
|
||||||
|
} else {
|
||||||
|
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyRange(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (startIndex, endIndex int, err error) {
|
||||||
|
v, _ := ((*indexList)[0]).(*intPair)
|
||||||
|
startIndex = v.a
|
||||||
|
endIndex = v.b
|
||||||
|
if endIndex == kern.ConstLastIndex {
|
||||||
|
endIndex = maxValue
|
||||||
|
}
|
||||||
|
if startIndex < 0 && startIndex >= -maxValue {
|
||||||
|
startIndex = maxValue + startIndex
|
||||||
|
}
|
||||||
|
if endIndex < 0 && endIndex >= -maxValue {
|
||||||
|
endIndex = maxValue + endIndex
|
||||||
|
}
|
||||||
|
if startIndex < 0 || startIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
|
||||||
|
} else if endIndex < 0 || endIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
|
||||||
|
} else if startIndex > endIndex {
|
||||||
|
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var indexList *kern.ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTerm := opTerm.Children[1]
|
||||||
|
if indexList, ok = rightValue.(*kern.ListType); !ok {
|
||||||
|
err = opTerm.Errorf("invalid index expression")
|
||||||
|
return
|
||||||
|
} else if len(*indexList) != 1 {
|
||||||
|
err = indexTerm.Errorf("one index only is allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if kern.IsInteger((*indexList)[0]) {
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *kern.ListType:
|
||||||
|
var index int
|
||||||
|
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||||
|
v = (*unboxedValue)[index]
|
||||||
|
}
|
||||||
|
case *kern.LinkedList:
|
||||||
|
var index int
|
||||||
|
if index, err = verifyIndex(indexTerm, indexList, unboxedValue.Len()); err == nil {
|
||||||
|
v, err = unboxedValue.At(index)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var index int
|
||||||
|
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||||
|
v = string(unboxedValue[index])
|
||||||
|
}
|
||||||
|
case *kern.DictType:
|
||||||
|
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
|
||||||
|
default:
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if isIntPair((*indexList)[0]) {
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *kern.ListType:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||||
|
sublist := kern.ListType((*unboxedValue)[start:end])
|
||||||
|
v = &sublist
|
||||||
|
}
|
||||||
|
case *kern.LinkedList:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, unboxedValue.Len()); err == nil {
|
||||||
|
v = unboxedValue.Sub(start, end)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||||
|
v = unboxedValue[start:end]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if kern.IsDict(leftValue) {
|
||||||
|
d := leftValue.(*kern.DictType)
|
||||||
|
v, err = getDictItem(d, indexTerm, indexList, rightValue)
|
||||||
|
} else {
|
||||||
|
rightChild := opTerm.Children[1]
|
||||||
|
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDictItem(d *kern.DictType, indexTerm *scan.Term, indexList *kern.ListType, rightValue any) (v any, err error) {
|
||||||
|
var ok bool
|
||||||
|
var indexValue any
|
||||||
|
|
||||||
|
if indexValue, err = verifyKey(indexList); err == nil {
|
||||||
|
if v, ok = (*d)[indexValue]; !ok {
|
||||||
|
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymIndex, newIndexTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-insert.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- prepend term
|
||||||
|
|
||||||
|
func newPrependTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriInsert,
|
||||||
|
EvalFunc: evalPrepend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppendTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 2),
|
||||||
|
Position: scan.PosInfix,
|
||||||
|
Priority: scan.PriInsert,
|
||||||
|
EvalFunc: evalAppend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prependToList(opTerm *scan.Term, leftValue, rightValue any) (result any, err error) {
|
||||||
|
if list, ok := rightValue.(*kern.ListType); ok {
|
||||||
|
var it kern.Iterator
|
||||||
|
if it, ok = leftValue.(kern.Iterator); !ok {
|
||||||
|
it = NewFormalIterator(leftValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls := kern.NewLinkedList()
|
||||||
|
ls.SeqPushBack(it)
|
||||||
|
|
||||||
|
newList := kern.ListType(nil)
|
||||||
|
if newSize := len(*list) + int(ls.Len()); newSize > cap(*list) {
|
||||||
|
newList = make([]any, 0, newSize)
|
||||||
|
for node := ls.FirstNode(); node != nil; node = node.Next() {
|
||||||
|
newList = append(newList, node.Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range *list {
|
||||||
|
newList = append(newList, item)
|
||||||
|
}
|
||||||
|
result = &newList
|
||||||
|
} else if list, ok := rightValue.(*kern.LinkedList); ok {
|
||||||
|
var it kern.Iterator
|
||||||
|
if it, ok = leftValue.(kern.Iterator); !ok {
|
||||||
|
it = NewFormalIterator(leftValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = list.SeqPushBack(it)
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPrepend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = prependToList(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendToList(opTerm *scan.Term, leftValue, rightValue any) (result any, err error) {
|
||||||
|
if list, ok := leftValue.(*kern.ListType); ok {
|
||||||
|
var it kern.Iterator
|
||||||
|
if it, ok = rightValue.(kern.Iterator); !ok {
|
||||||
|
it = NewFormalIterator(rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
ls := kern.NewLinkedList()
|
||||||
|
ls.SeqPushBack(it)
|
||||||
|
|
||||||
|
newList := *list
|
||||||
|
if newSize := len(*list) + int(ls.Len()); newSize > cap(*list) {
|
||||||
|
newList = make([]any, 0, newSize)
|
||||||
|
for _, item := range *list {
|
||||||
|
newList = append(newList, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for node := ls.FirstNode(); node != nil; node = node.Next() {
|
||||||
|
newList = append(newList, node.Data())
|
||||||
|
}
|
||||||
|
result = &newList
|
||||||
|
} else if list, ok := leftValue.(*kern.LinkedList); ok {
|
||||||
|
var it kern.Iterator
|
||||||
|
if it, ok = rightValue.(kern.Iterator); !ok {
|
||||||
|
it = NewFormalIterator(rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = list.SeqPushBack(it)
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAppend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = appendToList(opTerm, leftValue, rightValue)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if IsList(leftValue) {
|
||||||
|
// list, _ := leftValue.(*ListType)
|
||||||
|
// newList := append(*list, rightValue)
|
||||||
|
// v = &newList
|
||||||
|
// if
|
||||||
|
// } else {
|
||||||
|
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
scan.RegisterTermConstructor(scan.SymPlusGreater, newPrependTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymLessPlus, newAppendTerm)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-iter-value.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.portale-stac.it/go-pkg/expr/kern"
|
||||||
|
"git.portale-stac.it/go-pkg/expr/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
//-------- iter value term
|
||||||
|
|
||||||
|
func newIterValueTerm(tk *scan.Token) (inst *scan.Term) {
|
||||||
|
return &scan.Term{
|
||||||
|
Tk: *tk,
|
||||||
|
Children: make([]*scan.Term, 0, 1),
|
||||||
|
Position: scan.PosPrefix,
|
||||||
|
Priority: scan.PriDereference,
|
||||||
|
EvalFunc: evalIterValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalIterValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
|
||||||
|
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok := childValue.(kern.Iterator); ok {
|
||||||
|
v, err = it.Current()
|
||||||
|
} else {
|
||||||
|
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
|
||||||
|
scan.RegisterTermConstructor(scan.SymDereference, newIterValueTerm)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user