From 73c828d99b65be37306778a3484bea4eb9fcec98 Mon Sep 17 00:00:00 2001 From: PuqiAR Date: Fri, 19 Dec 2025 20:38:40 +0800 Subject: [PATCH] v0.3.1 --- .build_session | 1 + .build_timestamp | 1 + .clang-format | 214 ++ .gitignore | 9 + .gitmodules | 0 Tools/SpeedTest/fib.fig | 11 + Tools/SpeedTest/fib.py | 11 + Tools/SpeedTest/fibLoopTest.fig | 24 + .../fig-languague-syntax/.vscodeignore | 4 + .../fig-languague-syntax/CHANGELOG.md | 9 + .../fig-languague-syntax/LICENSE.md | 0 .../fig-languague-syntax/README.md | 3 + .../fig-languague-syntax-0.0.1.vsix | Bin 0 -> 3316 bytes .../language-configuration.json | 82 + .../fig-languague-syntax/package.json | 33 + .../syntaxes/fig.tmLanguage.json | 90 + .../vsc-extension-quickstart.md | 29 + compile_flags.txt | 3 + docs/FigDesignDocument.md | 248 ++ include/Ast/AccessModifier.hpp | 14 + include/Ast/BinaryExpr.hpp | 29 + include/Ast/ContainerInitExprs.hpp | 65 + include/Ast/ControlSt.hpp | 46 + include/Ast/ExpressionStmt.hpp | 21 + include/Ast/FunctionCall.hpp | 54 + include/Ast/FunctionDefSt.hpp | 46 + include/Ast/IfSt.hpp | 68 + include/Ast/ImplementSt.hpp | 0 include/Ast/InitExpr.hpp | 28 + include/Ast/LambdaExpr.hpp | 43 + include/Ast/StructDefSt.hpp | 45 + include/Ast/TernaryExpr.hpp | 29 + include/Ast/UnaryExpr.hpp | 27 + include/Ast/ValueExpr.hpp | 26 + include/Ast/VarAssignSt.hpp | 26 + include/Ast/VarDef.hpp | 34 + include/Ast/VarExpr.hpp | 24 + include/Ast/WhileSt.hpp | 26 + include/Ast/astBase.hpp | 338 +++ include/Ast/functionParameters.hpp | 39 + include/AstPrinter.hpp | 200 ++ include/Value/BaseValue.hpp | 190 ++ include/Value/Type.hpp | 79 + include/Value/function.hpp | 74 + include/Value/structInstance.hpp | 50 + include/Value/structType.hpp | 95 + include/Value/valueError.hpp | 17 + include/argparse/argparse.hpp | 2589 +++++++++++++++++ include/ast.hpp | 23 + include/builtins.hpp | 152 + include/context.hpp | 168 ++ include/context_forward.hpp | 9 + include/core.hpp | 59 + include/error.hpp | 128 + include/errorLog.hpp | 140 + include/evaluator.hpp | 103 + include/fassert.hpp | 3 + include/fig_string.hpp | 100 + include/lexer.hpp | 95 + include/magic_enum/magic_enum.hpp | 1508 ++++++++++ include/magic_enum/magic_enum_all.hpp | 44 + include/magic_enum/magic_enum_containers.hpp | 1174 ++++++++ include/magic_enum/magic_enum_flags.hpp | 222 ++ include/magic_enum/magic_enum_format.hpp | 114 + include/magic_enum/magic_enum_fuse.hpp | 89 + include/magic_enum/magic_enum_iostream.hpp | 117 + include/magic_enum/magic_enum_switch.hpp | 195 ++ include/magic_enum/magic_enum_utility.hpp | 138 + include/module.hpp | 47 + include/parser.hpp | 265 ++ include/token.hpp | 167 ++ include/utf8_iterator.hpp | 260 ++ include/utils.hpp | 109 + include/value.hpp | 373 +++ include/warning.hpp | 55 + src/evaluator.cpp | 488 ++++ src/lexer.cpp | 524 ++++ src/main.cpp | 187 ++ src/parser.cpp | 848 ++++++ src/value.cpp | 39 + src/waring.cpp | 9 + test.fig | 1 + xmake.lua | 21 + 83 files changed, 13068 insertions(+) create mode 100644 .build_session create mode 100644 .build_timestamp create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Tools/SpeedTest/fib.fig create mode 100644 Tools/SpeedTest/fib.py create mode 100644 Tools/SpeedTest/fibLoopTest.fig create mode 100644 Tools/VscodeExtension/fig-languague-syntax/.vscodeignore create mode 100644 Tools/VscodeExtension/fig-languague-syntax/CHANGELOG.md create mode 100644 Tools/VscodeExtension/fig-languague-syntax/LICENSE.md create mode 100644 Tools/VscodeExtension/fig-languague-syntax/README.md create mode 100644 Tools/VscodeExtension/fig-languague-syntax/fig-languague-syntax-0.0.1.vsix create mode 100644 Tools/VscodeExtension/fig-languague-syntax/language-configuration.json create mode 100644 Tools/VscodeExtension/fig-languague-syntax/package.json create mode 100644 Tools/VscodeExtension/fig-languague-syntax/syntaxes/fig.tmLanguage.json create mode 100644 Tools/VscodeExtension/fig-languague-syntax/vsc-extension-quickstart.md create mode 100644 compile_flags.txt create mode 100644 docs/FigDesignDocument.md create mode 100644 include/Ast/AccessModifier.hpp create mode 100644 include/Ast/BinaryExpr.hpp create mode 100644 include/Ast/ContainerInitExprs.hpp create mode 100644 include/Ast/ControlSt.hpp create mode 100644 include/Ast/ExpressionStmt.hpp create mode 100644 include/Ast/FunctionCall.hpp create mode 100644 include/Ast/FunctionDefSt.hpp create mode 100644 include/Ast/IfSt.hpp create mode 100644 include/Ast/ImplementSt.hpp create mode 100644 include/Ast/InitExpr.hpp create mode 100644 include/Ast/LambdaExpr.hpp create mode 100644 include/Ast/StructDefSt.hpp create mode 100644 include/Ast/TernaryExpr.hpp create mode 100644 include/Ast/UnaryExpr.hpp create mode 100644 include/Ast/ValueExpr.hpp create mode 100644 include/Ast/VarAssignSt.hpp create mode 100644 include/Ast/VarDef.hpp create mode 100644 include/Ast/VarExpr.hpp create mode 100644 include/Ast/WhileSt.hpp create mode 100644 include/Ast/astBase.hpp create mode 100644 include/Ast/functionParameters.hpp create mode 100644 include/AstPrinter.hpp create mode 100644 include/Value/BaseValue.hpp create mode 100644 include/Value/Type.hpp create mode 100644 include/Value/function.hpp create mode 100644 include/Value/structInstance.hpp create mode 100644 include/Value/structType.hpp create mode 100644 include/Value/valueError.hpp create mode 100644 include/argparse/argparse.hpp create mode 100644 include/ast.hpp create mode 100644 include/builtins.hpp create mode 100644 include/context.hpp create mode 100644 include/context_forward.hpp create mode 100644 include/core.hpp create mode 100644 include/error.hpp create mode 100644 include/errorLog.hpp create mode 100644 include/evaluator.hpp create mode 100644 include/fassert.hpp create mode 100644 include/fig_string.hpp create mode 100644 include/lexer.hpp create mode 100644 include/magic_enum/magic_enum.hpp create mode 100644 include/magic_enum/magic_enum_all.hpp create mode 100644 include/magic_enum/magic_enum_containers.hpp create mode 100644 include/magic_enum/magic_enum_flags.hpp create mode 100644 include/magic_enum/magic_enum_format.hpp create mode 100644 include/magic_enum/magic_enum_fuse.hpp create mode 100644 include/magic_enum/magic_enum_iostream.hpp create mode 100644 include/magic_enum/magic_enum_switch.hpp create mode 100644 include/magic_enum/magic_enum_utility.hpp create mode 100644 include/module.hpp create mode 100644 include/parser.hpp create mode 100644 include/token.hpp create mode 100644 include/utf8_iterator.hpp create mode 100644 include/utils.hpp create mode 100644 include/value.hpp create mode 100644 include/warning.hpp create mode 100644 src/evaluator.cpp create mode 100644 src/lexer.cpp create mode 100644 src/main.cpp create mode 100644 src/parser.cpp create mode 100644 src/value.cpp create mode 100644 src/waring.cpp create mode 100644 test.fig create mode 100644 xmake.lua diff --git a/.build_session b/.build_session new file mode 100644 index 0000000..5290c1e --- /dev/null +++ b/.build_session @@ -0,0 +1 @@ +20251216111638 \ No newline at end of file diff --git a/.build_timestamp b/.build_timestamp new file mode 100644 index 0000000..76a4bc5 --- /dev/null +++ b/.build_timestamp @@ -0,0 +1 @@ +2025-12-16 11:16:38 \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..901fe52 --- /dev/null +++ b/.clang-format @@ -0,0 +1,214 @@ +# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto +Language: Cpp +# BasedOnStyle: LLVM + +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 + +# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) +AlignAfterOpenBracket: Align + +# 连续赋值时,对齐所有等号 +AlignConsecutiveAssignments: false + +# 连续声明时,对齐所有声明的变量名 +AlignConsecutiveDeclarations: false + +# 右对齐逃脱换行(使用反斜杠换行)的反斜杠 +AlignEscapedNewlines: Right + +# 水平对齐二元和三元表达式的操作数 +AlignOperands: true + +# 对齐连续的尾随的注释 +AlignTrailingComments: true + +# 允许函数声明的所有参数在放在下一行 +AllowAllParametersOfDeclarationOnNextLine: true + +# 允许短的块放在同一行 +AllowShortBlocksOnASingleLine: true + +# 允许短的case标签放在同一行 +AllowShortCaseLabelsOnASingleLine: true + +# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All +AllowShortFunctionsOnASingleLine: Inline + +# 允许短的if语句保持在同一行 +AllowShortIfStatementsOnASingleLine: true + +# 允许短的循环保持在同一行 +AllowShortLoopsOnASingleLine: true + +# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), +# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) +AlwaysBreakAfterReturnType: None + +# 总是在多行string字面量前换行 +AlwaysBreakBeforeMultilineStrings: false + +# 总是在template声明后换行 +AlwaysBreakTemplateDeclarations: true + +# false表示函数实参要么都在同一行,要么都各自一行 +BinPackArguments: true + +# false表示所有形参要么都在同一行,要么都各自一行 +BinPackParameters: true + +# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 +BraceWrapping: + # class定义后面 + AfterClass: true + # 控制语句后面 + AfterControlStatement: true + # enum定义后面 + AfterEnum: true + # 函数定义后面 + AfterFunction: true + # 命名空间定义后面 + AfterNamespace: true + # struct定义后面 + AfterStruct: true + # union定义后面 + AfterUnion: true + # extern之后 + AfterExternBlock: false + # catch之前 + BeforeCatch: true + # else之前 + BeforeElse: true + # 缩进大括号 + IndentBraces: false + # 分离空函数 + SplitEmptyFunction: true + # 分离空语句 + SplitEmptyRecord: false + # 分离空命名空间 + SplitEmptyNamespace: false + +# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) +BreakBeforeBinaryOperators: NonAssignment + +# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), +# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), +# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom +# 注:这里认为语句块也属于函数 +BreakBeforeBraces: Custom + +# 在三元运算符前换行 +BreakBeforeTernaryOperators: false + +# 在构造函数的初始化列表的冒号后换行 +BreakConstructorInitializers: AfterColon + +#BreakInheritanceList: AfterColon + +BreakStringLiterals: false + +# 每行字符的限制,0表示没有限制 +ColumnLimit: 0 + +CompactNamespaces: true + +# 构造函数的初始化列表要么都在同一行,要么都各自一行 +ConstructorInitializerAllOnOneLineOrOnePerLine: false + +# 构造函数的初始化列表的缩进宽度 +ConstructorInitializerIndentWidth: 4 + +# 延续的行的缩进宽度 +ContinuationIndentWidth: 4 + +# 去除C++11的列表初始化的大括号{后和}前的空格 +Cpp11BracedListStyle: true + +# 继承最常用的指针和引用的对齐方式 +DerivePointerAlignment: false + +# 固定命名空间注释 +FixNamespaceComments: true + +# 缩进case标签 +IndentCaseLabels: true + +IndentPPDirectives: BeforeHash + +# 缩进宽度 +IndentWidth: 4 + +# 函数返回类型换行时,缩进函数声明或函数定义的函数名 +IndentWrappedFunctionNames: false + +# 保留在块开始处的空行 +KeepEmptyLinesAtTheStartOfBlocks: false + +# 连续空行的最大数量 +MaxEmptyLinesToKeep: 1 + +# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All +NamespaceIndentation: All + +# 指针和引用的对齐: Left, Right, Middle +PointerAlignment: Right + +# 允许重新排版注释 +ReflowComments: true + +# 允许排序#include +SortIncludes: false + +# 允许排序 using 声明 +SortUsingDeclarations: false + +# 在C风格类型转换后添加空格 +SpaceAfterCStyleCast: true +# true -> (int) 0.1 false-> (int)0.1 + +# 在Template 关键字后面添加空格 +SpaceAfterTemplateKeyword: true + +# 在赋值运算符之前添加空格 +SpaceBeforeAssignmentOperators: true + +# SpaceBeforeCpp11BracedList: true + +# SpaceBeforeCtorInitializerColon: true + +# SpaceBeforeInheritanceColon: true + +# 开圆括号之前添加一个空格: Never, ControlStatements, Always +SpaceBeforeParens: ControlStatements + +# SpaceBeforeRangeBasedForLoopColon: true + +# 在空的圆括号中添加空格 +SpaceInEmptyParentheses: false + +# 在尾随的评论前添加的空格数(只适用于//) +SpacesBeforeTrailingComments: 1 + +# 在尖括号的<后和>前添加空格 +SpacesInAngles: false + +# 在C风格类型转换的括号中添加空格 +SpacesInCStyleCastParentheses: false + +# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 +SpacesInContainerLiterals: true + +# 在圆括号的(后和)前添加空格 +SpacesInParentheses: false + +# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 +SpacesInSquareBrackets: false + +# 标准: Cpp03, Cpp11, Auto +Standard: Auto + +# tab宽度 +TabWidth: 4 + +# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always +UseTab: Never \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0239152 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + +.vscode +.VSCodeCounter \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/Tools/SpeedTest/fib.fig b/Tools/SpeedTest/fib.fig new file mode 100644 index 0000000..4e19409 --- /dev/null +++ b/Tools/SpeedTest/fib.fig @@ -0,0 +1,11 @@ +fun fib(x:Int) -> Int +{ + if (x <= 1) + { + return x; + } + return fib(x-1) + fib(x-2); +} + +var result := fib(25); +__fstdout_println("result: ", result); \ No newline at end of file diff --git a/Tools/SpeedTest/fib.py b/Tools/SpeedTest/fib.py new file mode 100644 index 0000000..0d1d315 --- /dev/null +++ b/Tools/SpeedTest/fib.py @@ -0,0 +1,11 @@ +from time import time as tt + +def fib(x:int) -> int: + if x <= 1: return x; + return fib(x-1) + fib(x-2) + +if __name__ == '__main__': + t0 = tt() + result = fib(25) + t1 = tt() + print('cost: ',t1-t0, 'result:', result) diff --git a/Tools/SpeedTest/fibLoopTest.fig b/Tools/SpeedTest/fibLoopTest.fig new file mode 100644 index 0000000..10a7bf6 --- /dev/null +++ b/Tools/SpeedTest/fibLoopTest.fig @@ -0,0 +1,24 @@ +var callCnt:Int = 0; +fun fib(x:Int) -> Int +{ + callCnt = callCnt + 1; + if (x <= 1) + { + return x; + } + return fib(x-1) + fib(x-2); +} + +var fibx:Int; +__fstdout_print("input an index of fib "); +fibx = __fvalue_int_parse(__fstdin_read()); + +var cnt:Int = 0; +__fstdout_println("test forever"); +while (true) +{ + cnt = cnt + 1; + __fstdout_println("test ", cnt,",result: ", fib(fibx)); + __fstdout_println("func `fib` called ", callCnt); + callCnt = 0; +} \ No newline at end of file diff --git a/Tools/VscodeExtension/fig-languague-syntax/.vscodeignore b/Tools/VscodeExtension/fig-languague-syntax/.vscodeignore new file mode 100644 index 0000000..f369b5e --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/.vscodeignore @@ -0,0 +1,4 @@ +.vscode/** +.vscode-test/** +.gitignore +vsc-extension-quickstart.md diff --git a/Tools/VscodeExtension/fig-languague-syntax/CHANGELOG.md b/Tools/VscodeExtension/fig-languague-syntax/CHANGELOG.md new file mode 100644 index 0000000..d813835 --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "fig-languague-syntax" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/Tools/VscodeExtension/fig-languague-syntax/LICENSE.md b/Tools/VscodeExtension/fig-languague-syntax/LICENSE.md new file mode 100644 index 0000000..e69de29 diff --git a/Tools/VscodeExtension/fig-languague-syntax/README.md b/Tools/VscodeExtension/fig-languague-syntax/README.md new file mode 100644 index 0000000..66f88eb --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/README.md @@ -0,0 +1,3 @@ +# fig-languague-syntax README + +Nothing \ No newline at end of file diff --git a/Tools/VscodeExtension/fig-languague-syntax/fig-languague-syntax-0.0.1.vsix b/Tools/VscodeExtension/fig-languague-syntax/fig-languague-syntax-0.0.1.vsix new file mode 100644 index 0000000000000000000000000000000000000000..f5db6ff745ad8a0d667eb7e179bc898c98b653ad GIT binary patch literal 3316 zcmaJ^dpwi-AD_!u940Cqx7^9y!YLg0OEs4Xm0Xv(vuT^llxs1!)G(ynb*4y?;#5l} zDMRkJHH4xgj#7}yU3Vm(cdg30Bna>Gg#DIjhD)TtM$B3!p9x~|xZ15h* z!$x83zvHirF`V;F^ol89` z(wsON%j9=xu(Qe2_=36)j zBS0#+^rfL*fS#{xW!t?D8L-rAj@4Zr>Hz)tCHWv4_9l(I*t zr@C0|B*4HHN+>M%rVsF&OZ#|iVj;19!mFU`Klz{ta+DtMr<8xkx?jf z0X+H1ByVtD@H+2-h|xnzVf(*ON8VW{--@)Ex5hGe_X}o{vg4TQc?@LqTF|HA+*Oo| zlEnDpS|#ih;lEJ!tE?XDwUD0a9WXRC?8@_C;6hzhD&SZjxt!dQg zfKzjqOHz|2hYNy+X4j$y@P-@UUdNUSs$nkK7Ie|FwD97tiKB${_&7fq`Y&=%?Q?bx zGnP4utE3oxXwj&t$H6?4fwK=q7MP5+n^j@Nvy1Z6k&1GZy(&8zMH+ES#W#+PY84GN zUO;M>Wx5w>8E`17Q0&%^OI0)z+jei?4g_ zcWfFpcE!JD+MelA(I_1pL@017+xIqv*aVihQ%RAxvTAQ9w>yp=mRgts|kMZHlVpqq*>gD>>Rr0>H zA4l5AF{dff$qbx~fvoc~*!I!CN`1g4EUq*-A+3{)5K9Ev>TFP9ewsQ- z{&UO@>Z$}KEHNru_DGyR;h`o{Ye<1wUgu8->*?>Ru@#U~EO?4jo}Mnvayz5cV0^pW zBko)gR$+C@$j5wI8JBIV##|7X z%fDoUb_ok>3Ekww={vb=9Imp|W0cHhrsw(Q&(3emY^2|9t@F*%=n39$@Gwi&Kv~Vd zsm$B5Kcx7X#v7d_a?~T?gWxId7%fpAD}Xp~6z|5}1nL-@mYS;3alY|RTi>u{Q3RYD z;z7oGDg#b21cN~Tv>1#1%h>Jg1M^`E`&LIx&28+>fd=CM{=*Gni3E&{Z}kHMhu@B| zy-kCWo&kV>-y{o3Z}4x__$6VEz%$P9IMYq{AjITb{QCZ&ZiiS?_u=;*hf1zbFo}5w zQO;pCOJBRxU-X+m{E94{cn8z>>s5Jk9yt9%SaeinPNq`;D(;fT8!jo64^wozFIl~; z#fjC4`^XeqH+cy!J4POQX;+h=QeDw>PeA%~g=>cHqnY0E^EXjaPcu*Qv0Yru{Luev z^m2Rn)Y^AKMShK^nm=~YL? ze9&a%)`J%+eg23*1f8gt;$C#5D;t#em>;Q*aBVs#q(A;_ZGEjKPklm z{{f66pz+9XFPu7HG&OvX)xXyJrd*Rf9nH`X(1*K|c!EYPt#$`>nhG=N^txz{f;OZCDI6I!J4o?7;V*2p&gKkq{Q(y3v^v|OJ_1t;^Kx<&okb8W}d zmE6OL$M;&7CGD`E)UK7iM~)!d->P#XJf=N*o7Q2lST)i<^uX4GDM!{?|WIeC9bfI>7@+p{^)uGBVmdpjc}z* z+~R-6732#+h}ErE_6x?3Wo4YZ=3YPYd+qD|azuoxNl=y>@@(2?Orai3rzK8@?=jb^ zY4@bDc+9i1;BhjK;r^p@7cI(;bS0Q)%!Gdw<@Xl0kC9Gb zOO}ce<)~=h2;bXX_y(mi-j^G)4~He72=&1RTsf%=P|E9tG3IGqUJ8Xxa-D{c_@F(m zT&0JBWaka9CdXG$`tz91rTnY$h;E7Y+pUK4u9j>n>}=$&j;KDusVjkU|ks8>C| zw_N6UKhO1iVZ-w44!c#6Tf40MX}f1H?kohIOoQ^`pk=8rLrCM;h)3Fp@Zvm5Go>$B zM}7Pqf+*j@ti4LVQ0sqJ;T~~jEO98oxyPX>0}VxL=yNqN3FwvObNm|IC-vD&Ur008 z*;qa(;6@iZ10IOtV)=lL-H_w|A5?*!{Z=)xtlRhY-F9sK8RO@p!aoQgP)#ficRk!C)Xwbiy2lX kZIz#uNt<;)_R}_i|4JwX91JWNc)bBWzX6<3cv!#w23;RkO#lD@ literal 0 HcmV?d00001 diff --git a/Tools/VscodeExtension/fig-languague-syntax/language-configuration.json b/Tools/VscodeExtension/fig-languague-syntax/language-configuration.json new file mode 100644 index 0000000..4942c27 --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/language-configuration.json @@ -0,0 +1,82 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ + "/*", + "*/" + ] + }, + "brackets": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "/\"", + "\"/" + ] + ], + "autoClosingPairs": [ + { + "open": "{", + "close": "}" + }, + { + "open": "[", + "close": "]" + }, + { + "open": "(", + "close": ")" + }, + { + "open": "\"", + "close": "\"", + "notIn": [ + "string" + ] + }, + { + "open": "/\"", + "close": "\"/" + } + ], + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "/\"", + "\"/" + ] + ], + "indentationRules": { + "increaseIndentPattern": "^(?=.*(\\{|\\()|.*/\\\").*$", + "decreaseIndentPattern": "^\\s*[}\\)]|\\s*\"/" + } +} \ No newline at end of file diff --git a/Tools/VscodeExtension/fig-languague-syntax/package.json b/Tools/VscodeExtension/fig-languague-syntax/package.json new file mode 100644 index 0000000..f76a77a --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/package.json @@ -0,0 +1,33 @@ +{ + "name": "fig-languague-syntax", + "displayName": "Fig Languague Syntax", + "description": ":)", + "version": "0.0.1", + "engines": { + "vscode": "^1.96.0" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [ + { + "id": "fig", + "extensions": [ + ".fl" + ], + "aliases": [ + "Fig" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "fig", + "scopeName": "source.fig", + "path": "./syntaxes/fig.tmLanguage.json" + } + ] + } +} \ No newline at end of file diff --git a/Tools/VscodeExtension/fig-languague-syntax/syntaxes/fig.tmLanguage.json b/Tools/VscodeExtension/fig-languague-syntax/syntaxes/fig.tmLanguage.json new file mode 100644 index 0000000..ae957f2 --- /dev/null +++ b/Tools/VscodeExtension/fig-languague-syntax/syntaxes/fig.tmLanguage.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Fig", + "scopeName": "source.fig", + "fileTypes": [ + ".fl" + ], + "patterns": [ + { + "name": "comment.line.double-slash", + "match": "//.*" + }, + { + "name": "comment.block", + "begin": "/\\*", + "end": "\\*/", + "captures": { + "0": { + "name": "comment.block.fig" + } + } + }, + { + "name": "keyword.control", + "match": "\\b(if|else|for|while|continue|break|return|or|not)\\b" + }, + { + "name": "keyword.declaration", + "match": "\\b(var|val|func|module)\\b" + }, + { + "name": "storage.type", + "match": "\\b(Int32|Int64|Float|Double|Map|Bool|Null|String)\\b" + }, + { + "name": "entity.name.function", + "match": "(?<=\\bfunc\\b)\\s+\\w+" + }, + { + "name": "keyword.operator.arrow", + "match": "->" + }, + { + "name": "keyword.operator", + "match": "[+\\-*/%&|>/.vscode/extensions` folder and restart Code. +* To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension. diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..ee95ad3 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,3 @@ +-std=c++2b +-static +-stdlib=libc++ \ No newline at end of file diff --git a/docs/FigDesignDocument.md b/docs/FigDesignDocument.md new file mode 100644 index 0000000..5645676 --- /dev/null +++ b/docs/FigDesignDocument.md @@ -0,0 +1,248 @@ +## `Fig Programming Language` DESIGN DOC + +--- +### 关键词解释 Token + +``` cpp +enum class TokenType : int8_t + { + Illegal = -1, + EndOfFile = 0, + + Comments, + + Identifier, + + /* Keywords */ + And, // and + Or, // or + Not, // not + Import, // import + Function, // fun + Variable, // var + Const, // const + Final, // final + While, // while + For, // for + Struct, // struct + Interface, // interface + Implement, // implement + Public, // public + + // TypeNull, // Null + // TypeInt, // Int + // TypeString, // String + // TypeBool, // Bool + // TypeDouble, // Double + + /* Literal Types (not keyword)*/ + LiteralNumber, // number (int,float...) + LiteralString, // FString + LiteralBool, // bool (true/false) + LiteralNull, // null (Null的唯一实例) + + /* Punct */ + Plus, // + + Minus, // - + Asterisk, // * + Slash, // / + Percent, // % + Caret, // ^ + Ampersand, // & + Pipe, // | + Tilde, // ~ + ShiftLeft, // << + ShiftRight, // >> + // Exclamation, // ! + Question, // ? + Assign, // = + Less, // < + Greater, // > + Dot, // . + Comma, // , + Colon, // : + Semicolon, // ; + SingleQuote, // ' + DoubleQuote, // " + // Backtick, // ` + // At, // @ + // Hash, // # + // Dollar, // $ + // Backslash, // '\' + // Underscore, // _ + LeftParen, // ( + RightParen, // ) + LeftBracket, // [ + RightBracket, // ] + LeftBrace, // { + RightBrace, // } + // LeftArrow, // <- + RightArrow, // -> + // DoubleArrow, // => + Equal, // == + NotEqual, // != + LessEqual, // <= + GreaterEqual, // >= + PlusEqual, // += + MinusEqual, // -= + AsteriskEqual, // *= + SlashEqual, // /= + PercentEqual, // %= + CaretEqual, // ^= + DoublePlus, // ++ + DoubleMinus, // -- + DoubleAmpersand, // && + DoublePipe, // || + Walrus, // := + Power, // ** + }; +``` + +* `Illegal` + 非法Token:无法解析或语法错误 +  +* `EndOfFile` + 即: + ```cpp + EOF + ``` + 文件终止符 +  +* `Comments` + 注释Token,包括单行和多行 +  +* `Identifier` + 标识符,用户定义的‘名字’ +  +* `And` -> `&&` 或 `and` + 逻辑与 +  +* `Or` -> `||` 或 `or` + 逻辑或 +  +* `Not` -> `!` 或 `!` + 逻辑非 +  +* `Import` -> `import` + 导入关键字,用于导入包。 e.g + ``` python + import std.io + ``` +  +* `Function` -> `function` + 定义函数,匿名也可 + ``` javascript + function greeting() -> Null public + { + std.io.println("Hello, world!"); + } + + function intAdder() -> Function public + { + return function(n1: Int, n2: Int) => n1 + n2; + } + ``` + 此处的 `public` 为公开标识 + 不进行显示声明 `public` 默认为私有,即对象仅能在当前作用域访问 +  +* `Variable` -> `var` + 定义变量 + ``` dart + var foobar; + var defaultVal = 1145; + var numberSpecific: Int; + var numberDefault: Int = 91; + + foobar = "hello, world!"; + foobar = 13; + + defaultVal = "it can be any value"; + + numberSpecific = 78; + numberDefault = 0; + ``` +  +* `Const` -> `const` + 定义`全过程`常量: 从词法分析到求值器内的生命周期都为常量,仅能**在生命周期内**赋值一次,使用时也只有一个唯一对象 +   + 必须在源码中指定值 + + ``` dart + const Pi = 3.1415926; // recommended + + const name; // ❌ 错误 + ``` + + 定义后的常量,其值及类型均不可改变,故可省略类型标识。这是推荐的写法 + 同时,也可作为结构体成员的修饰 + ``` cpp + struct MathConstants + { + const Pi = 3.1415926; + }; + ``` +  +* `Final` -> `final` + 定义`结构体运行时`常量:从运行期开始的常量,仅能**在运行时**被赋值一次, **仅修饰结构体成员** + 不存在 **final** 类型的外部常量 +   + 定义后的常量,其值及类型均不可改变,故可省略类型标识。这是推荐的写法 + ``` cpp + struct Person + { + final name: String + final age: Int + + final sex: String = "gender" // ❌ 请使用 const 代替 + } + ``` +  +* `While` -> `while` + while循环,满足一个布尔类型条件循环执行语句 + + ``` cpp + while (ans != 27.19236) + { + ans = Int.parse(std.io.readline()); + } + ``` +  +* `For` -> `for` + for循环,拥有初始语句、条件、增长语句 + ``` cpp + for (init; condition; increment) + { + statements...; + } + ``` +  +* `Struct` -> `struct` + 结构体,面对对象 + + ``` cpp + struct Person + { + public final name: String; // public, final + public age: Int; // public + sex: String; // private normally; + + const ADULT_AGE = 18; // private, const + + fun printInfo() + { + std.io.println("name: {}, age: {}, sex: {}", name, age, sex); + } + }; + + var person = Person {"Fig", 1, "IDK"}; + // or + var person = Person {name: "Fig", age: 1, sex: "IDK"}; // can be unordered + + var name = "Fig"; + var age = 1; + var sex = "IDK"; + + var person = Person {name, age, sex}; + // = `var person = Person {name: name, age: age, sex: sex}` + ``` + diff --git a/include/Ast/AccessModifier.hpp b/include/Ast/AccessModifier.hpp new file mode 100644 index 0000000..7547332 --- /dev/null +++ b/include/Ast/AccessModifier.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace Fig +{ + enum class AccessModifier + { + Normal, + Const, + Final, + Public, + PublicConst, + PublicFinal, + }; +}; \ No newline at end of file diff --git a/include/Ast/BinaryExpr.hpp b/include/Ast/BinaryExpr.hpp new file mode 100644 index 0000000..e17675a --- /dev/null +++ b/include/Ast/BinaryExpr.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class BinaryExprAst final : public ExpressionAst + { + public: + Operator op; + Expression lexp, rexp; + + BinaryExprAst() + { + type = AstType::BinaryExpr; + } + BinaryExprAst(Expression _lexp, Operator _op, Expression _rexp) + { + type = AstType::BinaryExpr; + + lexp = _lexp; + op = _op; + rexp = _rexp; + } + }; + + using BinaryExpr = std::shared_ptr; + +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/ContainerInitExprs.hpp b/include/Ast/ContainerInitExprs.hpp new file mode 100644 index 0000000..3072434 --- /dev/null +++ b/include/Ast/ContainerInitExprs.hpp @@ -0,0 +1,65 @@ + +// Container Data Types --- Tuple/List/Map... + +#pragma once + +#include + +#include + +namespace Fig::Ast +{ + class ListExprAst final : public ExpressionAst + { + public: + std::vector val; + + ListExprAst() + { + type = AstType::ListExpr; + } + + ListExprAst(std::vector _val) : + val(std::move(_val)) + { + type = AstType::ListExpr; + } + }; + using ListExpr = std::shared_ptr; + + class TupleExprAst final : public ExpressionAst + { + public: + std::vector val; + + TupleExprAst() + { + type = AstType::TupleExpr; + } + + TupleExprAst(std::vector _val) : + val(std::move(_val)) + { + type = AstType::TupleExpr; + } + }; + using TupleExpr = std::shared_ptr; + + class MapExprAst final : public ExpressionAst + { + public: + std::map val; + + MapExprAst() + { + type = AstType::MapExpr; + } + + MapExprAst(std::map _val) : + val(std::move(_val)) + { + type = AstType::MapExpr; + } + }; + using MapExpr = std::shared_ptr; +}; // namespace Fig::Ast \ No newline at end of file diff --git a/include/Ast/ControlSt.hpp b/include/Ast/ControlSt.hpp new file mode 100644 index 0000000..f2def57 --- /dev/null +++ b/include/Ast/ControlSt.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class ReturnSt final : public StatementAst + { + public: + Expression retValue; + + ReturnSt() + { + type = AstType::ReturnSt; + } + ReturnSt(Expression _retValue) : + retValue(_retValue) + { + type = AstType::ReturnSt; + } + }; + + using Return = std::shared_ptr; + + class BreakSt final : public StatementAst + { + public: + BreakSt() + { + type = AstType::BreakSt; + } + }; + + using Break = std::shared_ptr; + + class ContinueSt final : public StatementAst + { + public: + ContinueSt() + { + type = AstType::ContinueSt; + } + }; + + using Continue = std::shared_ptr; +}; \ No newline at end of file diff --git a/include/Ast/ExpressionStmt.hpp b/include/Ast/ExpressionStmt.hpp new file mode 100644 index 0000000..13e6c4f --- /dev/null +++ b/include/Ast/ExpressionStmt.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class ExpressionStmtAst final : public StatementAst + { + public: + Expression exp; + ExpressionStmtAst() + { + type = AstType::ExpressionStmt; + } + ExpressionStmtAst(Expression _exp) : exp(std::move(_exp)) + { + type = AstType::ExpressionStmt; + } + }; + using ExpressionStmt = std::shared_ptr; +} diff --git a/include/Ast/FunctionCall.hpp b/include/Ast/FunctionCall.hpp new file mode 100644 index 0000000..2c0067f --- /dev/null +++ b/include/Ast/FunctionCall.hpp @@ -0,0 +1,54 @@ +// include/Ast/FunctionCall.hpp +#pragma once + +#include +#include + +namespace Fig::Ast +{ + struct FunctionArguments + { + std::vector argv; + size_t getLength() const { return argv.size(); } + }; + + struct FunctionCallArgs final + { + std::vector argv; + size_t getLength() const { return argv.size(); } + }; + + class FunctionCallExpr final : public ExpressionAst + { + public: + FString name; + FunctionArguments arg; + + FunctionCallExpr() + { + type = AstType::FunctionCall; + } + + FunctionCallExpr(FString _name, FunctionArguments _arg) : + name(std::move(_name)), arg(std::move(_arg)) + { + type = AstType::FunctionCall; + } + + virtual FString toString() override + { + FString s = name; + s += u8"("; + for (size_t i = 0; i < arg.argv.size(); ++i) + { + s += arg.argv[i]->toString(); + if (i + 1 < arg.argv.size()) + s += u8", "; + } + s += u8")"; + return s; + } + }; + + using FunctionCall = std::shared_ptr; +}; // namespace Fig diff --git a/include/Ast/FunctionDefSt.hpp b/include/Ast/FunctionDefSt.hpp new file mode 100644 index 0000000..9b1d6f6 --- /dev/null +++ b/include/Ast/FunctionDefSt.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Fig::Ast +{ + /* + fun greet(greeting, name:String, age:Int, split:String=":") public -> Null + { + io.println("{}, {}{}{}", greeting, name, split, age); + } + + `greeting`, `name`, `age` -> positional parameters + `split` -> default parameter + */ + + class FunctionDefSt final : public StatementAst // for define + { + public: + FString name; + FunctionParameters paras; + bool isPublic; + FString retType; + BlockStatement body; + FunctionDefSt() : + retType(ValueType::Null.name) + { + type = AstType::FunctionDefSt; + } + FunctionDefSt(FString _name, FunctionParameters _paras, bool _isPublic, FString _retType, BlockStatement _body) + { + type = AstType::FunctionDefSt; + + name = std::move(_name); + paras = std::move(_paras); + isPublic = _isPublic; + retType = std::move(_retType); + body = std::move(_body); + } + }; + using FunctionDef = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/IfSt.hpp b/include/Ast/IfSt.hpp new file mode 100644 index 0000000..7643abd --- /dev/null +++ b/include/Ast/IfSt.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class ElseSt final : public StatementAst + { + public: + BlockStatement body; + ElseSt() + { + type = AstType::ElseSt; + } + ElseSt(BlockStatement _body) : + body(_body) + { + type = AstType::ElseSt; + } + virtual FString toString() override + { + return FString(std::format("", aai.line, aai.column)); + } + }; + using Else = std::shared_ptr; + class ElseIfSt final : public StatementAst + { + public: + Expression condition; + BlockStatement body; + ElseIfSt() + { + type = AstType::ElseIfSt; + } + ElseIfSt(Expression _condition, + BlockStatement _body) : + condition(_condition), body(_body) + { + type = AstType::ElseIfSt; + } + virtual FString toString() override + { + return FString(std::format("", aai.line, aai.column)); + } + }; + using ElseIf = std::shared_ptr; + class IfSt final : public StatementAst + { + public: + Expression condition; + BlockStatement body; + std::vector elifs; + Else els; + IfSt() + { + type = AstType::IfSt; + } + IfSt(Expression _condition, + BlockStatement _body, + std::vector _elifs, + Else _els) : + condition(_condition), body(_body), elifs(_elifs), els(_els) + { + type = AstType::IfSt; + } + }; + using If = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/ImplementSt.hpp b/include/Ast/ImplementSt.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/Ast/InitExpr.hpp b/include/Ast/InitExpr.hpp new file mode 100644 index 0000000..f5cbf01 --- /dev/null +++ b/include/Ast/InitExpr.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class InitExprAst final : public ExpressionAst + { + public: + FString structName; + + std::vector> args; + + InitExprAst() + { + type = AstType::InitExpr; + } + + InitExprAst(FString _structName, std::vector> _args) : + structName(std::move(_structName)), args(std::move(_args)) + { + type = AstType::InitExpr; + } + }; + + using InitExpr = std::shared_ptr; + +}; // namespace Fig::Ast \ No newline at end of file diff --git a/include/Ast/LambdaExpr.hpp b/include/Ast/LambdaExpr.hpp new file mode 100644 index 0000000..3eaac5c --- /dev/null +++ b/include/Ast/LambdaExpr.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Fig::Ast +{ + class LambdaExprAst : public ExpressionAst + { + public: + /* + Lambda: + fun (greeting) -> Null {} + */ + + FunctionParameters paras; + FString retType; + BlockStatement body; + LambdaExprAst() : + retType(ValueType::Null.name) + { + type = AstType::LambdaExpr; + } + LambdaExprAst(FunctionParameters _paras, FString _retType, BlockStatement _body) : + retType(ValueType::Null.name) + { + paras = std::move(_paras); + retType = std::move(_retType); + body = std::move(_body); + } + + virtual FString typeName() override + { + return FString(std::format("LambdaExprAst<{}>", retType.toBasicString())); + } + virtual ~LambdaExprAst() = default; + }; + + using LambdaExpr = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/StructDefSt.hpp b/include/Ast/StructDefSt.hpp new file mode 100644 index 0000000..48761c5 --- /dev/null +++ b/include/Ast/StructDefSt.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Fig::Ast +{ + + struct StructDefField + { + AccessModifier am; + FString fieldName; + FString tiName; + Expression defaultValueExpr; + + StructDefField() {} + StructDefField(AccessModifier _am, FString _fieldName, FString _tiName, Expression _defaultValueExpr) : + am(std::move(_am)), fieldName(std::move(_fieldName)), tiName(std::move(_tiName)), defaultValueExpr(std::move(_defaultValueExpr)) + { + } + }; + class StructDefSt final : public StatementAst + { + public: + bool isPublic; + const FString name; + const std::vector fields; // field name (:type name = default value expression) + // name / name: String / name: String = "Fig" + const BlockStatement body; + StructDefSt() + { + type = AstType::StructSt; + } + StructDefSt(bool _isPublic, FString _name, std::vector _fields, BlockStatement _body) : + isPublic(std::move(_isPublic)), name(std::move(_name)), fields(std::move(_fields)), body(std::move(_body)) + { + type = AstType::StructSt; + } + }; + + using StructDef = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/TernaryExpr.hpp b/include/Ast/TernaryExpr.hpp new file mode 100644 index 0000000..23e04d9 --- /dev/null +++ b/include/Ast/TernaryExpr.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + // condition ? val_true : val_false + class TernaryExprAst final : public ExpressionAst + { + public: + Expression condition; + Expression valueT; + Expression valueF; + + TernaryExprAst() + { + type = AstType::TernaryExpr; + } + TernaryExprAst(Expression _condition, Expression _valueT, Expression _valueF) + { + type = AstType::TernaryExpr; + + condition = std::move(_condition); + valueT = std::move(_valueT); + valueF = std::move(_valueF); + } + }; + using TernaryExpr = std::shared_ptr; +} // namespace Fig \ No newline at end of file diff --git a/include/Ast/UnaryExpr.hpp b/include/Ast/UnaryExpr.hpp new file mode 100644 index 0000000..987fcf7 --- /dev/null +++ b/include/Ast/UnaryExpr.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class UnaryExprAst final : public ExpressionAst + { + public: + Operator op; + Expression exp; + + UnaryExprAst() + { + type = AstType::UnaryExpr; + } + UnaryExprAst(Operator _op, Expression _exp) + { + type = AstType::UnaryExpr; + + op = _op; + exp = std::move(_exp); + } + }; + + using UnaryExpr = std::shared_ptr; +} // namespace Fig \ No newline at end of file diff --git a/include/Ast/ValueExpr.hpp b/include/Ast/ValueExpr.hpp new file mode 100644 index 0000000..0866a18 --- /dev/null +++ b/include/Ast/ValueExpr.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +namespace Fig::Ast +{ + class ValueExprAst final : public ExpressionAst + { + public: + Value val; + + ValueExprAst() + { + type = AstType::ValueExpr; + } + ValueExprAst(Value _val) + { + type = AstType::ValueExpr; + val = std::move(_val); + } + }; + + using ValueExpr = std::shared_ptr; +}; \ No newline at end of file diff --git a/include/Ast/VarAssignSt.hpp b/include/Ast/VarAssignSt.hpp new file mode 100644 index 0000000..861c5d8 --- /dev/null +++ b/include/Ast/VarAssignSt.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class VarAssignSt final : public StatementAst + { + public: + const FString varName; + const Expression valueExpr; + + VarAssignSt() + { + type = AstType::VarAssignSt; + } + + VarAssignSt(FString _varName, Expression _valueExpr) : + varName(std::move(_varName)), valueExpr(std::move(_valueExpr)) + { + type = AstType::VarAssignSt; + } + }; + + using VarAssign = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/VarDef.hpp b/include/Ast/VarDef.hpp new file mode 100644 index 0000000..a5ad13e --- /dev/null +++ b/include/Ast/VarDef.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace Fig::Ast +{ + class VarDefAst final : public StatementAst + { + public: + bool isPublic; + bool isConst; + FString name; + FString typeName; + Expression expr; + + VarDefAst() : + typeName(ValueType::Any.name) + { + type = AstType::VarDefSt; + } + VarDefAst(bool _isPublic, bool _isConst, FString _name, FString _info, Expression _expr) : + typeName(std::move(_info)) + { + type = AstType::VarDefSt; + isPublic = _isPublic; + isConst = _isConst; + name = std::move(_name); + expr = std::move(_expr); + } + }; + + using VarDef = std::shared_ptr; +} // namespace Fig \ No newline at end of file diff --git a/include/Ast/VarExpr.hpp b/include/Ast/VarExpr.hpp new file mode 100644 index 0000000..ce7fb9b --- /dev/null +++ b/include/Ast/VarExpr.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class VarExprAst final : public ExpressionAst + { + public: + const FString name; + VarExprAst() : + name(u8"") + { + type = AstType::VarExpr; + } + VarExprAst(FString _name) : + name(std::move(_name)) + { + type = AstType::VarExpr; + } + }; + + using VarExpr = std::shared_ptr; +}; // namespace Fig \ No newline at end of file diff --git a/include/Ast/WhileSt.hpp b/include/Ast/WhileSt.hpp new file mode 100644 index 0000000..491f697 --- /dev/null +++ b/include/Ast/WhileSt.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Fig::Ast +{ + class WhileSt final : public StatementAst + { + public: + Expression condition; + BlockStatement body; + + WhileSt() + { + type = AstType::WhileSt; + } + + WhileSt(Expression _condition, BlockStatement _body) + : condition(_condition), body(_body) + { + type = AstType::WhileSt; + } + }; + + using While = std::shared_ptr; +}; \ No newline at end of file diff --git a/include/Ast/astBase.hpp b/include/Ast/astBase.hpp new file mode 100644 index 0000000..2dcc335 --- /dev/null +++ b/include/Ast/astBase.hpp @@ -0,0 +1,338 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace Fig::Ast +{ + enum class AstType : uint8_t + { + /* Base Class */ + _AstBase, + StatementBase, + ExpressionBase, + /* Expression */ + ValueExpr, + VarExpr, + FunctionCall, + LambdaExpr, + UnaryExpr, + BinaryExpr, + TernaryExpr, + + ListExpr, // [] + TupleExpr, // () + MapExpr, // {} + InitExpr, // struct{} + + /* Statement */ + BlockStatement, + ExpressionStmt, + + VarDefSt, + FunctionDefSt, + StructSt, + ImplementSt, + + IfSt, + ElseSt, + ElseIfSt, + + VarAssignSt, + WhileSt, + ReturnSt, + BreakSt, + ContinueSt, + }; + + static const std::unordered_map astTypeToString{ + /* Base Class */ + {AstType::_AstBase, FString(u8"Ast")}, + {AstType::StatementBase, FString(u8"Statement")}, + {AstType::ExpressionBase, FString(u8"Expression")}, + /* Expression */ + {AstType::ValueExpr, FString(u8"ValueExpr")}, + {AstType::LambdaExpr, FString(u8"LambdaExpr")}, + {AstType::UnaryExpr, FString(u8"UnaryExpr")}, + {AstType::BinaryExpr, FString(u8"BinaryExpr")}, + {AstType::TernaryExpr, FString(u8"TernaryExpr")}, + + {AstType::InitExpr, FString(u8"InitExpr")}, + + /* Statement */ + {AstType::BlockStatement, FString(u8"BlockStatement")}, + + {AstType::VarDefSt, FString(u8"VarSt")}, + {AstType::FunctionDefSt, FString(u8"FunctionDefSt")}, + {AstType::StructSt, FString(u8"StructSt")}, + {AstType::ImplementSt, FString(u8"ImplementSt")}, + + {AstType::IfSt, FString(u8"IfSt")}, + {AstType::ElseSt, FString(u8"ElseSt")}, + {AstType::ElseIfSt, FString(u8"ElseIfSt")}, + {AstType::VarAssignSt, FString(u8"VarAssignSt")}, + {AstType::WhileSt, FString(u8"WhileSt")}, + {AstType::ReturnSt, FString(u8"ReturnSt")}, + {AstType::BreakSt, FString(u8"BreakSt")}, + {AstType::ContinueSt, FString(u8"ContinueSt")}, + }; + + struct AstAddressInfo + { + size_t line, column; + }; + + class _AstBase + { + protected: + AstType type; + AstAddressInfo aai; + + public: + _AstBase(const _AstBase &) = default; + _AstBase(_AstBase &&) = default; + + _AstBase &operator=(const _AstBase &) = default; + _AstBase &operator=(_AstBase &&) = default; + + _AstBase() {} + + void setAAI(AstAddressInfo _aai) + { + aai = std::move(_aai); + } + + virtual FString typeName() + { + return astTypeToString.at(type); + } + virtual FString toString() + { + return FString(std::format("", typeName().toBasicString(), aai.line, aai.column)); + } + + AstAddressInfo getAAI() + { + return aai; + } + + AstType getType() + { + return type; + } + }; + + class StatementAst : public _AstBase + { + public: + using _AstBase::_AstBase; + using _AstBase::operator=; + StatementAst() + { + type = AstType::StatementBase; + } + + virtual FString toString() override + { + return FString(std::format("", typeName().toBasicString(), aai.line, aai.column)); + } + }; + + class EofStmt final : public StatementAst + { + public: + EofStmt() + { + type = AstType::StatementBase; + } + + virtual FString toString() override + { + return FString(std::format("", aai.line, aai.column)); + } + }; + + class ExpressionAst : public _AstBase + { + public: + using _AstBase::_AstBase; + using _AstBase::operator=; + ExpressionAst() + { + type = AstType::ExpressionBase; + } + + virtual FString toString() override + { + return FString(std::format("", typeName().toBasicString(), aai.line, aai.column)); + } + }; + enum class Operator : uint8_t + { + LeftParen, + RightParen, + + // 算术 + Add, // + + Subtract, // - + Multiply, // * + Divide, // / + Modulo, // % + Power, // ** + + // 逻辑 + And, // and / && + Or, // or / || + Not, // not / ! + + // 比较 + Equal, // == + NotEqual, // != + Less, // < + LessEqual, // <= + Greater, // > + GreaterEqual, // >= + + // 三目 + TernaryCond, + + // 位运算 + BitAnd, // & + BitOr, // | + BitXor, // ^ + BitNot, // ~ + ShiftLeft, // << + ShiftRight, // >> + + // 赋值表达式 + Walrus, // := + + // 点运算符 . + Dot, + }; + + static const std::unordered_set unaryOps{ + Operator::Not, + Operator::Subtract, + Operator::BitNot, + }; + static const std::unordered_set binaryOps{ + Operator::Add, + Operator::Subtract, + Operator::Multiply, + Operator::Divide, + Operator::Modulo, + Operator::Power, + Operator::And, + Operator::Or, + + Operator::Equal, + Operator::NotEqual, + Operator::Less, + Operator::LessEqual, + Operator::Greater, + Operator::GreaterEqual, + + Operator::BitAnd, + Operator::BitOr, + Operator::BitXor, + Operator::BitNot, + Operator::ShiftLeft, + Operator::ShiftRight, + + Operator::Walrus, + Operator::Dot}; + static const std::unordered_set ternaryOps{Operator::TernaryCond}; + + static const std::unordered_map TokenToOp{ + // 算术 + {TokenType::Plus, Operator::Add}, + {TokenType::Minus, Operator::Subtract}, + {TokenType::Asterisk, Operator::Multiply}, + {TokenType::Slash, Operator::Divide}, + {TokenType::Percent, Operator::Modulo}, + {TokenType::Power, Operator::Power}, + + // 逻辑 + {TokenType::And, Operator::And}, + {TokenType::DoubleAmpersand, Operator::And}, + {TokenType::Or, Operator::Or}, + {TokenType::DoublePipe, Operator::Or}, + {TokenType::Not, Operator::Not}, + + // 比较 + {TokenType::Equal, Operator::Equal}, + {TokenType::NotEqual, Operator::NotEqual}, + {TokenType::Less, Operator::Less}, + {TokenType::LessEqual, Operator::LessEqual}, + {TokenType::Greater, Operator::Greater}, + {TokenType::GreaterEqual, Operator::GreaterEqual}, + + // 三目 + {TokenType::Question, Operator::TernaryCond}, + + // 位运算 + {TokenType::Ampersand, Operator::BitAnd}, + {TokenType::Pipe, Operator::BitOr}, + {TokenType::Caret, Operator::BitXor}, + {TokenType::Tilde, Operator::BitNot}, + {TokenType::ShiftLeft, Operator::ShiftLeft}, + {TokenType::ShiftRight, Operator::ShiftRight}, + + // 赋值表达式 + {TokenType::Walrus, Operator::Walrus}, + // 点运算符 + {TokenType::Dot, Operator::Dot}, + }; // := + + inline bool isOpUnary(Operator op) + { + return unaryOps.contains(op); + } + inline bool isOpBinary(Operator op) + { + return binaryOps.contains(op); + } + inline bool isOpTernary(Operator op) + { + return ternaryOps.contains(op); + } + + using AstBase = std::shared_ptr<_AstBase>; + using Statement = std::shared_ptr; + using Expression = std::shared_ptr; + using Eof = std::shared_ptr; + + class BlockStatementAst : public StatementAst + { + public: + const std::vector stmts; + BlockStatementAst() + { + type = AstType::BlockStatement; + } + BlockStatementAst(std::vector _stmts) : + stmts(std::move(_stmts)) + { + type = AstType::BlockStatement; + } + virtual FString typeName() override + { + return FString(u8"BlockStatement"); + } + virtual FString toString() override + { + return FString(std::format("", typeName().toBasicString(), aai.line, aai.column)); + } + virtual ~BlockStatementAst() = default; + }; + + using BlockStatement = std::shared_ptr; + // static BlockStatement builtinEmptyBlockSt(new BlockStatementAst()); +}; // namespace Fig::Ast \ No newline at end of file diff --git a/include/Ast/functionParameters.hpp b/include/Ast/functionParameters.hpp new file mode 100644 index 0000000..376bf46 --- /dev/null +++ b/include/Ast/functionParameters.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace Fig::Ast +{ + struct FunctionParameters // for define + { + /* + Positional Parameters: + fun test(pp1, pp2: Int) + Default Parameters: + fun test2(dp1 = 10, dp2:String = "default parameter 2") + */ + + using PosParasType = std::vector>; + using DefParasType = std::vector>>; + + PosParasType posParas; + DefParasType defParas; // default parameters + + FunctionParameters() + { + + } + FunctionParameters(PosParasType _posParas, DefParasType _defParas) + { + posParas = std::move(_posParas); + defParas = std::move(_defParas); + } + + size_t size() const + { + return posParas.size() + defParas.size(); + } + }; +} \ No newline at end of file diff --git a/include/AstPrinter.hpp b/include/AstPrinter.hpp new file mode 100644 index 0000000..9d69245 --- /dev/null +++ b/include/AstPrinter.hpp @@ -0,0 +1,200 @@ +#pragma once +#include +#include +#include +#include +#include + +using namespace Fig; +using namespace Fig::Ast; +class AstPrinter +{ +public: + void print(const AstBase &node, int indent = 0) + { + if (!node) return; + switch (node->getType()) + { + case AstType::BinaryExpr: + printBinaryExpr(std::static_pointer_cast(node), indent); + break; + case AstType::UnaryExpr: + printUnaryExpr(std::static_pointer_cast(node), indent); + break; + case AstType::ValueExpr: + printValueExpr(std::static_pointer_cast(node), indent); + break; + case AstType::VarDefSt: + printVarDef(std::static_pointer_cast(node), indent); + break; + case AstType::VarExpr: + printVarExpr(std::static_pointer_cast(node), indent); + break; + case AstType::BlockStatement: + printBlockStatement(std::static_pointer_cast(node), indent); + break; + case AstType::FunctionCall: + printFunctionCall(std::static_pointer_cast(node), indent); + break; + case AstType::FunctionDefSt: + printFunctionSt(std::static_pointer_cast(node), indent); + break; + case AstType::IfSt: + printIfSt(std::static_pointer_cast(node), indent); + break; + case AstType::LambdaExpr: + printLambdaExpr(std::static_pointer_cast(node), indent); + break; + case AstType::TernaryExpr: + printTernaryExpr(std::static_pointer_cast(node), indent); + break; + default: + printIndent(indent); + std::cout << "Unknown AST Node\n"; + } + } + +private: + void printIndent(int indent) + { + std::cout << std::string(indent, ' '); + } + + void printFString(const Fig::FString &fstr, int indent) + { + printIndent(indent); + std::cout << "FString: \""; + std::cout.write(reinterpret_cast(fstr.data()), fstr.size()); + std::cout << "\"\n"; + } + + template + void printEnum(const EnumT &value, int indent) + { + printIndent(indent); + std::cout << "Enum: " << magic_enum::enum_name(value) << "\n"; + } + + void printBinaryExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "BinaryExpr\n"; + printEnum(node->op, indent + 2); + printIndent(indent + 2); + std::cout << "Left:\n"; + print(node->lexp, indent + 4); + printIndent(indent + 2); + std::cout << "Right:\n"; + print(node->rexp, indent + 4); + } + + void printUnaryExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "UnaryExpr\n"; + printEnum(node->op, indent + 2); + printIndent(indent + 2); + std::cout << "Expr:\n"; + print(node->exp, indent + 4); + } + + void printValueExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "ValueExpr\n"; + printFString(node->val.toString(), indent + 2); + } + + void printVarDef(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "VarDef\n"; + printIndent(indent + 2); + std::cout << "Name: "; + printFString(node->name, 0); + printIndent(indent + 2); + std::cout << "Type: "; + printFString(node->typeName, 0); + if (node->expr) + { + printIndent(indent + 2); + std::cout << "InitExpr:\n"; + print(node->expr, indent + 4); + } + } + + void printVarExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "VarExpr\n"; + printIndent(indent + 2); + std::cout << "Name: "; + printFString(node->name, 0); + } + + void printBlockStatement(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "BlockStatement\n"; + for (const auto &stmt : node->stmts) + { + print(stmt, indent + 2); + } + } + + void printFunctionCall(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "FunctionCall\n"; + printIndent(indent + 2); + std::cout << "FuncName: "; + printFString(node->name, 0); + printIndent(indent + 2); + } + + void printFunctionSt(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "FunctionSt\n"; + printIndent(indent + 2); + std::cout << "Name: "; + printFString(node->name, 0); + printIndent(indent + 2); + printIndent(indent + 2); + std::cout << "Body:\n"; + print(node->body, indent + 4); + } + + void printIfSt(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "IfSt\n"; + printIndent(indent + 2); + std::cout << "Condition:\n"; + print(node->condition, indent + 4); + printIndent(indent + 2); + } + + void printLambdaExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "LambdaExpr\n" + << node->toString().toBasicString(); + printIndent(indent + 2); + } + + void printTernaryExpr(const std::shared_ptr &node, int indent) + { + printIndent(indent); + std::cout << "TernaryExpr\n"; + printIndent(indent + 2); + std::cout << "Condition:\n"; + print(node->condition, indent + 4); + printIndent(indent + 2); + std::cout << "TrueExpr:\n"; + print(node->valueT, indent + 4); + printIndent(indent + 2); + std::cout << "FalseExpr:\n"; + print(node->valueF, indent + 4); + } +}; \ No newline at end of file diff --git a/include/Value/BaseValue.hpp b/include/Value/BaseValue.hpp new file mode 100644 index 0000000..a033a2d --- /dev/null +++ b/include/Value/BaseValue.hpp @@ -0,0 +1,190 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Fig +{ + + template + class __ValueWrapper + { + public: + const TypeInfo ti; + std::unique_ptr data; + + __ValueWrapper(const __ValueWrapper &other) : + ti(other.ti) + { + if (other.data) + data = std::make_unique(*other.data); + } + + __ValueWrapper(__ValueWrapper &&other) noexcept + : + ti(other.ti), data(std::move(other.data)) {} + + __ValueWrapper &operator=(const __ValueWrapper &other) + { + if (this != &other) + { + if (other.data) + data = std::make_unique(*other.data); + else + data.reset(); + } + return *this; + } + + __ValueWrapper &operator=(__ValueWrapper &&other) noexcept + { + if (this != &other) + { + data = std::move(other.data); + } + return *this; + } + + const T &getValue() const + { + if (!data) throw std::runtime_error("Accessing null Value data"); + return *data; + } + + virtual size_t getSize() const + { + return sizeof(T); + } + + virtual bool isNull() const + { + return !data; + } + + virtual FString toString() const + { + if (!data) + return FString(std::format("<{} object (null)>", ti.name.toBasicString())); + + return FString(std::format( + "<{} object @{:p}>", + ti.name.toBasicString(), + static_cast(data.get()))); + } + + __ValueWrapper(const TypeInfo &_ti) : + ti(_ti) {} + + __ValueWrapper(const T &x, const TypeInfo &_ti) : + ti(_ti) + { + data = std::make_unique(x); + } + }; + + class Int final : public __ValueWrapper + { + public: + Int(const ValueType::IntClass &x) : + __ValueWrapper(x, ValueType::Int) {} + + Int(const Int &) = default; + Int(Int &&) noexcept = default; + + Int &operator=(const Int &) = default; + Int &operator=(Int &&) noexcept = default; + + bool operator==(const Int &other) const noexcept + { + return getValue() == other.getValue(); + } + bool operator!=(const Int &other) const noexcept + { + return !(*this == other); + } + }; + + class Double final : public __ValueWrapper + { + public: + Double(const ValueType::DoubleClass &x) : + __ValueWrapper(x, ValueType::Double) {} + Double(const Double &) = default; + Double(Double &&) noexcept = default; + + Double &operator=(const Double &) = default; + Double &operator=(Double &&) noexcept = default; + + bool operator==(const Double &other) const noexcept + { + return getValue() == other.getValue(); + } + bool operator!=(const Double &other) const noexcept + { + return !(*this == other); + } + }; + + class Null final : public __ValueWrapper + { + public: + Null() : + __ValueWrapper(ValueType::NullClass{}, ValueType::Null) {} + + Null(const Null &) = default; + Null(Null &&) noexcept = default; + + Null &operator=(const Null &) = default; + Null &operator=(Null &&) noexcept = default; + + bool isNull() const override { return true; } + bool operator==(const Null &) const noexcept { return true; } + bool operator!=(const Null &) const noexcept { return false; } + }; + + class String final : public __ValueWrapper + { + public: + String(const ValueType::StringClass &x) : + __ValueWrapper(x, ValueType::String) {} + String(const String &) = default; + String(String &&) noexcept = default; + + String &operator=(const String &) = default; + String &operator=(String &&) noexcept = default; + + bool operator==(const String &other) const noexcept + { + return getValue() == other.getValue(); + } + bool operator!=(const String &other) const noexcept + { + return !(*this == other); + } + }; + + class Bool final : public __ValueWrapper + { + public: + Bool(const ValueType::BoolClass &x) : + __ValueWrapper(x, ValueType::Bool) {} + + Bool(const Bool &) = default; + Bool(Bool &&) noexcept = default; + + Bool &operator=(const Bool &) = default; + Bool &operator=(Bool &&) noexcept = default; + + bool operator==(const Bool &other) const noexcept + { + return getValue() == other.getValue(); + } + bool operator!=(const Bool &other) const noexcept + { + return !(*this == other); + } + }; +} // namespace Fig diff --git a/include/Value/Type.hpp b/include/Value/Type.hpp new file mode 100644 index 0000000..8b83c66 --- /dev/null +++ b/include/Value/Type.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Fig +{ + + class TypeInfo final + { + private: + size_t id; + + public: + FString name; + + FString toString() const + { + return name; + } + + static std::map typeMap; + + static size_t getID(FString _name) + { + return typeMap.at(_name); + } + size_t getInstanceID(FString _name) const + { + return id; + } + + TypeInfo(); + TypeInfo(FString _name, bool reg = false); + TypeInfo(const TypeInfo &other) = default; + + bool operator==(const TypeInfo &other) const + { + return id == other.id; + } + }; + + // class Value; + namespace ValueType + { + extern const TypeInfo Any; + extern const TypeInfo Null; + extern const TypeInfo Int; + extern const TypeInfo String; + extern const TypeInfo Bool; + extern const TypeInfo Double; + extern const TypeInfo Function; + extern const TypeInfo StructType; + extern const TypeInfo StructInstance; + extern const TypeInfo List; + extern const TypeInfo Map; + extern const TypeInfo Tuple; + + using IntClass = int64_t; + using DoubleClass = double; + using BoolClass = bool; + using NullClass = std::monostate; + using StringClass = FString; + + /* complex types */ + struct FunctionStruct; + using FunctionClass = FunctionStruct; + + struct StructT; + using StructTypeClass = StructT; + + struct StructInstanceT; + using StructInstanceClass = StructInstanceT; + }; // namespace ValueType +}; // namespace Fig \ No newline at end of file diff --git a/include/Value/function.hpp b/include/Value/function.hpp new file mode 100644 index 0000000..fd34632 --- /dev/null +++ b/include/Value/function.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#include +namespace Fig +{ + /* complex objects */ + struct FunctionStruct + { + std::size_t id; + Ast::FunctionParameters paras; + TypeInfo retType; + Ast::BlockStatement body; + + FunctionStruct(Ast::FunctionParameters _paras, TypeInfo _retType, Ast::BlockStatement _body) : + id(nextId()), // 分配唯一 ID + paras(std::move(_paras)), + retType(std::move(_retType)), + body(std::move(_body)) + { + } + + FunctionStruct(const FunctionStruct &other) : + id(other.id), paras(other.paras), retType(other.retType), body(other.body) {} + + FunctionStruct &operator=(const FunctionStruct &other) = default; + FunctionStruct(FunctionStruct &&) noexcept = default; + FunctionStruct &operator=(FunctionStruct &&) noexcept = default; + + bool operator==(const FunctionStruct &other) const noexcept + { + return id == other.id; + } + bool operator!=(const FunctionStruct &other) const noexcept + { + return !(*this == other); + } + + private: + static std::size_t nextId() + { + static std::atomic counter{1}; + return counter++; + } + }; + + class Function final : public __ValueWrapper + { + public: + Function(const FunctionStruct &x) : + __ValueWrapper(ValueType::Function) + { + data = std::make_unique(x); + } + Function(Ast::FunctionParameters paras, TypeInfo ret, Ast::BlockStatement body) : + __ValueWrapper(ValueType::Function) + { + data = std::make_unique( + std::move(paras), std::move(ret), std::move(body)); + } + bool operator==(const Function &other) const noexcept + { + if (!data || !other.data) return false; + return *data == *other.data; // call -> FunctionStruct::operator== (based on ID comparing) + } + Function(const Function &) = default; + Function(Function &&) noexcept = default; + Function &operator=(const Function &) = default; + Function &operator=(Function &&) noexcept = default; + }; +} // namespace Fig diff --git a/include/Value/structInstance.hpp b/include/Value/structInstance.hpp new file mode 100644 index 0000000..4b5f0ac --- /dev/null +++ b/include/Value/structInstance.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include + +namespace Fig +{ + struct StructInstanceT final + { + FString structName; // 类的名字 (StructType), 非变量名。用于获取所属类 + ContextPtr localContext; + + StructInstanceT(FString _structName, ContextPtr _localContext) : + structName(std::move(_structName)), localContext(std::move(_localContext)) {} + + StructInstanceT(const StructInstanceT &other) : + structName(other.structName), localContext(other.localContext) {} + StructInstanceT &operator=(const StructInstanceT &) = default; + StructInstanceT(StructInstanceT &&) = default; + StructInstanceT &operator=(StructInstanceT &&) = default; + + bool operator==(const StructInstanceT &) const = default; + }; + + class StructInstance final : public __ValueWrapper + { + public: + StructInstance(const StructInstanceT &x) : + __ValueWrapper(ValueType::StructInstance) + { + data = std::make_unique(x); + } + StructInstance(FString _structName, ContextPtr _localContext) : + __ValueWrapper(ValueType::StructInstance) + { + data = std::make_unique(std::move(_structName), std::move(_localContext)); + } + + bool operator==(const StructInstance &other) const noexcept + { + return data == other.data; + } + StructInstance(const StructInstance &) = default; + StructInstance(StructInstance &&) = default; + StructInstance &operator=(const StructInstance &) = default; + StructInstance &operator=(StructInstance &&) = default; + }; + +}; // namespace Fig \ No newline at end of file diff --git a/include/Value/structType.hpp b/include/Value/structType.hpp new file mode 100644 index 0000000..5d961a8 --- /dev/null +++ b/include/Value/structType.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include + +namespace Fig +{ + struct Field + { + bool isPublic() const + { + return am == AccessModifier::Public or am == AccessModifier::PublicConst or am == AccessModifier::PublicFinal; + } + bool isConst() const + { + return am == AccessModifier::Const or am == AccessModifier::PublicConst; + } + bool isFinal() const + { + return am == AccessModifier::Final or am == AccessModifier::PublicFinal; + } + + AccessModifier am; + FString name; + TypeInfo type; + Ast::Expression defaultValue; + + Field(AccessModifier _am, FString _name, TypeInfo _type, Ast::Expression _defaultValue) : + am(std::move(_am)), name(std::move(_name)), type(std::move(_type)), defaultValue(std::move(_defaultValue)) {} + }; + struct StructT final// = StructType 结构体定义 + { + std::size_t id; + ContextPtr defContext; // 定义时的上下文 + std::vector fields; + StructT(ContextPtr _defContext, std::vector fieldsMap) : + defContext(std::move(_defContext)), + fields(std::move(fieldsMap)) + { + id = nextId(); + } + StructT(const StructT &other) : + id(other.id), fields(other.fields) {} + StructT &operator=(const StructT &other) = default; + StructT(StructT &&) noexcept = default; + StructT &operator=(StructT &&) noexcept = default; + + bool operator==(const StructT &other) const noexcept + { + return id == other.id; + } + bool operator!=(const StructT &other) const noexcept + { + return !(*this == other); + } + + private: + static std::size_t nextId() + { + static std::atomic counter{1}; + return counter++; + } + }; + + class StructType final : public __ValueWrapper + { + public: + StructType(const StructT &x) : + __ValueWrapper(ValueType::StructType) + { + data = std::make_unique(x); + } + StructType(ContextPtr _defContext, std::vector fieldsMap) : + __ValueWrapper(ValueType::StructType) + { + data = std::make_unique(std::move(_defContext), std::move(fieldsMap)); + } + bool operator==(const StructType &other) const noexcept + { + return data == other.data; + } + StructType(const StructType &) = default; + StructType(StructType &&) noexcept = default; + StructType &operator=(const StructType &) = default; + StructType &operator=(StructType &&) noexcept = default; + }; + +}; // namespace Fig \ No newline at end of file diff --git a/include/Value/valueError.hpp b/include/Value/valueError.hpp new file mode 100644 index 0000000..6851a3c --- /dev/null +++ b/include/Value/valueError.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Fig +{ + class ValueError : public UnaddressableError + { + public: + using UnaddressableError::UnaddressableError; + virtual FString toString() const override + { + std::string msg = std::format("[ValueError] {} in [{}] {}", std::string(this->message.begin(), this->message.end()), this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + }; +}; \ No newline at end of file diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp new file mode 100644 index 0000000..06d30fd --- /dev/null +++ b/include/argparse/argparse.hpp @@ -0,0 +1,2589 @@ +/* + __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ + / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ +| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse + \__,_|_| \__, | .__/ \__,_|_| |___/\___| + |___/|_| + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2019-2022 Pranav Srinivas Kumar +and other contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#pragma once + +#include + +#ifndef ARGPARSE_MODULE_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOF +#define ARGPARSE_CUSTOM_STRTOF strtof +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOD +#define ARGPARSE_CUSTOM_STRTOD strtod +#endif + +#ifndef ARGPARSE_CUSTOM_STRTOLD +#define ARGPARSE_CUSTOM_STRTOLD strtold +#endif + +namespace argparse { + +namespace details { // namespace for helper methods + +template +struct HasContainerTraits : std::false_type {}; + +template <> struct HasContainerTraits : std::false_type {}; + +template <> struct HasContainerTraits : std::false_type {}; + +template +struct HasContainerTraits< + T, std::void_t().begin()), + decltype(std::declval().end()), + decltype(std::declval().size())>> : std::true_type {}; + +template +inline constexpr bool IsContainer = HasContainerTraits::value; + +template +struct HasStreamableTraits : std::false_type {}; + +template +struct HasStreamableTraits< + T, + std::void_t() << std::declval())>> + : std::true_type {}; + +template +inline constexpr bool IsStreamable = HasStreamableTraits::value; + +constexpr std::size_t repr_max_container_size = 5; + +template std::string repr(T const &val) { + if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else if constexpr (std::is_convertible_v) { + return '"' + std::string{std::string_view{val}} + '"'; + } else if constexpr (IsContainer) { + std::stringstream out; + out << "{"; + const auto size = val.size(); + if (size > 1) { + out << repr(*val.begin()); + std::for_each( + std::next(val.begin()), + std::next( + val.begin(), + static_cast( + std::min(size, repr_max_container_size) - 1)), + [&out](const auto &v) { out << " " << repr(v); }); + if (size <= repr_max_container_size) { + out << " "; + } else { + out << "..."; + } + } + if (size > 0) { + out << repr(*std::prev(val.end())); + } + out << "}"; + return out.str(); + } else if constexpr (IsStreamable) { + std::stringstream out; + out << val; + return out.str(); + } else { + return ""; + } +} + +namespace { + +template constexpr bool standard_signed_integer = false; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; +template <> constexpr bool standard_signed_integer = true; + +template constexpr bool standard_unsigned_integer = false; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> constexpr bool standard_unsigned_integer = true; +template <> +constexpr bool standard_unsigned_integer = true; + +} // namespace + +constexpr int radix_2 = 2; +constexpr int radix_8 = 8; +constexpr int radix_10 = 10; +constexpr int radix_16 = 16; + +template +constexpr bool standard_integer = + standard_signed_integer || standard_unsigned_integer; + +template +constexpr decltype(auto) +apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, + std::index_sequence /*unused*/) { + return std::invoke(std::forward(f), std::get(std::forward(t))..., + std::forward(x)); +} + +template +constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { + return details::apply_plus_one_impl( + std::forward(f), std::forward(t), std::forward(x), + std::make_index_sequence< + std::tuple_size_v>>{}); +} + +constexpr auto pointer_range(std::string_view s) noexcept { + return std::tuple(s.data(), s.data() + s.size()); +} + +template +constexpr bool starts_with(std::basic_string_view prefix, + std::basic_string_view s) noexcept { + return s.substr(0, prefix.size()) == prefix; +} + +enum class chars_format { + scientific = 0xf1, + fixed = 0xf2, + hex = 0xf4, + binary = 0xf8, + general = fixed | scientific +}; + +struct ConsumeBinaryPrefixResult { + bool is_binary; + std::string_view rest; +}; + +constexpr auto consume_binary_prefix(std::string_view s) + -> ConsumeBinaryPrefixResult { + if (starts_with(std::string_view{"0b"}, s) || + starts_with(std::string_view{"0B"}, s)) { + s.remove_prefix(2); + return {true, s}; + } + return {false, s}; +} + +struct ConsumeHexPrefixResult { + bool is_hexadecimal; + std::string_view rest; +}; + +using namespace std::literals; + +constexpr auto consume_hex_prefix(std::string_view s) + -> ConsumeHexPrefixResult { + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { + s.remove_prefix(2); + return {true, s}; + } + return {false, s}; +} + +template +inline auto do_from_chars(std::string_view s) -> T { + T x{0}; + auto [first, last] = pointer_range(s); + auto [ptr, ec] = std::from_chars(first, last, x, Param); + if (ec == std::errc()) { + if (ptr == last) { + return x; + } + throw std::invalid_argument{"pattern '" + std::string(s) + + "' does not match to the end"}; + } + if (ec == std::errc::invalid_argument) { + throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"}; + } + if (ec == std::errc::result_out_of_range) { + throw std::range_error{"'" + std::string(s) + "' not representable"}; + } + return x; // unreachable +} + +template struct parse_number { + auto operator()(std::string_view s) -> T { + return do_from_chars(s); + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + if (auto [ok, rest] = consume_binary_prefix(s); ok) { + return do_from_chars(rest); + } + throw std::invalid_argument{"pattern not found"}; + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { + if (auto [ok, rest] = consume_hex_prefix(s); ok) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + } else { + // Allow passing hex numbers without prefix + // Shape 'x' already has to be specified + try { + return do_from_chars(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + + throw std::invalid_argument{"pattern '" + std::string(s) + + "' not identified as hexadecimal"}; + } +}; + +template struct parse_number { + auto operator()(std::string_view s) -> T { + auto [ok, rest] = consume_hex_prefix(s); + if (ok) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as hexadecimal: " + err.what()); + } + } + + auto [ok_binary, rest_binary] = consume_binary_prefix(s); + if (ok_binary) { + try { + return do_from_chars(rest_binary); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } + } + + if (starts_with("0"sv, s)) { + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as octal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as octal: " + err.what()); + } + } + + try { + return do_from_chars(rest); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as decimal integer: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as decimal integer: " + err.what()); + } + } +}; + +namespace { + +template inline const auto generic_strtod = nullptr; +template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOF; +template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOD; +template <> +inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; + +} // namespace + +template inline auto do_strtod(std::string const &s) -> T { + if (isspace(static_cast(s[0])) || s[0] == '+') { + throw std::invalid_argument{"pattern '" + s + "' not found"}; + } + + auto [first, last] = pointer_range(s); + char *ptr; + + errno = 0; + auto x = generic_strtod(first, &ptr); + if (errno == 0) { + if (ptr == last) { + return x; + } + throw std::invalid_argument{"pattern '" + s + + "' does not match to the end"}; + } + if (errno == ERANGE) { + throw std::range_error{"'" + s + "' not representable"}; + } + return x; // unreachable +} + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::general does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::general does not parse binfloat"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as number: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as number: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { + throw std::invalid_argument{"chars_format::hex parses hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{"chars_format::hex does not parse binfloat"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as hexadecimal: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as hexadecimal: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::binary does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); !r.is_binary) { + throw std::invalid_argument{"chars_format::binary parses binfloat"}; + } + + return do_strtod(s); + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::scientific does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::scientific does not parse binfloat"}; + } + if (s.find_first_of("eE") == std::string::npos) { + throw std::invalid_argument{ + "chars_format::scientific requires exponent part"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as scientific notation: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as scientific notation: " + err.what()); + } + } +}; + +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::fixed does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::fixed does not parse binfloat"}; + } + if (s.find_first_of("eE") != std::string::npos) { + throw std::invalid_argument{ + "chars_format::fixed does not parse exponent part"}; + } + + try { + return do_strtod(s); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + s + + "' as fixed notation: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + s + + "' as fixed notation: " + err.what()); + } + } +}; + +template +std::string join(StrIt first, StrIt last, const std::string &separator) { + if (first == last) { + return ""; + } + std::stringstream value; + value << *first; + ++first; + while (first != last) { + value << separator << *first; + ++first; + } + return value.str(); +} + +template struct can_invoke_to_string { + template + static auto test(int) + -> decltype(std::to_string(std::declval()), std::true_type{}); + + template static auto test(...) -> std::false_type; + + static constexpr bool value = decltype(test(0))::value; +}; + +template struct IsChoiceTypeSupported { + using CleanType = typename std::decay::type; + static const bool value = std::is_integral::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; +}; + +template +std::size_t get_levenshtein_distance(const StringType &s1, + const StringType &s2) { + std::vector> dp( + s1.size() + 1, std::vector(s2.size() + 1, 0)); + + for (std::size_t i = 0; i <= s1.size(); ++i) { + for (std::size_t j = 0; j <= s2.size(); ++j) { + if (i == 0) { + dp[i][j] = j; + } else if (j == 0) { + dp[i][j] = i; + } else if (s1[i - 1] == s2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}); + } + } + } + + return dp[s1.size()][s2.size()]; +} + +template +std::string get_most_similar_string(const std::map &map, + const std::string &input) { + std::string most_similar{}; + std::size_t min_distance = (std::numeric_limits::max)(); + + for (const auto &entry : map) { + std::size_t distance = get_levenshtein_distance(entry.first, input); + if (distance < min_distance) { + min_distance = distance; + most_similar = entry.first; + } + } + + return most_similar; +} + +} // namespace details + +enum class nargs_pattern { optional, any, at_least_one }; + +enum class default_arguments : unsigned int { + none = 0, + help = 1, + version = 2, + all = help | version, +}; + +inline default_arguments operator&(const default_arguments &a, + const default_arguments &b) { + return static_cast( + static_cast::type>(a) & + static_cast::type>(b)); +} + +class ArgumentParser; + +class Argument { + friend class ArgumentParser; + friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) + -> std::ostream &; + + template + explicit Argument(std::string_view prefix_chars, + std::array &&a, + std::index_sequence /*unused*/) + : m_accepts_optional_like_value(false), + m_is_optional((is_optional(a[I], prefix_chars) || ...)), + m_is_required(false), m_is_repeatable(false), m_is_used(false), + m_is_hidden(false), m_prefix_chars(prefix_chars) { + ((void)m_names.emplace_back(a[I]), ...); + std::sort( + m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { + return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); + }); + } + +public: + template + explicit Argument(std::string_view prefix_chars, + std::array &&a) + : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} + + Argument &help(std::string help_text) { + m_help = std::move(help_text); + return *this; + } + + Argument &metavar(std::string metavar) { + m_metavar = std::move(metavar); + return *this; + } + + template Argument &default_value(T &&value) { + m_num_args_range = NArgsRange{0, m_num_args_range.get_max()}; + m_default_value_repr = details::repr(value); + + if constexpr (std::is_convertible_v) { + m_default_value_str = std::string{std::string_view{value}}; + } else if constexpr (details::can_invoke_to_string::value) { + m_default_value_str = std::to_string(value); + } + + m_default_value = std::forward(value); + return *this; + } + + Argument &default_value(const char *value) { + return default_value(std::string(value)); + } + + Argument &required() { + m_is_required = true; + return *this; + } + + Argument &implicit_value(std::any value) { + m_implicit_value = std::move(value); + m_num_args_range = NArgsRange{0, 0}; + return *this; + } + + // This is shorthand for: + // program.add_argument("foo") + // .default_value(false) + // .implicit_value(true) + Argument &flag() { + default_value(false); + implicit_value(true); + return *this; + } + + template + auto action(F &&callable, Args &&... bound_args) + -> std::enable_if_t, + Argument &> { + using action_type = std::conditional_t< + std::is_void_v>, + void_action, valued_action>; + if constexpr (sizeof...(Args) == 0) { + m_actions.emplace_back(std::forward(callable)); + } else { + m_actions.emplace_back( + [f = std::forward(callable), + tup = std::make_tuple(std::forward(bound_args)...)]( + std::string const &opt) mutable { + return details::apply_plus_one(f, tup, opt); + }); + } + return *this; + } + + auto &store_into(bool &var) { + if ((!m_default_value.has_value()) && (!m_implicit_value.has_value())) { + flag(); + } + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto & /*unused*/) { + var = true; + return var; + }); + return *this; + } + + template ::value>::type * = nullptr> + auto &store_into(T &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + return var; + }); + return *this; + } + + template ::value>::type * = nullptr> + auto &store_into(T &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + return var; + }); + return *this; + } + + auto &store_into(std::string &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const std::string &s) { + var = s; + return var; + }); + return *this; + } + + auto &store_into(std::filesystem::path &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const std::string &s) { var = s; }); + return *this; + } + + auto &store_into(std::vector &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.push_back(s); + return var; + }); + return *this; + } + + auto &store_into(std::vector &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.push_back(details::parse_number()(s)); + return var; + }); + return *this; + } + + auto &store_into(std::set &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.insert(s); + return var; + }); + return *this; + } + + auto &store_into(std::set &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.insert(details::parse_number()(s)); + return var; + }); + return *this; + } + + auto &append() { + m_is_repeatable = true; + return *this; + } + + // Cause the argument to be invisible in usage and help + auto &hidden() { + m_is_hidden = true; + return *this; + } + + template + auto scan() -> std::enable_if_t, Argument &> { + static_assert(!(std::is_const_v || std::is_volatile_v), + "T should not be cv-qualified"); + auto is_one_of = [](char c, auto... x) constexpr { + return ((c == x) || ...); + }; + + if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'i') && + details::standard_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'u') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'b') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'o') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'x', 'X') && + details::standard_unsigned_integer) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'a', 'A') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'e', 'E') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'f', 'F') && + std::is_floating_point_v) { + action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'g', 'G') && + std::is_floating_point_v) { + action(details::parse_number()); + } else { + static_assert(alignof(T) == 0, "No scan specification for T"); + } + + return *this; + } + + Argument &nargs(std::size_t num_args) { + m_num_args_range = NArgsRange{num_args, num_args}; + return *this; + } + + Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { + m_num_args_range = NArgsRange{num_args_min, num_args_max}; + return *this; + } + + Argument &nargs(nargs_pattern pattern) { + switch (pattern) { + case nargs_pattern::optional: + m_num_args_range = NArgsRange{0, 1}; + break; + case nargs_pattern::any: + m_num_args_range = + NArgsRange{0, (std::numeric_limits::max)()}; + break; + case nargs_pattern::at_least_one: + m_num_args_range = + NArgsRange{1, (std::numeric_limits::max)()}; + break; + } + return *this; + } + + Argument &remaining() { + m_accepts_optional_like_value = true; + return nargs(nargs_pattern::any); + } + + template void add_choice(T &&choice) { + static_assert(details::IsChoiceTypeSupported::value, + "Only string or integer type supported for choice"); + static_assert(std::is_convertible_v || + details::can_invoke_to_string::value, + "Choice is not convertible to string_type"); + if (!m_choices.has_value()) { + m_choices = std::vector{}; + } + + if constexpr (std::is_convertible_v) { + m_choices.value().push_back( + std::string{std::string_view{std::forward(choice)}}); + } else if constexpr (details::can_invoke_to_string::value) { + m_choices.value().push_back(std::to_string(std::forward(choice))); + } + } + + Argument &choices() { + if (!m_choices.has_value()) { + throw std::runtime_error("Zero choices provided"); + } + return *this; + } + + template + Argument &choices(T &&first, U &&... rest) { + add_choice(std::forward(first)); + choices(std::forward(rest)...); + return *this; + } + + void find_default_value_in_choices_or_throw() const { + + const auto &choices = m_choices.value(); + + if (m_default_value.has_value()) { + if (std::find(choices.begin(), choices.end(), m_default_value_str) == + choices.end()) { + // provided arg not in list of allowed choices + // report error + + std::string choices_as_csv = + std::accumulate(choices.begin(), choices.end(), std::string(), + [](const std::string &a, const std::string &b) { + return a + (a.empty() ? "" : ", ") + b; + }); + + throw std::runtime_error( + std::string{"Invalid default value "} + m_default_value_repr + + " - allowed options: {" + choices_as_csv + "}"); + } + } + } + + template + bool is_value_in_choices(Iterator option_it) const { + + const auto &choices = m_choices.value(); + + return (std::find(choices.begin(), choices.end(), *option_it) != + choices.end()); + } + + template + void throw_invalid_arguments_error(Iterator option_it) const { + const auto &choices = m_choices.value(); + const std::string choices_as_csv = std::accumulate( + choices.begin(), choices.end(), std::string(), + [](const std::string &option_a, const std::string &option_b) { + return option_a + (option_a.empty() ? "" : ", ") + option_b; + }); + + throw std::runtime_error(std::string{"Invalid argument "} + + details::repr(*option_it) + + " - allowed options: {" + choices_as_csv + "}"); + } + + /* The dry_run parameter can be set to true to avoid running the actions, + * and setting m_is_used. This may be used by a pre-processing step to do + * a first iteration over arguments. + */ + template + Iterator consume(Iterator start, Iterator end, + std::string_view used_name = {}, bool dry_run = false) { + if (!m_is_repeatable && m_is_used) { + throw std::runtime_error( + std::string("Duplicate argument ").append(used_name)); + } + m_used_name = used_name; + + std::size_t passed_options = 0; + + if (m_choices.has_value()) { + // Check each value in (start, end) and make sure + // it is in the list of allowed choices/options + const auto max_number_of_args = m_num_args_range.get_max(); + const auto min_number_of_args = m_num_args_range.get_min(); + for (auto it = start; it != end; ++it) { + if (is_value_in_choices(it)) { + passed_options += 1; + continue; + } + + if ((passed_options >= min_number_of_args) && + (passed_options <= max_number_of_args)) { + break; + } + + throw_invalid_arguments_error(it); + } + } + + const auto num_args_max = + (m_choices.has_value()) ? passed_options : m_num_args_range.get_max(); + const auto num_args_min = m_num_args_range.get_min(); + std::size_t dist = 0; + if (num_args_max == 0) { + if (!dry_run) { + m_values.emplace_back(m_implicit_value); + for(auto &action: m_actions) { + std::visit([&](const auto &f) { f({}); }, action); + } + if(m_actions.empty()){ + std::visit([&](const auto &f) { f({}); }, m_default_action); + } + m_is_used = true; + } + return start; + } + if ((dist = static_cast(std::distance(start, end))) >= + num_args_min) { + if (num_args_max < dist) { + end = std::next(start, static_cast( + num_args_max)); + } + if (!m_accepts_optional_like_value) { + end = std::find_if( + start, end, + std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); + dist = static_cast(std::distance(start, end)); + if (dist < num_args_min) { + throw std::runtime_error("Too few arguments for '" + + std::string(m_used_name) + "'."); + } + } + struct ActionApply { + void operator()(valued_action &f) { + std::transform(first, last, std::back_inserter(self.m_values), f); + } + + void operator()(void_action &f) { + std::for_each(first, last, f); + if (!self.m_default_value.has_value()) { + if (!self.m_accepts_optional_like_value) { + self.m_values.resize( + static_cast(std::distance(first, last))); + } + } + } + + Iterator first, last; + Argument &self; + }; + if (!dry_run) { + for(auto &action: m_actions) { + std::visit(ActionApply{start, end, *this}, action); + } + if(m_actions.empty()){ + std::visit(ActionApply{start, end, *this}, m_default_action); + } + m_is_used = true; + } + return end; + } + if (m_default_value.has_value()) { + if (!dry_run) { + m_is_used = true; + } + return start; + } + throw std::runtime_error("Too few arguments for '" + + std::string(m_used_name) + "'."); + } + + /* + * @throws std::runtime_error if argument values are not valid + */ + void validate() const { + if (m_is_optional) { + // TODO: check if an implicit value was programmed for this argument + if (!m_is_used && !m_default_value.has_value() && m_is_required) { + throw_required_arg_not_used_error(); + } + if (m_is_used && m_is_required && m_values.empty()) { + throw_required_arg_no_value_provided_error(); + } + } else { + if (!m_num_args_range.contains(m_values.size()) && + !m_default_value.has_value()) { + throw_nargs_range_validation_error(); + } + } + + if (m_choices.has_value()) { + // Make sure the default value (if provided) + // is in the list of choices + find_default_value_in_choices_or_throw(); + } + } + + std::string get_names_csv(char separator = ',') const { + return std::accumulate( + m_names.begin(), m_names.end(), std::string{""}, + [&](const std::string &result, const std::string &name) { + return result.empty() ? name : result + separator + name; + }); + } + + std::string get_usage_full() const { + std::stringstream usage; + + usage << get_names_csv('/'); + const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; + if (m_num_args_range.get_max() > 0) { + usage << " " << metavar; + if (m_num_args_range.get_max() > 1) { + usage << "..."; + } + } + return usage.str(); + } + + std::string get_inline_usage() const { + std::stringstream usage; + // Find the longest variant to show in the usage string + std::string longest_name = m_names.front(); + for (const auto &s : m_names) { + if (s.size() > longest_name.size()) { + longest_name = s; + } + } + if (!m_is_required) { + usage << "["; + } + usage << longest_name; + const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; + if (m_num_args_range.get_max() > 0) { + usage << " " << metavar; + if (m_num_args_range.get_max() > 1 && + m_metavar.find("> <") == std::string::npos) { + usage << "..."; + } + } + if (!m_is_required) { + usage << "]"; + } + if (m_is_repeatable) { + usage << "..."; + } + return usage.str(); + } + + std::size_t get_arguments_length() const { + + std::size_t names_size = std::accumulate( + std::begin(m_names), std::end(m_names), std::size_t(0), + [](const auto &sum, const auto &s) { return sum + s.size(); }); + + if (is_positional(m_names.front(), m_prefix_chars)) { + // A set metavar means this replaces the names + if (!m_metavar.empty()) { + // Indent and metavar + return 2 + m_metavar.size(); + } + + // Indent and space-separated + return 2 + names_size + (m_names.size() - 1); + } + // Is an option - include both names _and_ metavar + // size = text + (", " between names) + std::size_t size = names_size + 2 * (m_names.size() - 1); + if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { + size += m_metavar.size() + 1; + } + return size + 2; // indent + } + + friend std::ostream &operator<<(std::ostream &stream, + const Argument &argument) { + std::stringstream name_stream; + name_stream << " "; // indent + if (argument.is_positional(argument.m_names.front(), + argument.m_prefix_chars)) { + if (!argument.m_metavar.empty()) { + name_stream << argument.m_metavar; + } else { + name_stream << details::join(argument.m_names.begin(), + argument.m_names.end(), " "); + } + } else { + name_stream << details::join(argument.m_names.begin(), + argument.m_names.end(), ", "); + // If we have a metavar, and one narg - print the metavar + if (!argument.m_metavar.empty() && + argument.m_num_args_range == NArgsRange{1, 1}) { + name_stream << " " << argument.m_metavar; + } + else if (!argument.m_metavar.empty() && + argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() && + argument.m_metavar.find("> <") != std::string::npos) { + name_stream << " " << argument.m_metavar; + } + } + + // align multiline help message + auto stream_width = stream.width(); + auto name_padding = std::string(name_stream.str().size(), ' '); + auto pos = std::string::size_type{}; + auto prev = std::string::size_type{}; + auto first_line = true; + auto hspace = " "; // minimal space between name and help message + stream << name_stream.str(); + std::string_view help_view(argument.m_help); + while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) { + auto line = help_view.substr(prev, pos - prev + 1); + if (first_line) { + stream << hspace << line; + first_line = false; + } else { + stream.width(stream_width); + stream << name_padding << hspace << line; + } + prev += pos - prev + 1; + } + if (first_line) { + stream << hspace << argument.m_help; + } else { + auto leftover = help_view.substr(prev, argument.m_help.size() - prev); + if (!leftover.empty()) { + stream.width(stream_width); + stream << name_padding << hspace << leftover; + } + } + + // print nargs spec + if (!argument.m_help.empty()) { + stream << " "; + } + stream << argument.m_num_args_range; + + bool add_space = false; + if (argument.m_default_value.has_value() && + argument.m_num_args_range != NArgsRange{0, 0}) { + stream << "[default: " << argument.m_default_value_repr << "]"; + add_space = true; + } else if (argument.m_is_required) { + stream << "[required]"; + add_space = true; + } + if (argument.m_is_repeatable) { + if (add_space) { + stream << " "; + } + stream << "[may be repeated]"; + } + stream << "\n"; + return stream; + } + + template bool operator!=(const T &rhs) const { + return !(*this == rhs); + } + + /* + * Compare to an argument value of known type + * @throws std::logic_error in case of incompatible types + */ + template bool operator==(const T &rhs) const { + if constexpr (!details::IsContainer) { + return get() == rhs; + } else { + using ValueType = typename T::value_type; + auto lhs = get(); + return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), + std::end(rhs), [](const auto &a, const auto &b) { + return std::any_cast(a) == b; + }); + } + } + + /* + * positional: + * _empty_ + * '-' + * '-' decimal-literal + * !'-' anything + */ + static bool is_positional(std::string_view name, + std::string_view prefix_chars) { + auto first = lookahead(name); + + if (first == eof) { + return true; + } + if (prefix_chars.find(static_cast(first)) != + std::string_view::npos) { + name.remove_prefix(1); + if (name.empty()) { + return true; + } + return is_decimal_literal(name); + } + return true; + } + +private: + class NArgsRange { + std::size_t m_min; + std::size_t m_max; + + public: + NArgsRange(std::size_t minimum, std::size_t maximum) + : m_min(minimum), m_max(maximum) { + if (minimum > maximum) { + throw std::logic_error("Range of number of arguments is invalid"); + } + } + + bool contains(std::size_t value) const { + return value >= m_min && value <= m_max; + } + + bool is_exact() const { return m_min == m_max; } + + bool is_right_bounded() const { + return m_max < (std::numeric_limits::max)(); + } + + std::size_t get_min() const { return m_min; } + + std::size_t get_max() const { return m_max; } + + // Print help message + friend auto operator<<(std::ostream &stream, const NArgsRange &range) + -> std::ostream & { + if (range.m_min == range.m_max) { + if (range.m_min != 0 && range.m_min != 1) { + stream << "[nargs: " << range.m_min << "] "; + } + } else { + if (range.m_max == (std::numeric_limits::max)()) { + stream << "[nargs: " << range.m_min << " or more] "; + } else { + stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; + } + } + return stream; + } + + bool operator==(const NArgsRange &rhs) const { + return rhs.m_min == m_min && rhs.m_max == m_max; + } + + bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } + }; + + void throw_nargs_range_validation_error() const { + std::stringstream stream; + if (!m_used_name.empty()) { + stream << m_used_name << ": "; + } else { + stream << m_names.front() << ": "; + } + if (m_num_args_range.is_exact()) { + stream << m_num_args_range.get_min(); + } else if (m_num_args_range.is_right_bounded()) { + stream << m_num_args_range.get_min() << " to " + << m_num_args_range.get_max(); + } else { + stream << m_num_args_range.get_min() << " or more"; + } + stream << " argument(s) expected. " << m_values.size() << " provided."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_not_used_error() const { + std::stringstream stream; + stream << m_names.front() << ": required."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_no_value_provided_error() const { + std::stringstream stream; + stream << m_used_name << ": no value provided."; + throw std::runtime_error(stream.str()); + } + + static constexpr int eof = std::char_traits::eof(); + + static auto lookahead(std::string_view s) -> int { + if (s.empty()) { + return eof; + } + return static_cast(static_cast(s[0])); + } + + /* + * decimal-literal: + * '0' + * nonzero-digit digit-sequence_opt + * integer-part fractional-part + * fractional-part + * integer-part '.' exponent-part_opt + * integer-part exponent-part + * + * integer-part: + * digit-sequence + * + * fractional-part: + * '.' post-decimal-point + * + * post-decimal-point: + * digit-sequence exponent-part_opt + * + * exponent-part: + * 'e' post-e + * 'E' post-e + * + * post-e: + * sign_opt digit-sequence + * + * sign: one of + * '+' '-' + */ + static bool is_decimal_literal(std::string_view s) { + auto is_digit = [](auto c) constexpr { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return true; + default: + return false; + } + }; + + // precondition: we have consumed or will consume at least one digit + auto consume_digits = [=](std::string_view sd) { + // NOLINTNEXTLINE(readability-qualified-auto) + auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); + return sd.substr(static_cast(it - std::begin(sd))); + }; + + switch (lookahead(s)) { + case '0': { + s.remove_prefix(1); + if (s.empty()) { + return true; + } + goto integer_part; + } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + s = consume_digits(s); + if (s.empty()) { + return true; + } + goto integer_part_consumed; + } + case '.': { + s.remove_prefix(1); + goto post_decimal_point; + } + default: + return false; + } + + integer_part: + s = consume_digits(s); + integer_part_consumed: + switch (lookahead(s)) { + case '.': { + s.remove_prefix(1); + if (is_digit(lookahead(s))) { + goto post_decimal_point; + } else { + goto exponent_part_opt; + } + } + case 'e': + case 'E': { + s.remove_prefix(1); + goto post_e; + } + default: + return false; + } + + post_decimal_point: + if (is_digit(lookahead(s))) { + s = consume_digits(s); + goto exponent_part_opt; + } + return false; + + exponent_part_opt: + switch (lookahead(s)) { + case eof: + return true; + case 'e': + case 'E': { + s.remove_prefix(1); + goto post_e; + } + default: + return false; + } + + post_e: + switch (lookahead(s)) { + case '-': + case '+': + s.remove_prefix(1); + } + if (is_digit(lookahead(s))) { + s = consume_digits(s); + return s.empty(); + } + return false; + } + + static bool is_optional(std::string_view name, + std::string_view prefix_chars) { + return !is_positional(name, prefix_chars); + } + + /* + * Get argument value given a type + * @throws std::logic_error in case of incompatible types + */ + template T get() const { + if (!m_values.empty()) { + if constexpr (details::IsContainer) { + return any_cast_container(m_values); + } else { + return std::any_cast(m_values.front()); + } + } + if (m_default_value.has_value()) { + return std::any_cast(m_default_value); + } + if constexpr (details::IsContainer) { + if (!m_accepts_optional_like_value) { + return any_cast_container(m_values); + } + } + + throw std::logic_error("No value provided for '" + m_names.back() + "'."); + } + + /* + * Get argument value given a type. + * @pre The object has no default value. + * @returns The stored value if any, std::nullopt otherwise. + */ + template auto present() const -> std::optional { + if (m_default_value.has_value()) { + throw std::logic_error("Argument with default value always presents"); + } + if (m_values.empty()) { + return std::nullopt; + } + if constexpr (details::IsContainer) { + return any_cast_container(m_values); + } + return std::any_cast(m_values.front()); + } + + template + static auto any_cast_container(const std::vector &operand) -> T { + using ValueType = typename T::value_type; + + T result; + std::transform( + std::begin(operand), std::end(operand), std::back_inserter(result), + [](const auto &value) { return std::any_cast(value); }); + return result; + } + + void set_usage_newline_counter(int i) { m_usage_newline_counter = i; } + + void set_group_idx(std::size_t i) { m_group_idx = i; } + + std::vector m_names; + std::string_view m_used_name; + std::string m_help; + std::string m_metavar; + std::any m_default_value; + std::string m_default_value_repr; + std::optional + m_default_value_str; // used for checking default_value against choices + std::any m_implicit_value; + std::optional> m_choices{std::nullopt}; + using valued_action = std::function; + using void_action = std::function; + std::vector> m_actions; + std::variant m_default_action{ + std::in_place_type, + [](const std::string &value) { return value; }}; + std::vector m_values; + NArgsRange m_num_args_range{1, 1}; + // Bit field of bool values. Set default value in ctor. + bool m_accepts_optional_like_value : 1; + bool m_is_optional : 1; + bool m_is_required : 1; + bool m_is_repeatable : 1; + bool m_is_used : 1; + bool m_is_hidden : 1; // if set, does not appear in usage or help + std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars + int m_usage_newline_counter = 0; + std::size_t m_group_idx = 0; +}; + +class ArgumentParser { +public: + explicit ArgumentParser(std::string program_name = {}, + std::string version = "1.0", + default_arguments add_args = default_arguments::all, + bool exit_on_default_arguments = true, + std::ostream &os = std::cout) + : m_program_name(std::move(program_name)), m_version(std::move(version)), + m_exit_on_default_arguments(exit_on_default_arguments), + m_parser_path(m_program_name) { + if ((add_args & default_arguments::help) == default_arguments::help) { + add_argument("-h", "--help") + .action([&](const auto & /*unused*/) { + os << help().str(); + if (m_exit_on_default_arguments) { + std::exit(0); + } + }) + .default_value(false) + .help("shows help message and exits") + .implicit_value(true) + .nargs(0); + } + if ((add_args & default_arguments::version) == default_arguments::version) { + add_argument("-v", "--version") + .action([&](const auto & /*unused*/) { + os << m_version << std::endl; + if (m_exit_on_default_arguments) { + std::exit(0); + } + }) + .default_value(false) + .help("prints version information and exits") + .implicit_value(true) + .nargs(0); + } + } + + ~ArgumentParser() = default; + + // ArgumentParser is meant to be used in a single function. + // Setup everything and parse arguments in one place. + // + // ArgumentParser internally uses std::string_views, + // references, iterators, etc. + // Many of these elements become invalidated after a copy or move. + ArgumentParser(const ArgumentParser &other) = delete; + ArgumentParser &operator=(const ArgumentParser &other) = delete; + ArgumentParser(ArgumentParser &&) noexcept = delete; + ArgumentParser &operator=(ArgumentParser &&) = delete; + + explicit operator bool() const { + auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(), + [](auto &it) { return it.second->m_is_used; }); + auto subparser_used = + std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(), + [](auto &it) { return it.second; }); + + return m_is_parsed && (arg_used || subparser_used); + } + + // Parameter packing + // Call add_argument with variadic number of string arguments + template Argument &add_argument(Targs... f_args) { + using array_of_sv = std::array; + auto argument = + m_optional_arguments.emplace(std::cend(m_optional_arguments), + m_prefix_chars, array_of_sv{f_args...}); + + if (!argument->m_is_optional) { + m_positional_arguments.splice(std::cend(m_positional_arguments), + m_optional_arguments, argument); + } + argument->set_usage_newline_counter(m_usage_newline_counter); + argument->set_group_idx(m_group_names.size()); + + index_argument(argument); + return *argument; + } + + class MutuallyExclusiveGroup { + friend class ArgumentParser; + + public: + MutuallyExclusiveGroup() = delete; + + explicit MutuallyExclusiveGroup(ArgumentParser &parent, + bool required = false) + : m_parent(parent), m_required(required), m_elements({}) {} + + MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete; + MutuallyExclusiveGroup & + operator=(const MutuallyExclusiveGroup &other) = delete; + + MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept + : m_parent(other.m_parent), m_required(other.m_required), + m_elements(std::move(other.m_elements)) { + other.m_elements.clear(); + } + + template Argument &add_argument(Targs... f_args) { + auto &argument = m_parent.add_argument(std::forward(f_args)...); + m_elements.push_back(&argument); + argument.set_usage_newline_counter(m_parent.m_usage_newline_counter); + argument.set_group_idx(m_parent.m_group_names.size()); + return argument; + } + + private: + ArgumentParser &m_parent; + bool m_required{false}; + std::vector m_elements{}; + }; + + MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) { + m_mutually_exclusive_groups.emplace_back(*this, required); + return m_mutually_exclusive_groups.back(); + } + + // Parameter packed add_parents method + // Accepts a variadic number of ArgumentParser objects + template + ArgumentParser &add_parents(const Targs &... f_args) { + for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { + for (const auto &argument : parent_parser.m_positional_arguments) { + auto it = m_positional_arguments.insert( + std::cend(m_positional_arguments), argument); + index_argument(it); + } + for (const auto &argument : parent_parser.m_optional_arguments) { + auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), + argument); + index_argument(it); + } + } + return *this; + } + + // Ask for the next optional arguments to be displayed on a separate + // line in usage() output. Only effective if set_usage_max_line_width() is + // also used. + ArgumentParser &add_usage_newline() { + ++m_usage_newline_counter; + return *this; + } + + // Ask for the next optional arguments to be displayed in a separate section + // in usage() and help (<< *this) output. + // For usage(), this is only effective if set_usage_max_line_width() is + // also used. + ArgumentParser &add_group(std::string group_name) { + m_group_names.emplace_back(std::move(group_name)); + return *this; + } + + ArgumentParser &add_description(std::string description) { + m_description = std::move(description); + return *this; + } + + ArgumentParser &add_epilog(std::string epilog) { + m_epilog = std::move(epilog); + return *this; + } + + // Add a un-documented/hidden alias for an argument. + // Ideally we'd want this to be a method of Argument, but Argument + // does not own its owing ArgumentParser. + ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) { + for (auto it = m_optional_arguments.begin(); + it != m_optional_arguments.end(); ++it) { + if (&(*it) == &arg) { + m_argument_map.insert_or_assign(std::string(alias), it); + return *this; + } + } + throw std::logic_error( + "Argument is not an optional argument of this parser"); + } + + /* Getter for arguments and subparsers. + * @throws std::logic_error in case of an invalid argument or subparser name + */ + template T &at(std::string_view name) { + if constexpr (std::is_same_v) { + return (*this)[name]; + } else { + std::string str_name(name); + auto subparser_it = m_subparser_map.find(str_name); + if (subparser_it != m_subparser_map.end()) { + return subparser_it->second->get(); + } + throw std::logic_error("No such subparser: " + str_name); + } + } + + ArgumentParser &set_prefix_chars(std::string prefix_chars) { + m_prefix_chars = std::move(prefix_chars); + return *this; + } + + ArgumentParser &set_assign_chars(std::string assign_chars) { + m_assign_chars = std::move(assign_chars); + return *this; + } + + /* Call parse_args_internal - which does all the work + * Then, validate the parsed arguments + * This variant is used mainly for testing + * @throws std::runtime_error in case of any invalid argument + */ + void parse_args(const std::vector &arguments) { + parse_args_internal(arguments); + // Check if all arguments are parsed + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + argument->validate(); + } + + // Check each mutually exclusive group and make sure + // there are no constraint violations + for (const auto &group : m_mutually_exclusive_groups) { + auto mutex_argument_used{false}; + Argument *mutex_argument_it{nullptr}; + for (Argument *arg : group.m_elements) { + if (!mutex_argument_used && arg->m_is_used) { + mutex_argument_used = true; + mutex_argument_it = arg; + } else if (mutex_argument_used && arg->m_is_used) { + // Violation + throw std::runtime_error("Argument '" + arg->get_usage_full() + + "' not allowed with '" + + mutex_argument_it->get_usage_full() + "'"); + } + } + + if (!mutex_argument_used && group.m_required) { + // at least one argument from the group is + // required + std::string argument_names{}; + std::size_t i = 0; + std::size_t size = group.m_elements.size(); + for (Argument *arg : group.m_elements) { + if (i + 1 == size) { + // last + argument_names += std::string("'") + arg->get_usage_full() + std::string("' "); + } else { + argument_names += std::string("'") + arg->get_usage_full() + std::string("' or "); + } + i += 1; + } + throw std::runtime_error("One of the arguments " + argument_names + + "is required"); + } + } + } + + /* Call parse_known_args_internal - which does all the work + * Then, validate the parsed arguments + * This variant is used mainly for testing + * @throws std::runtime_error in case of any invalid argument + */ + std::vector + parse_known_args(const std::vector &arguments) { + auto unknown_arguments = parse_known_args_internal(arguments); + // Check if all arguments are parsed + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + argument->validate(); + } + return unknown_arguments; + } + + /* Main entry point for parsing command-line arguments using this + * ArgumentParser + * @throws std::runtime_error in case of any invalid argument + */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) + void parse_args(int argc, const char *const argv[]) { + parse_args({argv, argv + argc}); + } + + /* Main entry point for parsing command-line arguments using this + * ArgumentParser + * @throws std::runtime_error in case of any invalid argument + */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) + auto parse_known_args(int argc, const char *const argv[]) { + return parse_known_args({argv, argv + argc}); + } + + /* Getter for options with default values. + * @throws std::logic_error if parse_args() has not been previously called + * @throws std::logic_error if there is no such option + * @throws std::logic_error if the option has no value + * @throws std::bad_any_cast if the option is not of type T + */ + template T get(std::string_view arg_name) const { + if (!m_is_parsed) { + throw std::logic_error("Nothing parsed, no arguments are available."); + } + return (*this)[arg_name].get(); + } + + /* Getter for options without default values. + * @pre The option has no default value. + * @throws std::logic_error if there is no such option + * @throws std::bad_any_cast if the option is not of type T + */ + template + auto present(std::string_view arg_name) const -> std::optional { + return (*this)[arg_name].present(); + } + + /* Getter that returns true for user-supplied options. Returns false if not + * user-supplied, even with a default value. + */ + auto is_used(std::string_view arg_name) const { + return (*this)[arg_name].m_is_used; + } + + /* Getter that returns true if a subcommand is used. + */ + auto is_subcommand_used(std::string_view subcommand_name) const { + return m_subparser_used.at(std::string(subcommand_name)); + } + + /* Getter that returns true if a subcommand is used. + */ + auto is_subcommand_used(const ArgumentParser &subparser) const { + return is_subcommand_used(subparser.m_program_name); + } + + /* Indexing operator. Return a reference to an Argument object + * Used in conjunction with Argument.operator== e.g., parser["foo"] == true + * @throws std::logic_error in case of an invalid argument name + */ + Argument &operator[](std::string_view arg_name) const { + std::string name(arg_name); + auto it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + if (!is_valid_prefix_char(arg_name.front())) { + const auto legal_prefix_char = get_any_valid_prefix_char(); + const auto prefix = std::string(1, legal_prefix_char); + + // "-" + arg_name + name = prefix + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + // "--" + arg_name + name = prefix + name; + it = m_argument_map.find(name); + if (it != m_argument_map.end()) { + return *(it->second); + } + } + throw std::logic_error("No such argument: " + std::string(arg_name)); + } + + // Print help message + friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) + -> std::ostream & { + stream.setf(std::ios_base::left); + + auto longest_arg_length = parser.get_length_of_longest_argument(); + + stream << parser.usage() << "\n\n"; + + if (!parser.m_description.empty()) { + stream << parser.m_description << "\n\n"; + } + + const bool has_visible_positional_args = std::find_if( + parser.m_positional_arguments.begin(), + parser.m_positional_arguments.end(), + [](const auto &argument) { + return !argument.m_is_hidden; }) != + parser.m_positional_arguments.end(); + if (has_visible_positional_args) { + stream << "Positional arguments:\n"; + } + + for (const auto &argument : parser.m_positional_arguments) { + if (!argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + + if (!parser.m_optional_arguments.empty()) { + stream << (!has_visible_positional_args ? "" : "\n") + << "Optional arguments:\n"; + } + + for (const auto &argument : parser.m_optional_arguments) { + if (argument.m_group_idx == 0 && !argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + + for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) { + stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n"; + for (const auto &argument : parser.m_optional_arguments) { + if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) { + stream.width(static_cast(longest_arg_length)); + stream << argument; + } + } + } + + bool has_visible_subcommands = std::any_of( + parser.m_subparser_map.begin(), parser.m_subparser_map.end(), + [](auto &p) { return !p.second->get().m_suppress; }); + + if (has_visible_subcommands) { + stream << (parser.m_positional_arguments.empty() + ? (parser.m_optional_arguments.empty() ? "" : "\n") + : "\n") + << "Subcommands:\n"; + for (const auto &[command, subparser] : parser.m_subparser_map) { + if (subparser->get().m_suppress) { + continue; + } + + stream << std::setw(2) << " "; + stream << std::setw(static_cast(longest_arg_length - 2)) + << command; + stream << " " << subparser->get().m_description << "\n"; + } + } + + if (!parser.m_epilog.empty()) { + stream << '\n'; + stream << parser.m_epilog << "\n\n"; + } + + return stream; + } + + // Format help message + auto help() const -> std::stringstream { + std::stringstream out; + out << *this; + return out; + } + + // Sets the maximum width for a line of the Usage message + ArgumentParser &set_usage_max_line_width(size_t w) { + this->m_usage_max_line_width = w; + return *this; + } + + // Asks to display arguments of mutually exclusive group on separate lines in + // the Usage message + ArgumentParser &set_usage_break_on_mutex() { + this->m_usage_break_on_mutex = true; + return *this; + } + + // Format usage part of help only + auto usage() const -> std::string { + std::stringstream stream; + + std::string curline("Usage: "); + curline += this->m_parser_path; + const bool multiline_usage = + this->m_usage_max_line_width < (std::numeric_limits::max)(); + const size_t indent_size = curline.size(); + + const auto deal_with_options_of_group = [&](std::size_t group_idx) { + bool found_options = false; + // Add any options inline here + const MutuallyExclusiveGroup *cur_mutex = nullptr; + int usage_newline_counter = -1; + for (const auto &argument : this->m_optional_arguments) { + if (argument.m_is_hidden) { + continue; + } + if (multiline_usage) { + if (argument.m_group_idx != group_idx) { + continue; + } + if (usage_newline_counter != argument.m_usage_newline_counter) { + if (usage_newline_counter >= 0) { + if (curline.size() > indent_size) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + } + usage_newline_counter = argument.m_usage_newline_counter; + } + } + found_options = true; + const std::string arg_inline_usage = argument.get_inline_usage(); + const MutuallyExclusiveGroup *arg_mutex = + get_belonging_mutex(&argument); + if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) { + curline += ']'; + if (this->m_usage_break_on_mutex) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) { + if ((this->m_usage_break_on_mutex && curline.size() > indent_size) || + curline.size() + 3 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " ["; + } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) { + if (cur_mutex != arg_mutex) { + curline += ']'; + if (this->m_usage_break_on_mutex || + curline.size() + 3 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " ["; + } else { + curline += '|'; + } + } + cur_mutex = arg_mutex; + if (curline.size() != indent_size && + curline.size() + 1 + arg_inline_usage.size() > + this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + curline += " "; + } else if (cur_mutex == nullptr) { + curline += " "; + } + curline += arg_inline_usage; + } + if (cur_mutex != nullptr) { + curline += ']'; + } + return found_options; + }; + + const bool found_options = deal_with_options_of_group(0); + + if (found_options && multiline_usage && + !this->m_positional_arguments.empty()) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + // Put positional arguments after the optionals + for (const auto &argument : this->m_positional_arguments) { + if (argument.m_is_hidden) { + continue; + } + const std::string pos_arg = !argument.m_metavar.empty() + ? argument.m_metavar + : argument.m_names.front(); + if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) { + stream << curline << std::endl; + curline = std::string(indent_size, ' '); + } + curline += " "; + if (argument.m_num_args_range.get_min() == 0 && + !argument.m_num_args_range.is_right_bounded()) { + curline += "["; + curline += pos_arg; + curline += "]..."; + } else if (argument.m_num_args_range.get_min() == 1 && + !argument.m_num_args_range.is_right_bounded()) { + curline += pos_arg; + curline += "..."; + } else { + curline += pos_arg; + } + } + + if (multiline_usage) { + // Display options of other groups + for (std::size_t i = 0; i < m_group_names.size(); ++i) { + stream << curline << std::endl << std::endl; + stream << m_group_names[i] << ":" << std::endl; + curline = std::string(indent_size, ' '); + deal_with_options_of_group(i + 1); + } + } + + stream << curline; + + // Put subcommands after positional arguments + if (!m_subparser_map.empty()) { + stream << " {"; + std::size_t i{0}; + for (const auto &[command, subparser] : m_subparser_map) { + if (subparser->get().m_suppress) { + continue; + } + + if (i == 0) { + stream << command; + } else { + stream << "," << command; + } + ++i; + } + stream << "}"; + } + + return stream.str(); + } + + // Printing the one and only help message + // I've stuck with a simple message format, nothing fancy. + [[deprecated("Use cout << program; instead. See also help().")]] std::string + print_help() const { + auto out = help(); + std::cout << out.rdbuf(); + return out.str(); + } + + void add_subparser(ArgumentParser &parser) { + parser.m_parser_path = m_program_name + " " + parser.m_program_name; + auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); + m_subparser_map.insert_or_assign(parser.m_program_name, it); + m_subparser_used.insert_or_assign(parser.m_program_name, false); + } + + void set_suppress(bool suppress) { m_suppress = suppress; } + +protected: + const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const { + for (const auto &mutex : m_mutually_exclusive_groups) { + if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) != + mutex.m_elements.end()) { + return &mutex; + } + } + return nullptr; + } + + bool is_valid_prefix_char(char c) const { + return m_prefix_chars.find(c) != std::string::npos; + } + + char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } + + /* + * Pre-process this argument list. Anything starting with "--", that + * contains an =, where the prefix before the = has an entry in the + * options table, should be split. + */ + std::vector + preprocess_arguments(const std::vector &raw_arguments) const { + std::vector arguments{}; + for (const auto &arg : raw_arguments) { + + const auto argument_starts_with_prefix_chars = + [this](const std::string &a) -> bool { + if (!a.empty()) { + + const auto legal_prefix = [this](char c) -> bool { + return m_prefix_chars.find(c) != std::string::npos; + }; + + // Windows-style + // if '/' is a legal prefix char + // then allow single '/' followed by argument name, followed by an + // assign char, e.g., ':' e.g., 'test.exe /A:Foo' + const auto windows_style = legal_prefix('/'); + + if (windows_style) { + if (legal_prefix(a[0])) { + return true; + } + } else { + // Slash '/' is not a legal prefix char + // For all other characters, only support long arguments + // i.e., the argument must start with 2 prefix chars, e.g, + // '--foo' e,g, './test --foo=Bar -DARG=yes' + if (a.size() > 1) { + return (legal_prefix(a[0]) && legal_prefix(a[1])); + } + } + } + return false; + }; + + // Check that: + // - We don't have an argument named exactly this + // - The argument starts with a prefix char, e.g., "--" + // - The argument contains an assign char, e.g., "=" + auto assign_char_pos = arg.find_first_of(m_assign_chars); + + if (m_argument_map.find(arg) == m_argument_map.end() && + argument_starts_with_prefix_chars(arg) && + assign_char_pos != std::string::npos) { + // Get the name of the potential option, and check it exists + std::string opt_name = arg.substr(0, assign_char_pos); + if (m_argument_map.find(opt_name) != m_argument_map.end()) { + // This is the name of an option! Split it into two parts + arguments.push_back(std::move(opt_name)); + arguments.push_back(arg.substr(assign_char_pos + 1)); + continue; + } + } + // If we've fallen through to here, then it's a standard argument + arguments.push_back(arg); + } + return arguments; + } + + /* + * @throws std::runtime_error in case of any invalid argument + */ + void parse_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); + if (m_program_name.empty() && !arguments.empty()) { + m_program_name = arguments.front(); + } + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument, m_prefix_chars)) { + if (positional_argument_it == std::end(m_positional_arguments)) { + + // Check sub-parsers + auto subparser_it = m_subparser_map.find(current_argument); + if (subparser_it != m_subparser_map.end()) { + + // build list of remaining args + const auto unprocessed_arguments = + std::vector(it, end); + + // invoke subparser + m_is_parsed = true; + m_subparser_used[current_argument] = true; + return subparser_it->second->get().parse_args( + unprocessed_arguments); + } + + if (m_positional_arguments.empty()) { + + // Ask the user if they argument they provided was a typo + // for some sub-parser, + // e.g., user provided `git totes` instead of `git notes` + if (!m_subparser_map.empty()) { + throw std::runtime_error( + "Failed to parse '" + current_argument + "', did you mean '" + + std::string{details::get_most_similar_string( + m_subparser_map, current_argument)} + + "'"); + } + + // Ask the user if they meant to use a specific optional argument + if (!m_optional_arguments.empty()) { + for (const auto &opt : m_optional_arguments) { + if (!opt.m_implicit_value.has_value()) { + // not a flag, requires a value + if (!opt.m_is_used) { + throw std::runtime_error( + "Zero positional arguments expected, did you mean " + + opt.get_usage_full()); + } + } + } + + throw std::runtime_error("Zero positional arguments expected"); + } else { + throw std::runtime_error("Zero positional arguments expected"); + } + } else { + throw std::runtime_error("Maximum number of positional arguments " + "exceeded, failed to parse '" + + current_argument + "'"); + } + } + auto argument = positional_argument_it++; + + // Deal with the situation of ... + if (argument->m_num_args_range.get_min() == 1 && + argument->m_num_args_range.get_max() == (std::numeric_limits::max)() && + positional_argument_it != std::end(m_positional_arguments) && + std::next(positional_argument_it) == std::end(m_positional_arguments) && + positional_argument_it->m_num_args_range.get_min() == 1 && + positional_argument_it->m_num_args_range.get_max() == 1 ) { + if (std::next(it) != end) { + positional_argument_it->consume(std::prev(end), end); + end = std::prev(end); + } else { + throw std::runtime_error("Missing " + positional_argument_it->m_names.front()); + } + } + + it = argument->consume(it, end); + continue; + } + + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) { + auto argument = arg_map_it->second; + it = argument->consume(std::next(it), end, arg_map_it->first); + } else if (const auto &compound_arg = current_argument; + compound_arg.size() > 1 && + is_valid_prefix_char(compound_arg[0]) && + !is_valid_prefix_char(compound_arg[1])) { + ++it; + for (std::size_t j = 1; j < compound_arg.size(); j++) { + auto hypothetical_arg = std::string{'-', compound_arg[j]}; + auto arg_map_it2 = m_argument_map.find(hypothetical_arg); + if (arg_map_it2 != m_argument_map.end()) { + auto argument = arg_map_it2->second; + it = argument->consume(it, end, arg_map_it2->first); + } else { + throw std::runtime_error("Unknown argument: " + current_argument); + } + } + } else { + throw std::runtime_error("Unknown argument: " + current_argument); + } + } + m_is_parsed = true; + } + + /* + * Like parse_args_internal but collects unused args into a vector + */ + std::vector + parse_known_args_internal(const std::vector &raw_arguments) { + auto arguments = preprocess_arguments(raw_arguments); + + std::vector unknown_arguments{}; + + if (m_program_name.empty() && !arguments.empty()) { + m_program_name = arguments.front(); + } + auto end = std::end(arguments); + auto positional_argument_it = std::begin(m_positional_arguments); + for (auto it = std::next(std::begin(arguments)); it != end;) { + const auto ¤t_argument = *it; + if (Argument::is_positional(current_argument, m_prefix_chars)) { + if (positional_argument_it == std::end(m_positional_arguments)) { + + // Check sub-parsers + auto subparser_it = m_subparser_map.find(current_argument); + if (subparser_it != m_subparser_map.end()) { + + // build list of remaining args + const auto unprocessed_arguments = + std::vector(it, end); + + // invoke subparser + m_is_parsed = true; + m_subparser_used[current_argument] = true; + return subparser_it->second->get().parse_known_args_internal( + unprocessed_arguments); + } + + // save current argument as unknown and go to next argument + unknown_arguments.push_back(current_argument); + ++it; + } else { + // current argument is the value of a positional argument + // consume it + auto argument = positional_argument_it++; + it = argument->consume(it, end); + } + continue; + } + + auto arg_map_it = m_argument_map.find(current_argument); + if (arg_map_it != m_argument_map.end()) { + auto argument = arg_map_it->second; + it = argument->consume(std::next(it), end, arg_map_it->first); + } else if (const auto &compound_arg = current_argument; + compound_arg.size() > 1 && + is_valid_prefix_char(compound_arg[0]) && + !is_valid_prefix_char(compound_arg[1])) { + ++it; + for (std::size_t j = 1; j < compound_arg.size(); j++) { + auto hypothetical_arg = std::string{'-', compound_arg[j]}; + auto arg_map_it2 = m_argument_map.find(hypothetical_arg); + if (arg_map_it2 != m_argument_map.end()) { + auto argument = arg_map_it2->second; + it = argument->consume(it, end, arg_map_it2->first); + } else { + unknown_arguments.push_back(current_argument); + break; + } + } + } else { + // current argument is an optional-like argument that is unknown + // save it and move to next argument + unknown_arguments.push_back(current_argument); + ++it; + } + } + m_is_parsed = true; + return unknown_arguments; + } + + // Used by print_help. + std::size_t get_length_of_longest_argument() const { + if (m_argument_map.empty()) { + return 0; + } + std::size_t max_size = 0; + for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { + max_size = + std::max(max_size, argument->get_arguments_length()); + } + for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { + max_size = std::max(max_size, command.size()); + } + return max_size; + } + + using argument_it = std::list::iterator; + using mutex_group_it = std::vector::iterator; + using argument_parser_it = + std::list>::iterator; + + void index_argument(argument_it it) { + for (const auto &name : std::as_const(it->m_names)) { + m_argument_map.insert_or_assign(name, it); + } + } + + std::string m_program_name; + std::string m_version; + std::string m_description; + std::string m_epilog; + bool m_exit_on_default_arguments = true; + std::string m_prefix_chars{"-"}; + std::string m_assign_chars{"="}; + bool m_is_parsed = false; + std::list m_positional_arguments; + std::list m_optional_arguments; + std::map m_argument_map; + std::string m_parser_path; + std::list> m_subparsers; + std::map m_subparser_map; + std::map m_subparser_used; + std::vector m_mutually_exclusive_groups; + bool m_suppress = false; + std::size_t m_usage_max_line_width = (std::numeric_limits::max)(); + bool m_usage_break_on_mutex = false; + int m_usage_newline_counter = 0; + std::vector m_group_names; +}; + +} // namespace argparse diff --git a/include/ast.hpp b/include/ast.hpp new file mode 100644 index 0000000..8bfa67d --- /dev/null +++ b/include/ast.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/include/builtins.hpp b/include/builtins.hpp new file mode 100644 index 0000000..0226526 --- /dev/null +++ b/include/builtins.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace Fig +{ + namespace Builtins + { + const std::unordered_map builtinValues = { + {u8"null", Value::getNullInstance()}, + {u8"true", Value(true)}, + {u8"false", Value(false)}, + }; + + using BuiltinFunction = std::function &)>; + + const std::unordered_map builtinFunctionArgCounts = { + {u8"__fstdout_print", -1}, // variadic + {u8"__fstdout_println", -1}, // variadic + {u8"__fstdin_read", 0}, + {u8"__fstdin_readln", 0}, + {u8"__fvalue_type", 1}, + {u8"__fvalue_int_parse", 1}, + {u8"__fvalue_int_from", 1}, + {u8"__fvalue_double_parse", 1}, + {u8"__fvalue_double_from", 1}, + {u8"__fvalue_string_from", 1}, + }; + + const std::unordered_map builtinFunctions{ + {u8"__fstdout_print", [](const std::vector &args) -> Value { + for (auto arg : args) + { + std::print("{}", arg.toString().toBasicString()); + } + return Value(Int(args.size())); + }}, + {u8"__fstdout_println", [](const std::vector &args) -> Value { + for (auto arg : args) + { + std::print("{}", arg.toString().toBasicString()); + } + std::print("\n"); + return Value(Int(args.size())); + }}, + {u8"__fstdin_read", [](const std::vector &args) -> Value { + std::string input; + std::cin >> input; + return Value(FString::fromBasicString(input)); + }}, + {u8"__fstdin_readln", [](const std::vector &args) -> Value { + std::string line; + std::getline(std::cin, line); + return Value(FString::fromBasicString(line)); + }}, + {u8"__fvalue_type", [](const std::vector &args) -> Value { + return Value(args[0].getTypeInfo().toString()); + }}, + {u8"__fvalue_int_parse", [](const std::vector &args) -> Value { + FString str = args[0].as().getValue(); + try + { + ValueType::IntClass val = std::stoi(str.toBasicString()); + return Value(Int(val)); + } + catch (...) + { + throw RuntimeError(FStringView(std::format("Invalid int string for parsing", str.toBasicString()))); + } + }}, + {u8"__fvalue_int_from", [](const std::vector &args) -> Value { + Value val = args[0]; + if (val.is()) + { + return Value(Int(static_cast(val.as().getValue()))); + } + else if (val.is()) + { + return Value(Int(val.as().getValue() ? 1 : 0)); + } + else + { + throw RuntimeError(FStringView(std::format("Type '{}' cannot be converted to int", val.getTypeInfo().toString().toBasicString()))); + } + }}, + {u8"__fvalue_double_parse", [](const std::vector &args) -> Value { + FString str = args[0].as().getValue(); + try + { + ValueType::DoubleClass val = std::stod(str.toBasicString()); + return Value(Double(val)); + } + catch (...) + { + throw RuntimeError(FStringView(std::format("Invalid double string for parsing", str.toBasicString()))); + } + }}, + {u8"__fvalue_double_from", [](const std::vector &args) -> Value { + Value val = args[0]; + if (val.is()) + { + return Value(Double(static_cast(val.as().getValue()))); + } + else if (val.is()) + { + return Value(Double(val.as().getValue() ? 1.0 : 0.0)); + } + else + { + throw RuntimeError(FStringView(std::format("Type '{}' cannot be converted to double", val.getTypeInfo().toString().toBasicString()))); + } + }}, + {u8"__fvalue_string_from", [](const std::vector &args) -> Value { + Value val = args[0]; + return Value(val.toString()); + }}, + + }; + + inline bool isBuiltinFunction(const FString &name) + { + return builtinFunctions.find(name) != builtinFunctions.end(); + } + + inline BuiltinFunction getBuiltinFunction(const FString &name) + { + auto it = builtinFunctions.find(name); + if (it == builtinFunctions.end()) + { + throw RuntimeError(FStringView(std::format("Builtin function '{}' not found", name.toBasicString()))); + } + return it->second; + } + + inline int getBuiltinFunctionParamCount(const FString &name) + { + auto it = builtinFunctionArgCounts.find(name); + if (it == builtinFunctionArgCounts.end()) + { + throw RuntimeError(FStringView(std::format("Builtin function '{}' not found", name.toBasicString()))); + } + return it->second; + } + }; // namespace Builtins +}; // namespace Fig \ No newline at end of file diff --git a/include/context.hpp b/include/context.hpp new file mode 100644 index 0000000..e785601 --- /dev/null +++ b/include/context.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace Fig +{ + struct Context + { + private: + FString scopeName; + std::unordered_map varTypes; + std::unordered_map variables; + std::unordered_map ams; + + public: + ContextPtr parent; + + Context(const Context &) = default; + Context(const FString &name, ContextPtr p = nullptr) : + scopeName(name), parent(p) {} + Context(const FString &name, std::unordered_map types, std::unordered_map vars, std::unordered_map _ams) : + scopeName(std::move(name)), varTypes(std::move(types)), variables(std::move(vars)), ams(std::move(_ams)) {} + + void setParent(ContextPtr _parent) + { + parent = _parent; + } + + void setScopeName(FString _name) + { + scopeName = std::move(_name); + } + + FString getScopeName() const + { + return scopeName; + } + + std::optional get(const FString &name) + { + auto it = variables.find(name); + if (it != variables.end()) + return it->second; + if (parent) + return parent->get(name); + return std::nullopt; + } + AccessModifier getAccessModifier(const FString &name) + { + if (variables.contains(name)) + { + return ams[name]; + } + else if (parent != nullptr) + { + return parent->getAccessModifier(name); + } + else + { + throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString()))); + } + } + ContextPtr createCopyWithPublicVariables() + { + std::unordered_map _varTypes; + std::unordered_map _variables; + std::unordered_map _ams; + for (const auto &p : this->variables) + { + if (isVariablePublic(p.first)) + { + _variables[p.first] = p.second; + _varTypes[p.first] = varTypes[p.first]; + _ams[p.first] = ams[p.first]; + } + } + return std::make_shared(this->scopeName, _varTypes, _variables, _ams); + } + bool isVariableMutable(const FString &name) + { + AccessModifier am = getAccessModifier(name); // may throw + return am == AccessModifier::Normal or am == AccessModifier::Public; + } + bool isVariablePublic(const FString &name) + { + AccessModifier am = getAccessModifier(name); // may throw + return am == AccessModifier::Public or am == AccessModifier::PublicConst or am == AccessModifier::PublicFinal; + } + void set(const FString &name, const Value &value) + { + if (variables.contains(name)) + { + if (!isVariableMutable(name)) + { + throw RuntimeError(FStringView(std::format("Variable '{}' is immutable", name.toBasicString()))); + } + variables[name] = value; + } + else if (parent != nullptr) + { + parent->set(name, value); + } + else + { + throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString()))); + } + } + void def(const FString &name, const TypeInfo &ti, AccessModifier am, const Value &value = Any()) + { + if (variables.contains(name)) + { + throw RuntimeError(FStringView(std::format("Variable '{}' already defined in this scope", name.toBasicString()))); + } + variables[name] = value; + varTypes[name] = ti; + ams[name] = am; + } + bool contains(const FString &name) + { + if (variables.contains(name)) + { + return true; + } + else if (parent != nullptr) + { + return parent->contains(name); + } + return false; + } + TypeInfo getTypeInfo(const FString &name) + { + if (varTypes.contains(name)) + { + return varTypes[name]; + } + else if (parent != nullptr) + { + return parent->getTypeInfo(name); + } + throw RuntimeError(FStringView(std::format("Variable '{}' not defined", name.toBasicString()))); + } + void printStackTrace(std::ostream &os = std::cerr, int indent = 0) const + { + const Context *ctx = this; + std::vector chain; + + while (ctx) + { + chain.push_back(ctx); + ctx = ctx->parent.get(); + } + + os << "[STACK TRACE]\n"; + for (int i = static_cast(chain.size()) - 1; i >= 0; --i) + { + os << " #" << (chain.size() - 1 - i) + << " " << chain[i]->scopeName.toBasicString() + << "\n"; + } + } + }; +}; // namespace Fig \ No newline at end of file diff --git a/include/context_forward.hpp b/include/context_forward.hpp new file mode 100644 index 0000000..143026b --- /dev/null +++ b/include/context_forward.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace Fig +{ + struct Context; + using ContextPtr = std::shared_ptr; +}; \ No newline at end of file diff --git a/include/core.hpp b/include/core.hpp new file mode 100644 index 0000000..8cb3209 --- /dev/null +++ b/include/core.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#define __FCORE_VERSION "0.3.1" + +#if defined(_WIN32) + #define __FCORE_PLATFORM "Windows" +#elif defined(__APPLE__) + #define __FCORE_PLATFORM "Apple" +#elif defined(__linux__) + #define __FCORE_PLATFORM "Linux" +#elif defined(__unix__) + #define __FCORE_PLATFORM "Unix" +#else + #define __FCORE_PLATFORM "Unknown" +#endif + +#if defined(__GNUC__) + #if defined(_WIN32) + #if defined(__clang__) + #define __FCORE_COMPILER "llvm-mingw" + #else + #define __FCORE_COMPILER "MinGW" + #endif + + #else + #define __FCORE_COMPILER "GCC" + #endif +#elif defined(__clang__) + #define __FCORE_COMPILER "Clang" +#elif defined(_MSC_VER) + #define __FCORE_COMPILER "MSVC" +#else + #define __FCORE_COMPILER "Unknown" +#endif + +#if SIZE_MAX == 18446744073709551615ull + #define __FCORE_ARCH "64" +#else + #define __FCORE_ARCH "86" +#endif + +namespace Fig +{ + namespace Core + { + inline constexpr std::string_view VERSION = __FCORE_VERSION; + inline constexpr std::string_view LICENSE = "MIT"; + inline constexpr std::string_view AUTHOR = "PuqiAR"; + inline constexpr std::string_view PLATFORM = __FCORE_PLATFORM; + inline constexpr std::string_view COMPILER = __FCORE_COMPILER; + inline constexpr std::string_view COMPILE_TIME = __FCORE_COMPILE_TIME; + inline constexpr std::string_view ARCH = __FCORE_ARCH; + inline constexpr FString MAIN_FUNCTION = u8"main"; + }; // namespace Core +}; // namespace Fig \ No newline at end of file diff --git a/include/error.hpp b/include/error.hpp new file mode 100644 index 0000000..2471b30 --- /dev/null +++ b/include/error.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Fig +{ + class AddressableError : public std::exception + { + public: + explicit AddressableError() {} + explicit AddressableError(FStringView _msg, + size_t _line, + size_t _column, + std::source_location loc = std::source_location::current()) : + src_loc(loc), line(_line), column(_column) + { + message = _msg; + } + virtual FString toString() const + { + std::string msg = std::format("[AddressableError] {} at {}:{}, in [{}] {}", std::string(this->message.begin(), this->message.end()), this->line, this->column, this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + const char *what() const noexcept override + { + static std::string msg = toString().toBasicString(); + return msg.c_str(); + } + std::source_location src_loc; + + size_t getLine() const { return line; } + size_t getColumn() const { return column; } + FStringView getMessage() const { return message; } + + virtual FString getErrorType() const + { + return FString(u8"AddressableError"); + } + + protected: + size_t line, column; + FStringView message; + }; + + class UnaddressableError : public std::exception + { + public: + explicit UnaddressableError() {} + explicit UnaddressableError(FStringView _msg, + std::source_location loc = std::source_location::current()) : + src_loc(loc) + { + message = _msg; + } + virtual FString toString() const + { + std::string msg = std::format("[UnaddressableError] {} in [{}] {}", std::string(this->message.begin(), this->message.end()), this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + const char *what() const noexcept override + { + static std::string msg = toString().toBasicString(); + return msg.c_str(); + } + std::source_location src_loc; + FStringView getMessage() const { return message; } + + virtual FString getErrorType() const + { + return FString(u8"UnaddressableError"); + } + + protected: + FStringView message; + }; + + class SyntaxError : public AddressableError + { + public: + using AddressableError::AddressableError; + + explicit SyntaxError(FStringView _msg, + size_t _line, + size_t _column, + std::source_location loc = std::source_location::current()) : + AddressableError(_msg, _line, _column, loc) + { + } + + virtual FString toString() const override + { + std::string msg = std::format("[SyntaxError] {} in [{}] {}", std::string(this->message.begin(), this->message.end()), this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + + virtual FString getErrorType() const override + { + return FString(u8"SyntaxError"); + } + }; + + class RuntimeError final : public UnaddressableError + { + public: + using UnaddressableError::UnaddressableError; + explicit RuntimeError(FStringView _msg, + std::source_location loc = std::source_location::current()) : + UnaddressableError(_msg, loc) + { + } + virtual FString toString() const override + { + std::string msg = std::format("[RuntimeError] {} in [{}] {}", std::string(this->message.begin(), this->message.end()), this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + + virtual FString getErrorType() const override + { + return FString(u8"RuntimeError"); + } + }; + +} // namespace Fig \ No newline at end of file diff --git a/include/errorLog.hpp b/include/errorLog.hpp new file mode 100644 index 0000000..355c648 --- /dev/null +++ b/include/errorLog.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +#include +#include + + +namespace Fig +{ + namespace ErrorLog + { + namespace TerminalColors + { + constexpr const char *Reset = "\033[0m"; + constexpr const char *Bold = "\033[1m"; + constexpr const char *Dim = "\033[2m"; + constexpr const char *Italic = "\033[3m"; + constexpr const char *Underline = "\033[4m"; + constexpr const char *Blink = "\033[5m"; + constexpr const char *Reverse = "\033[7m"; // 前背景反色 + constexpr const char *Hidden = "\033[8m"; // 隐藏文本 + constexpr const char *Strike = "\033[9m"; // 删除线 + + constexpr const char *Black = "\033[30m"; + constexpr const char *Red = "\033[31m"; + constexpr const char *Green = "\033[32m"; + constexpr const char *Yellow = "\033[33m"; + constexpr const char *Blue = "\033[34m"; + constexpr const char *Magenta = "\033[35m"; + constexpr const char *Cyan = "\033[36m"; + constexpr const char *White = "\033[37m"; + + constexpr const char *LightBlack = "\033[90m"; + constexpr const char *LightRed = "\033[91m"; + constexpr const char *LightGreen = "\033[92m"; + constexpr const char *LightYellow = "\033[93m"; + constexpr const char *LightBlue = "\033[94m"; + constexpr const char *LightMagenta = "\033[95m"; + constexpr const char *LightCyan = "\033[96m"; + constexpr const char *LightWhite = "\033[97m"; + + constexpr const char *DarkRed = "\033[38;2;128;0;0m"; + constexpr const char *DarkGreen = "\033[38;2;0;100;0m"; + constexpr const char *DarkYellow = "\033[38;2;128;128;0m"; + constexpr const char *DarkBlue = "\033[38;2;0;0;128m"; + constexpr const char *DarkMagenta = "\033[38;2;100;0;100m"; + constexpr const char *DarkCyan = "\033[38;2;0;128;128m"; + constexpr const char *DarkGray = "\033[38;2;64;64;64m"; + constexpr const char *Gray = "\033[38;2;128;128;128m"; + constexpr const char *Silver = "\033[38;2;192;192;192m"; + + constexpr const char *Navy = "\033[38;2;0;0;128m"; + constexpr const char *RoyalBlue = "\033[38;2;65;105;225m"; + constexpr const char *ForestGreen = "\033[38;2;34;139;34m"; + constexpr const char *Olive = "\033[38;2;128;128;0m"; + constexpr const char *Teal = "\033[38;2;0;128;128m"; + constexpr const char *Maroon = "\033[38;2;128;0;0m"; + constexpr const char *Purple = "\033[38;2;128;0;128m"; + constexpr const char *Orange = "\033[38;2;255;165;0m"; + constexpr const char *Gold = "\033[38;2;255;215;0m"; + constexpr const char *Pink = "\033[38;2;255;192;203m"; + constexpr const char *Crimson = "\033[38;2;220;20;60m"; + + constexpr const char *OnBlack = "\033[40m"; + constexpr const char *OnRed = "\033[41m"; + constexpr const char *OnGreen = "\033[42m"; + constexpr const char *OnYellow = "\033[43m"; + constexpr const char *OnBlue = "\033[44m"; + constexpr const char *OnMagenta = "\033[45m"; + constexpr const char *OnCyan = "\033[46m"; + constexpr const char *OnWhite = "\033[47m"; + + constexpr const char *OnLightBlack = "\033[100m"; + constexpr const char *OnLightRed = "\033[101m"; + constexpr const char *OnLightGreen = "\033[102m"; + constexpr const char *OnLightYellow = "\033[103m"; + constexpr const char *OnLightBlue = "\033[104m"; + constexpr const char *OnLightMagenta = "\033[105m"; + constexpr const char *OnLightCyan = "\033[106m"; + constexpr const char *OnLightWhite = "\033[107m"; + + constexpr const char *OnDarkBlue = "\033[48;2;0;0;128m"; + constexpr const char *OnGreenYellow = "\033[48;2;173;255;47m"; + constexpr const char *OnOrange = "\033[48;2;255;165;0m"; + constexpr const char *OnGray = "\033[48;2;128;128;128m"; + }; // namespace TerminalColors + + inline void coloredPrint(const char *colorCode, FString msg) + { + std::print("{}{}{}", colorCode, msg.toBasicString(), TerminalColors::Reset); + } + + inline void coloredPrint(const char *colorCode, std::string msg) + { + std::print("{}{}{}", colorCode, msg, TerminalColors::Reset); + } + + + inline void logAddressableError(const AddressableError &err, FString fileName, std::vector sourceLines) + { + std::print("\n"); + namespace TC = TerminalColors; + coloredPrint(TC::LightWhite, "An error occurred! "); + coloredPrint(TC::White, std::format("Fig {} ({})[{} {} bit on `{}`]\n",Core::VERSION, Core::COMPILE_TIME, Core::COMPILER, Core::ARCH, Core::PLATFORM)); + coloredPrint(TC::LightRed, "✖ "); + coloredPrint(TC::LightRed, std::format("{}: {}\n", err.getErrorType().toBasicString(), FString(err.getMessage()).toBasicString())); + coloredPrint(TC::White, std::format(" at {}:{} in file '{}'\n", err.getLine(), err.getColumn(), fileName.toBasicString())); + FString lineContent = ((int64_t(err.getLine()) - int64_t(1)) >= 0 ? sourceLines[err.getLine() - 1] : u8""); + coloredPrint(TC::LightBlue, std::format(" {}\n", lineContent.toBasicString())); + FString pointerLine; + for (size_t i = 1; i < err.getColumn(); ++i) + { + if (lineContent[i - 1] == U'\t') + { + pointerLine += U'\t'; + } + else + { + pointerLine += U' '; + } + } + pointerLine += U'^'; + coloredPrint(TC::LightGreen, std::format(" {}\n", pointerLine.toBasicString())); + coloredPrint(TC::DarkGray, std::format("🔧 in function '{}' ({}:{})\n", err.src_loc.function_name(), err.src_loc.file_name(), err.src_loc.line())); + } + + inline void logUnaddressableError(const UnaddressableError &err) + { + std::print("\n"); + namespace TC = TerminalColors; + coloredPrint(TC::LightWhite, "An error occurred! "); + coloredPrint(TC::White, std::format("Fig {} ({})[{} {} bit on `{}`]\n", Core::VERSION, Core::COMPILE_TIME, Core::COMPILER, Core::ARCH, Core::PLATFORM)); + coloredPrint(TC::DarkRed, "✖"); + coloredPrint(TC::Red, std::format("{}: {}\n", err.getErrorType().toBasicString(), FString(err.getMessage()).toBasicString())); + coloredPrint(TC::DarkGray, std::format("🔧 in function '{}' ({}:{})", err.src_loc.function_name(), err.src_loc.file_name(), err.src_loc.line())); + } + }; // namespace ErrorLog +}; // namespace Fig \ No newline at end of file diff --git a/include/evaluator.hpp b/include/evaluator.hpp new file mode 100644 index 0000000..9c6a5f1 --- /dev/null +++ b/include/evaluator.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Fig +{ + + template + class EvaluatorError final : public AddressableError + { + public: + virtual FString toString() const override + { + std::string msg = std::format("[Eve: {}] {} in [{}] {}", errName, std::string(this->message.begin(), this->message.end()), this->src_loc.file_name(), this->src_loc.function_name()); + return FString(msg); + } + using AddressableError::AddressableError; + explicit EvaluatorError(FStringView _msg, + Ast::AstAddressInfo aai, + std::source_location loc = std::source_location::current()) : + AddressableError(_msg, aai.line, aai.column, loc) + { + } + }; + struct StatementResult + { + Value result; + enum class Flow + { + Normal, + Return, + Break, + Continue + } flow; + + StatementResult(Value val, Flow f = Flow::Normal) : + result(val), flow(f) + { + } + + static StatementResult normal(Value val = Value::getNullInstance()) + { + return StatementResult(val, Flow::Normal); + } + static StatementResult returnFlow(Value val) + { + return StatementResult(val, Flow::Return); + } + static StatementResult breakFlow() + { + return StatementResult(Value::getNullInstance(), Flow::Break); + } + static StatementResult continueFlow() + { + return StatementResult(Value::getNullInstance(), Flow::Continue); + } + + bool isNormal() const { return flow == Flow::Normal; } + bool shouldReturn() const { return flow == Flow::Return; } + bool shouldBreak() const { return flow == Flow::Break; } + bool shouldContinue() const { return flow == Flow::Continue; } + }; + + class Evaluator + { + private: + std::vector asts; + std::shared_ptr globalContext; + std::shared_ptr currentContext; + + Ast::AstAddressInfo currentAddressInfo; + + public: + Evaluator(const std::vector &a) : + asts(a) + { + globalContext = std::make_shared(FString(u8"global")); + currentContext = globalContext; + } + + std::shared_ptr getCurrentContext() { return currentContext; } + std::shared_ptr getGlobalContext() { return globalContext; } + + Value __evalOp(Ast::Operator, const Value &, const Value & = Value::getNullInstance()); + Value evalBinary(const Ast::BinaryExpr &); + Value evalUnary(const Ast::UnaryExpr &); + + StatementResult evalStatement(const Ast::Statement &); + + Value eval(Ast::Expression); + void run(); + void printStackTrace() const; + }; + +} // namespace Fig diff --git a/include/fassert.hpp b/include/fassert.hpp new file mode 100644 index 0000000..376d4f9 --- /dev/null +++ b/include/fassert.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define fassert(exp,msg) (if(!exp) throw msg;) \ No newline at end of file diff --git a/include/fig_string.hpp b/include/fig_string.hpp new file mode 100644 index 0000000..4084e29 --- /dev/null +++ b/include/fig_string.hpp @@ -0,0 +1,100 @@ +#pragma once +#include +#include + + +namespace Fig +{ + // using String = std::u8string; + // using StringView = std::u8string_view; + + class FStringView : public std::u8string_view + { + public: + using std::u8string_view::u8string_view; + + static FStringView fromBasicStringView(std::string_view sv) + { + return FStringView(reinterpret_cast(sv.data()), sv.size()); + } + + explicit FStringView(std::string_view sv) + { + *this = fromBasicStringView(sv); + } + + explicit FStringView() + { + *this = fromBasicStringView(std::string_view("")); + } + + }; + + class FString : public std::u8string + { + public: + using std::u8string::u8string; + explicit FString(const std::u8string &str) + { + *this = fromU8String(str); + } + explicit FString(std::string str) + { + *this = fromBasicString(str); + } + explicit FString(FStringView sv) + { + *this = fromStringView(sv); + } + std::string toBasicString() const + { + return std::string(this->begin(), this->end()); + } + FStringView toStringView() const + { + return FStringView(this->data(), this->size()); + } + + static FString fromBasicString(const std::string &str) + { + return FString(str.begin(), str.end()); + } + + static FString fromStringView(FStringView sv) + { + return FString(reinterpret_cast (sv.data())); + } + + static FString fromU8String(const std::u8string &str) + { + return FString(str.begin(), str.end()); + } + + size_t length() + { + // get UTF8-String real length + size_t len = 0; + for (auto it = this->begin(); it != this->end(); ++it) + { + if ((*it & 0xC0) != 0x80) + { + ++len; + } + } + return len; + } + }; + +}; // namespace Fig + +namespace std +{ + template <> + struct hash + { + std::size_t operator()(const Fig::FString &s) const + { + return std::hash{}(static_cast(s)); + } + }; +} \ No newline at end of file diff --git a/include/lexer.hpp b/include/lexer.hpp new file mode 100644 index 0000000..a1f7d1e --- /dev/null +++ b/include/lexer.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Fig +{ + + class Lexer final + { + private: + size_t line; + const FString source; + SyntaxError error; + UTF8Iterator it; + + std::vector warnings; + + size_t last_line, last_column, column = 1; + + bool hasNext() + { + return !this->it.isEnd(); + } + + void skipLine(); + inline void next() + { + if (*it == U'\n') + { + ++this->line; + this->column = 1; + } + else + { + ++this->column; + } + ++it; + } + + void pushWarning(size_t id, FString msg) + { + warnings.push_back(Warning(id, std::move(msg), getCurrentLine(), getCurrentColumn())); + } + void pushWarning(size_t id, FString msg, size_t line, size_t column) + { + warnings.push_back(Warning(id, std::move(msg), line, column)); + } + + public: + static const std::unordered_map symbol_map; + static const std::unordered_map keyword_map; + + inline Lexer(const FString &_source) : + source(_source), it(source) + { + line = 1; + } + inline size_t getCurrentLine() + { + return line; + } + inline size_t getCurrentColumn() + { + return column; + } + SyntaxError getError() const + { + return error; + } + std::vector getWarnings() const + { + return warnings; + } + + Token nextToken(); + + Token scanNumber(); + Token scanString(); + Token scanRawString(); + Token scanMultilineString(); + Token scanIdentifier(); + Token scanSymbol(); + Token scanComments(); + }; +} // namespace Fig \ No newline at end of file diff --git a/include/magic_enum/magic_enum.hpp b/include/magic_enum/magic_enum.hpp new file mode 100644 index 0000000..158207e --- /dev/null +++ b/include/magic_enum/magic_enum.hpp @@ -0,0 +1,1508 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 9 +#define MAGIC_ENUM_VERSION_PATCH 7 + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(MAGIC_ENUM_CONFIG_FILE) +# include MAGIC_ENUM_CONFIG_FILE +#endif + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +# include +#endif +#endif + +#if defined(MAGIC_ENUM_NO_ASSERT) +# define MAGIC_ENUM_ASSERT(...) static_cast(0) +#elif !defined(MAGIC_ENUM_ASSERT) +# include +# define MAGIC_ENUM_ASSERT(...) assert((__VA_ARGS__)) +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +# pragma clang diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +# pragma GCC diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 26495) // Variable 'static_str::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 || defined(__RESHARPER__) +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 127. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 127 +#endif + +// Improve ReSharper C++ intellisense performance with builtins, avoiding unnecessary template instantiations. +#if defined(__RESHARPER__) +# undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +# undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +# if __RESHARPER__ >= 20230100 +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) __rscpp_enumerator_name(V) +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) __rscpp_type_name() +# else +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) nullptr +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) nullptr +# endif +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +} (), "magic_enum::customize wchar_t is not compatible with ASCII."); + +namespace customize { + +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 127. +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. +// If need another range for specific enum type, add specialization enum_range for necessary enum type. +template +struct enum_range { + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; +}; + +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); + +namespace detail { + +enum class customize_tag { + default_tag, + invalid_tag, + custom_tag +}; + +} // namespace magic_enum::customize::detail + +class customize_t : public std::pair { + public: + constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { + MAGIC_ENUM_ASSERT(tag != detail::customize_tag::custom_tag); + } +}; + +// Default customize. +inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; +// Invalid customize. +inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} + +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; +} + +} // namespace magic_enum::customize + +namespace detail { + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template , std::enable_if_t, int> = 0> +using enum_constant = std::integral_constant; + +template +inline constexpr bool always_false_v = false; + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +struct str_view { + const char* str_ = nullptr; + std::size_t size_ = 0; +}; + +template +class static_str { + public: + constexpr explicit static_str(str_view str) noexcept : static_str{str.str_, std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size_ == N); + } + + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size() == N); + } + + constexpr const char_type* data() const noexcept { return chars_; } + + constexpr std::uint16_t size() const noexcept { return N; } + + constexpr operator string_view() const noexcept { return {data(), size()}; } + + private: + template + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{static_cast(str[I])..., static_cast('\0')} {} + + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., static_cast('\0')} {} + + char_type chars_[static_cast(N) + 1]; +}; + +template <> +class static_str<0> { + public: + constexpr explicit static_str() = default; + + constexpr explicit static_str(str_view) noexcept {} + + constexpr explicit static_str(string_view) noexcept {} + + constexpr const char_type* data() const noexcept { return nullptr; } + + constexpr std::uint16_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } +}; + +template > +class case_insensitive { + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= static_cast('A') && c <= static_cast('Z')) ? static_cast(c + (static_cast('a') - static_cast('A'))) : c; + } + + public: + template + constexpr auto operator()(L lhs, R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { + return Op{}(to_lower(lhs), to_lower(rhs)); + } +}; + +constexpr std::size_t find(string_view str, char_type c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find(c); + } +} + +template +constexpr bool is_default_predicate() noexcept { + return std::is_same_v, std::equal_to> || + std::is_same_v, std::equal_to<>>; +} + +template +constexpr bool is_nothrow_invocable() { + return is_default_predicate() || + std::is_nothrow_invocable_r_v; +} + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (!is_default_predicate() || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } +} + +template +constexpr I log2(I value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + if constexpr (std::is_same_v) { // bool special case + return MAGIC_ENUM_ASSERT(false), value; + } else { + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} + + return ret; + } +} + +#if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define MAGIC_ENUM_ARRAY_CONSTEXPR 1 +#else +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[I]...}}; +} +#endif + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_TYPE_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(E); + constexpr auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 50; + name.str_ += 49; + } else { + name.size_ -= 40; + name.str_ += 37; + } +#elif defined(_MSC_VER) + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.str_ += 40; + name.size_ += sizeof(__FUNCSIG__) - 57; +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +template +constexpr auto type_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto name = n(); + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto type_name_v = type_name(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } + if (name.size_ > 22 && name.str_[0] == '(' && name.str_[1] == 'a' && name.str_[10] == ' ' && name.str_[22] == ':') { + name.size_ -= 23; + name.str_ += 23; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 55; + name.str_ += 54; + } else { + name.size_ -= 40; + name.str_ += 37; + } + if (name.str_[0] == '(') { + name = str_view{}; + } +#elif defined(_MSC_VER) + str_view name; + if ((__FUNCSIG__[5] == '_' && __FUNCSIG__[35] != '(') || (__FUNCSIG__[5] == 'c' && __FUNCSIG__[41] != '(')) { + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + name.str_ = __FUNCSIG__; + name.str_ += 35; + name.size_ = sizeof(__FUNCSIG__) - 52; + } +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 +# define MAGIC_ENUM_VS_2017_WORKAROUND 1 +#endif + +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + +# if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +# else + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.size_ = sizeof(__FUNCSIG__) - 17; + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ',' || name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } + return name; +# endif +} +#endif + +template +constexpr auto enum_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + constexpr auto name = n(); +#else + constexpr auto name = n(); +#endif + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto enum_name_v = enum_name(); + +template +constexpr bool is_valid() noexcept { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + constexpr E v = __builtin_bit_cast(E, V); +#else + constexpr E v = static_cast(V); +#endif + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + return n().size_ != 0; +#else + return n().size_ != 0; +#endif + } else { + return false; + } +} + +enum class enum_subtype { + common, + flags +}; + +template > +constexpr U ualue(std::size_t i) noexcept { + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::ualue requires valid offset."); + + return static_cast(i); + } else if constexpr (S == enum_subtype::flags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr E value(std::size_t i) noexcept { + return static_cast(ualue(i)); +} + +template > +constexpr int reflected_min() noexcept { + if constexpr (S == enum_subtype::flags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + if constexpr (S == enum_subtype::flags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +#define MAGIC_ENUM_FOR_EACH_256(T) \ + T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +template +constexpr void valid_count(bool* valid, std::size_t& count) noexcept { +#define MAGIC_ENUM_V(O) \ + if constexpr ((I + O) < Size) { \ + if constexpr (is_valid(I + O)>()) { \ + valid[I + O] = true; \ + ++count; \ + } \ + } + + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_V) + + if constexpr ((I + 256) < Size) { + valid_count(valid, count); + } +#undef MAGIC_ENUM_V +} + +template +struct valid_count_t { + std::size_t count = 0; + bool valid[N] = {}; +}; + +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc.valid, vc.count); + return vc; +} + +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); + + if constexpr (vc.count > 0) { +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + for (std::size_t i = 0, v = 0; v < vc.count; ++i) { + if (vc.valid[i]) { + values[v++] = value(i); + } + } +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); + + return values(); +} + +template > +constexpr enum_subtype subtype(std::true_type) noexcept { + if constexpr (std::is_same_v) { // bool special case + return enum_subtype::common; + } else if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags ? enum_subtype::flags : enum_subtype::common; + } else { +#if defined(MAGIC_ENUM_AUTO_IS_FLAGS) + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return enum_subtype::common; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return enum_subtype::common; + } + } + return enum_subtype::flags; +#else + return enum_subtype::common; +#endif + } +} + +template +constexpr enum_subtype subtype(std::false_type) noexcept { + // For non-enum type return default common subtype. + return enum_subtype::common; +} + +template > +inline constexpr auto subtype_v = subtype(std::is_enum{}); + +template +inline constexpr auto values_v = values(); + +template > +using values_t = decltype((values_v)); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + constexpr auto names = std::array{{enum_name_v[I]>...}}; + return names; +} + +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + +template > +using names_t = decltype((names_v)); + +template +constexpr auto entries(std::index_sequence) noexcept { + constexpr auto entries = std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + return entries; +} + +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { + constexpr auto max = (S == enum_subtype::flags) ? log2(max_v) : max_v; + constexpr auto min = (S == enum_subtype::flags) ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } +} + +template > +inline constexpr bool is_sparse_v = is_sparse(); + +template +struct is_reflected +#if defined(MAGIC_ENUM_NO_CHECK_REFLECTED_ENUM) + : std::true_type {}; +#else + : std::bool_constant && (count_v != 0)> {}; +#endif + +template +inline constexpr bool is_reflected_v = is_reflected, S>{}; + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template , typename D = std::decay_t> +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; + +template >, int> = 0> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + constexpr auto operator()(Value value) const noexcept { + using U = typename underlying_type::type; + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return static_cast(value); + } + } + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto acc = static_cast(2166136261ULL); + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +inline constexpr Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + // dead cases, try to avoid case collisions + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { + } + + { + auto it = result.begin(); + auto last_value = (std::numeric_limits::min)(); + for (; fill != result.end(); *fill++ = last_value++) { + while (last_value == *it) { + ++last_value, ++it; + } + } + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, + value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr bool has_duplicate() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + std::arraysize()> hashes{}; + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + return true; +} + +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } \ + break; \ + } else [[fallthrough]]; + +template ::value_type>, + typename BinaryPredicate = std::equal_to<>, + typename Lambda, + typename ResultGetterType> +constexpr decltype(auto) constexpr_switch( + Lambda&& lambda, + typename std::decay_t::value_type searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using result_t = std::invoke_result_t; + using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; + static_assert(has_duplicate(), "magic_enum::detail::constexpr_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr std::array cases = calculate_cases(Page); + + switch (hash_v(searched)) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return constexpr_switch(std::forward(lambda), searched, std::forward(def)); + } + break; + } + return def(); +} + +#undef MAGIC_ENUM_CASE + +#endif + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Checks whether T is an Unscoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Checks whether T is an Scoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +template +using enum_constant = detail::enum_constant; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>; + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template > +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v, S>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template > +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v) { + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::values_v[index]; + } else { + constexpr auto min = (S == detail::enum_subtype::flags) ? detail::log2(detail::min_v) : detail::min_v; + + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } +} + +// Returns enum value at specified index. +template > +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); + + return enum_value(I); +} + +// Returns std::array with enum values, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::values_v; +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Returns underlying value from enum value. +template +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template > +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + return {}; // Invalid value or out of range. +#endif + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_index(value); +} + +// Obtains index in enum values from static storage enum variable. +template >> +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t {\ + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto index = enum_index(V); + static_assert(index, "magic_enum::enum_index enum value does not have a index."); + + return *index; +} + +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template > +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; + } + return {}; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_name(value); +} + +// Returns std::array with names, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::names_v; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::entries_v; +} + +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive<>{}; + +// Obtains enum value from integer value. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + [](D v) { return optional{v}; }, + static_cast(value), + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (value == static_cast>(enum_value(i))) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. +#endif + } else { + if (value >= detail::min_v && value <= detail::max_v) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum value from name. +// Returns optional with enum value. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) + if constexpr (detail::is_default_predicate()) { + return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); + } +#endif + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. +} + +// Checks whether enum contains value with such value. +template > +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such integer value. +template > +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value)); +} + +// Checks whether enum contains enumerator with such name. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value, std::move(p))); +} + +// Returns true if the enum integer value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + if constexpr (detail::is_reflected_v) { + constexpr auto min = detail::reflected_min(); + constexpr auto max = detail::reflected_max(); + return value >= min && value <= max; + } else { + return false; + } +} + +// Returns true if the enum value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(static_cast>(value)); +} + +// Returns true if the enum value is in the range of values that can be reflected. +template +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(value); +} + +template +inline constexpr auto as_flags = AsFlags ? detail::enum_subtype::flags : detail::enum_subtype::common; + +template +inline constexpr auto as_common = AsFlags ? detail::enum_subtype::common : detail::enum_subtype::flags; + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +#undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +#undef MAGIC_ENUM_VS_2017_WORKAROUND +#undef MAGIC_ENUM_ARRAY_CONSTEXPR +#undef MAGIC_ENUM_FOR_EACH_256 + +#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/include/magic_enum/magic_enum_all.hpp b/include/magic_enum/magic_enum_all.hpp new file mode 100644 index 0000000..636f028 --- /dev/null +++ b/include/magic_enum/magic_enum_all.hpp @@ -0,0 +1,44 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_ALL_HPP +#define NEARGYE_MAGIC_ENUM_ALL_HPP + +#include "magic_enum.hpp" +#include "magic_enum_containers.hpp" +#include "magic_enum_flags.hpp" +#include "magic_enum_format.hpp" +#include "magic_enum_fuse.hpp" +#include "magic_enum_iostream.hpp" +#include "magic_enum_switch.hpp" +#include "magic_enum_utility.hpp" + +#endif // NEARGYE_MAGIC_ENUM_ALL_HPP diff --git a/include/magic_enum/magic_enum_containers.hpp b/include/magic_enum/magic_enum_containers.hpp new file mode 100644 index 0000000..f817306 --- /dev/null +++ b/include/magic_enum/magic_enum_containers.hpp @@ -0,0 +1,1174 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// Copyright (c) 2022 - 2023 Bela Schaum . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_CONTAINERS_HPP +#define NEARGYE_MAGIC_ENUM_CONTAINERS_HPP + +#include "magic_enum.hpp" + +#if !defined(MAGIC_ENUM_NO_EXCEPTION) && (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) throw (__VA_ARGS__) +#else +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) std::abort() +#endif + +namespace magic_enum::containers { + +namespace detail { + +template +static constexpr bool is_transparent_v{}; + +template +static constexpr bool is_transparent_v>{true}; + +template , typename T1, typename T2> +constexpr bool equal(T1&& t1, T2&& t2, Eq&& eq = {}) { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + for (; first1 != last1; ++first1, ++first2) { + if (first2 == last2 || !eq(*first1, *first2)) { + return false; + } + } + return first2 == last2; +} + +template , typename T1, typename T2> +constexpr bool lexicographical_compare(T1&& t1, T2&& t2, Cmp&& cmp = {}) noexcept { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + // copied from std::lexicographical_compare + for (; (first1 != last1) && (first2 != last2); ++first1, (void)++first2) { + if (cmp(*first1, *first2)) { + return true; + } + if (cmp(*first2, *first1)) { + return false; + } + } + return (first1 == last1) && (first2 != last2); +} + +template +constexpr std::size_t popcount(T x) noexcept { + std::size_t c = 0; + while (x > 0) { + c += x & 1; + x >>= 1; + } + return c; +} + +template , typename ForwardIt, typename E> +constexpr ForwardIt lower_bound(ForwardIt first, ForwardIt last, E&& e, Cmp&& comp = {}) { + auto count = std::distance(first, last); + for (auto it = first; count > 0;) { + auto step = count / 2; + std::advance(it, step); + if (comp(*it, e)) { + first = ++it; + count -= step + 1; + } else { + count = step; + } + } + return first; +} + +template , typename BidirIt, typename E> +constexpr auto equal_range(BidirIt begin, BidirIt end, E&& e, Cmp&& comp = {}) { + const auto first = lower_bound(begin, end, e, comp); + return std::pair{first, lower_bound(std::make_reverse_iterator(end), std::make_reverse_iterator(first), e, [&comp](auto&& lhs, auto&& rhs) { return comp(rhs, lhs); }).base()}; +} + +template , typename = void> +class indexing { + [[nodiscard]] static constexpr auto get_indices() noexcept { + // reverse result index mapping + std::array()> rev_res{}; + + // std::iota + for (std::size_t i = 0; i < enum_count(); ++i) { + rev_res[i] = i; + } + + constexpr auto orig_values = enum_values(); + constexpr Cmp cmp{}; + + // ~std::sort + for (std::size_t i = 0; i < enum_count(); ++i) { + for (std::size_t j = i + 1; j < enum_count(); ++j) { + if (cmp(orig_values[rev_res[j]], orig_values[rev_res[i]])) { + auto tmp = rev_res[i]; + rev_res[i] = rev_res[j]; + rev_res[j] = tmp; + } + } + } + + std::array()> sorted_values{}; + // reverse the sorted indices + std::array()> res{}; + for (std::size_t i = 0; i < enum_count(); ++i) { + res[rev_res[i]] = i; + sorted_values[i] = orig_values[rev_res[i]]; + } + + return std::pair{sorted_values, res}; + } + + static constexpr auto indices = get_indices(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return indices.first.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return indices.first.data() + indices.first.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return indices.first.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { + if (auto i = enum_index(val)) { + return indices.second[*i]; + } + return {}; + } +}; + +template +class indexing> && (std::is_same_v> || std::is_same_v>)>> { + static constexpr auto& values = enum_values(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return values.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return values.data() + values.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return values.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { return enum_index(val); } +}; + +template +struct indexing { + using is_transparent = std::true_type; + + template + [[nodiscard]] static constexpr optional at(E val) noexcept { + return indexing::at(val); + } +}; + +template , typename = void> +struct name_sort_impl { + [[nodiscard]] constexpr bool operator()(E e1, E e2) const noexcept { return Cmp{}(enum_name(e1), enum_name(e2)); } +}; + +template +struct name_sort_impl { + using is_transparent = std::true_type; + + template + struct FullCmp : C {}; + + template + struct FullCmp && std::is_invocable_v>> { + [[nodiscard]] constexpr bool operator()(string_view s1, string_view s2) const noexcept { return lexicographical_compare(s1, s2); } + }; + + template + [[nodiscard]] constexpr std::enable_if_t< + // at least one of need to be an enum type + (std::is_enum_v> || std::is_enum_v>) && + // if both is enum, only accept if the same enum + (!std::is_enum_v> || !std::is_enum_v> || std::is_same_v) && + // is invocable with comparator + (std::is_invocable_r_v, std::conditional_t>, string_view, E1>, std::conditional_t>, string_view, E2>>), + bool> + operator()(E1 e1, E2 e2) const noexcept { + using D1 = std::decay_t; + using D2 = std::decay_t; + constexpr FullCmp<> cmp{}; + + if constexpr (std::is_enum_v && std::is_enum_v) { + return cmp(enum_name(e1), enum_name(e2)); + } else if constexpr (std::is_enum_v) { + return cmp(enum_name(e1), e2); + } else /* if constexpr (std::is_enum_v) */ { + return cmp(e1, enum_name(e2)); + } + } +}; + +struct raw_access_t {}; + +template +struct FilteredIterator { + Parent parent; + Iterator first; + Iterator last; + Iterator current; + Getter getter; + Predicate predicate; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = std::remove_reference_t>; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + constexpr FilteredIterator() noexcept = default; + constexpr FilteredIterator(const FilteredIterator&) = default; + constexpr FilteredIterator& operator=(const FilteredIterator&) = default; + constexpr FilteredIterator(FilteredIterator&&) noexcept = default; + constexpr FilteredIterator& operator=(FilteredIterator&&) noexcept = default; + + template && std::is_convertible_v>*> + constexpr explicit FilteredIterator(const FilteredIterator& other) + : parent(other.parent), first(other.first), last(other.last), current(other.current), getter(other.getter), predicate(other.predicate) {} + + constexpr FilteredIterator(Parent p, Iterator begin, Iterator end, Iterator curr, Getter get = {}, Predicate pred = {}) + : parent(p), first(std::move(begin)), last(std::move(end)), current(std::move(curr)), getter{std::move(get)}, predicate{std::move(pred)} { + if (current == first && !predicate(parent, current)) { + ++*this; + } + } + + [[nodiscard]] constexpr reference operator*() const { return getter(parent, current); } + + [[nodiscard]] constexpr pointer operator->() const { return std::addressof(**this); } + + constexpr FilteredIterator& operator++() { + do { + ++current; + } while (current != last && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator++(int) { + FilteredIterator cp = *this; + ++*this; + return cp; + } + + constexpr FilteredIterator& operator--() { + do { + --current; + } while (current != first && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator--(int) { + FilteredIterator cp = *this; + --*this; + return cp; + } + + [[nodiscard]] friend constexpr bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current == rhs.current; } + + [[nodiscard]] friend constexpr bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current != rhs.current; } +}; + +} // namespace detail + +template +using name_less = detail::name_sort_impl; + +template +using name_greater = detail::name_sort_impl>; + +using name_less_case_insensitive = detail::name_sort_impl>>; + +using name_greater_case_insensitive = detail::name_sort_impl>>; + +template +using default_indexing = detail::indexing; + +template > +using comparator_indexing = detail::indexing; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ARRAY // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +struct array { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using index_type = Index; + using container_type = std::array()>; + + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = typename container_type::reverse_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + + constexpr reference at(E pos) { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at Unrecognized position")); + } + + constexpr const_reference at(E pos) const { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at: Unrecognized position")); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr const_reference operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr reference front() noexcept { return a.front(); } + + [[nodiscard]] constexpr const_reference front() const noexcept { return a.front(); } + + [[nodiscard]] constexpr reference back() noexcept { return a.back(); } + + [[nodiscard]] constexpr const_reference back() const noexcept { return a.back(); } + + [[nodiscard]] constexpr pointer data() noexcept { return a.data(); } + + [[nodiscard]] constexpr const_pointer data() const noexcept { return a.data(); } + + [[nodiscard]] constexpr iterator begin() noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return a.cbegin(); } + + [[nodiscard]] constexpr iterator end() noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return a.cend(); } + + [[nodiscard]] constexpr iterator rbegin() noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator rbegin() const noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator crbegin() const noexcept { return a.crbegin(); } + + [[nodiscard]] constexpr iterator rend() noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator rend() const noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator crend() const noexcept { return a.crend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return a.empty(); } + + [[nodiscard]] constexpr size_type size() const noexcept { return a.size(); } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void fill(const V& value) { + for (auto& v : a) { + v = value; + } + } + + constexpr void swap(array& other) noexcept(std::is_nothrow_swappable_v) { + for (std::size_t i = 0; i < a.size(); ++i) { + auto v = std::move(other.a[i]); + other.a[i] = std::move(a[i]); + a[i] = std::move(v); + } + } + + [[nodiscard]] friend constexpr bool operator==(const array& a1, const array& a2) { return detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator!=(const array& a1, const array& a2) { return !detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<(const array& a1, const array& a2) { return detail::lexicographical_compare(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>(const array& a1, const array& a2) { return detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a1, a2); } + + container_type a; +}; + +namespace detail { + +template +constexpr array> to_array_impl(T (&a)[N], std::index_sequence) { + return {{a[I]...}}; +} + +template +constexpr array> to_array_impl(T(&&a)[N], std::index_sequence) { + return {{std::move(a[I])...}}; +} + +} // namespace detail + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T (&a)[N]) { + return detail::to_array_impl(a, std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T(&&a)[N]) { + return detail::to_array_impl(std::move(a), std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == sizeof...(Ts)), array>>> make_array(Ts&&... ts) { + return {{std::forward(ts)...}}; +} + +inline constexpr detail::raw_access_t raw_access{}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BITSET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class bitset { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using base_type = std::conditional_t() <= 8, std::uint_least8_t, + std::conditional_t() <= 16, std::uint_least16_t, + std::conditional_t() <= 32, std::uint_least32_t, + std::uint_least64_t>>>; + + static constexpr std::size_t bits_per_base = sizeof(base_type) * 8; + static constexpr std::size_t base_type_count = (enum_count() > 0 ? (enum_count() - 1) / bits_per_base + 1 : 0); + static constexpr std::size_t not_interested = base_type_count * bits_per_base - enum_count(); + static constexpr base_type last_value_max = (base_type{1} << (bits_per_base - not_interested)) - 1; + + template + class reference_impl { + friend class bitset; + + parent_t parent; + std::size_t num_index; + base_type bit_index; + + constexpr reference_impl(parent_t p, std::size_t i) noexcept : reference_impl(p, std::pair{i / bits_per_base, base_type{1} << (i % bits_per_base)}) {} + + constexpr reference_impl(parent_t p, std::pair i) noexcept : parent(p), num_index(std::get<0>(i)), bit_index(std::get<1>(i)) {} + + public: + constexpr reference_impl& operator=(bool v) noexcept { + if (v) { + parent->a[num_index] |= bit_index; + } else { + parent->a[num_index] &= ~bit_index; + } + return *this; + } + + constexpr reference_impl& operator=(const reference_impl& v) noexcept { + if (this == &v) { + return *this; + } + *this = static_cast(v); + return *this; + } + + [[nodiscard]] constexpr operator bool() const noexcept { return (parent->a[num_index] & bit_index) > 0; } + + [[nodiscard]] constexpr bool operator~() const noexcept { return !static_cast(*this); } + + constexpr reference_impl& flip() noexcept { + *this = ~*this; + return *this; + } + }; + + template + [[nodiscard]] constexpr T to_(detail::raw_access_t) const { + T res{}; + T flag{1}; + for (std::size_t i = 0; i < size(); ++i, flag <<= 1) { + if (const_reference{this, i}) { + if (i >= sizeof(T) * 8) { + MAGIC_ENUM_THROW(std::overflow_error("magic_enum::containers::bitset::to: Cannot represent enum in this type")); + } + res |= flag; + } + } + return res; + } + + public: + using index_type = Index; + using container_type = std::array; + using reference = reference_impl<>; + using const_reference = reference_impl; + + constexpr explicit bitset(detail::raw_access_t = raw_access) noexcept : a{{}} {} + + constexpr explicit bitset(detail::raw_access_t, unsigned long long val) : a{{}} { + unsigned long long bit{1}; + for (std::size_t i = 0; i < (sizeof(val) * 8); ++i, bit <<= 1) { + if ((val & bit) > 0) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw number")); + } + + reference{this, i} = true; + } + } + } + + constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : a{{}} { + std::size_t i = 0; + for (auto c : sv.substr(pos, n)) { + if (c == one) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw string")); + } + reference{this, i} = true; + } else if (c != zero) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized character in raw string")); + } + ++i; + } + } + + constexpr explicit bitset(detail::raw_access_t, const char_type* str, std::size_t n = ~std::size_t{0}, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : bitset(string_view{str, (std::min)(std::char_traits::length(str), n)}, 0, n, zero, one) {} + + constexpr bitset(std::initializer_list starters) : a{{}} { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + for (auto& f : starters) { + *this |= bitset(f); + } + } else { + for (auto& f : starters) { + set(f); + } + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit bitset(V starter) : a{{}} { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if (auto ul = enum_underlying(v); (ul & u) != 0) { + u &= ~ul; + (*this)[v] = true; + } + } + if (u != 0) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in flag")); + } + } + + template > + constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char_type sep = static_cast('|')) { + for (std::size_t to = 0; (to = magic_enum::detail::find(sv, sep)) != string_view::npos; sv.remove_prefix(to + 1)) { + if (auto v = enum_cast(sv.substr(0, to), cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + if (!sv.empty()) { + if (auto v = enum_cast(sv, cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + } + + [[nodiscard]] friend constexpr bool operator==(const bitset& lhs, const bitset& rhs) noexcept { return detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] friend constexpr bool operator!=(const bitset& lhs, const bitset& rhs) noexcept { return !detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] constexpr bool operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), static_cast(const_reference(this, *i)); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), reference{this, *i}; + } + + constexpr bool test(E pos) const { + if (auto i = index_type::at(pos)) { + return static_cast(const_reference(this, *i)); + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::test: Unrecognized position")); + } + + [[nodiscard]] constexpr bool all() const noexcept { + if constexpr (base_type_count == 0) { + return true; + } + + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + auto check = ~a[i]; + if (check) { + return false; + } + } + + if constexpr (not_interested > 0) { + return a[base_type_count - 1] == last_value_max; + } + } + + [[nodiscard]] constexpr bool any() const noexcept { + for (auto& v : a) { + if (v > 0) { + return true; + } + } + return false; + } + + [[nodiscard]] constexpr bool none() const noexcept { return !any(); } + + [[nodiscard]] constexpr std::size_t count() const noexcept { + std::size_t c = 0; + for (auto& v : a) { + c += detail::popcount(v); + } + return c; + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { return enum_count(); } + + [[nodiscard]] constexpr std::size_t max_size() const noexcept { return enum_count(); } + + constexpr bitset& operator&=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] &= other.a[i]; + } + return *this; + } + + constexpr bitset& operator|=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] |= other.a[i]; + } + return *this; + } + + constexpr bitset& operator^=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] ^= other.a[i]; + } + return *this; + } + + [[nodiscard]] constexpr bitset operator~() const noexcept { + bitset res; + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + res.a[i] = ~a[i]; + } + + if constexpr (not_interested > 0) { + res.a[base_type_count - 1] = ~a[base_type_count - 1] & last_value_max; + } + return res; + } + + constexpr bitset& set() noexcept { + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + a[i] = ~base_type{0}; + } + + if constexpr (not_interested > 0) { + a[base_type_count - 1] = last_value_max; + } + return *this; + } + + constexpr bitset& set(E pos, bool value = true) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = value; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::set: Unrecognized position")); + } + + constexpr bitset& reset() noexcept { return *this = bitset{}; } + + constexpr bitset& reset(E pos) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = false; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::reset: Unrecognized position")); + } + + constexpr bitset& flip() noexcept { return *this = ~*this; } + + [[nodiscard]] friend constexpr bitset operator&(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp &= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator|(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp |= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator^(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp ^= rhs; + return cp; + } + + template + [[nodiscard]] constexpr explicit operator std::enable_if_t == magic_enum::detail::enum_subtype::flags, E>() const { + E res{}; + for (const auto& e : enum_values()) { + if (test(e)) { + res |= e; + } + } + return res; + } + + [[nodiscard]] string to_string(char_type sep = static_cast('|')) const { + string name; + + for (const auto& e : enum_values()) { + if (test(e)) { + if (!name.empty()) { + name.append(1, sep); + } + auto n = enum_name(e); + name.append(n.data(), n.size()); + } + } + return name; + } + + [[nodiscard]] string to_string(detail::raw_access_t, char_type zero = static_cast('0'), char_type one = static_cast('1')) const { + string name; + name.reserve(size()); + for (std::size_t i = 0; i < size(); ++i) { + name.append(1, const_reference{this, i} ? one : zero); + } + return name; + } + + [[nodiscard]] constexpr unsigned long long to_ullong(detail::raw_access_t raw) const { return to_(raw); } + + [[nodiscard]] constexpr unsigned long long to_ulong(detail::raw_access_t raw) const { return to_(raw); } + + friend std::ostream& operator<<(std::ostream& o, const bitset& bs) { return o << bs.to_string(); } + + friend std::istream& operator>>(std::istream& i, bitset& bs) { + string s; + if (i >> s; !s.empty()) { + bs = bitset(string_view{s}); + } + return i; + } + + private: + container_type a; +}; + +template +explicit bitset(V starter) -> bitset; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class set { + using index_type = detail::indexing; + struct Getter { + constexpr const E& operator()(const set*, const E* p) const noexcept { return *p; } + }; + struct Predicate { + constexpr bool operator()(const set* h, const E* e) const noexcept { return h->a[*e]; } + }; + + public: + using container_type = bitset; + using key_type = E; + using value_type = E; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = detail::FilteredIterator; + using const_iterator = detail::FilteredIterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr set() noexcept = default; + + template + constexpr set(InputIt first, InputIt last) { + while (first != last) { + insert(*first++); + } + } + + constexpr set(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit set(V starter) { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if ((enum_underlying(v) & u) != 0) { + insert(v); + } + } + } + + constexpr set(const set&) noexcept = default; + constexpr set(set&&) noexcept = default; + + constexpr set& operator=(const set&) noexcept = default; + constexpr set& operator=(set&&) noexcept = default; + constexpr set& operator=(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + + constexpr const_iterator begin() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::begin()}; + } + + constexpr const_iterator end() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::end()}; + } + + constexpr const_iterator cbegin() const noexcept { return begin(); } + + constexpr const_iterator cend() const noexcept { return end(); } + + constexpr const_reverse_iterator rbegin() const noexcept { return {end()}; } + + constexpr const_reverse_iterator rend() const noexcept { return {begin()}; } + + constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return s == 0; } + + [[nodiscard]] constexpr size_type size() const noexcept { return s; } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void clear() noexcept { + a.reset(); + s = 0; + } + + constexpr std::pair insert(const value_type& value) noexcept { + if (auto i = index_type::at(value)) { + typename container_type::reference ref = a[value]; + bool r = !ref; + if (r) { + ref = true; + ++s; + } + + return {iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}, r}; + } + return {end(), false}; + } + + constexpr std::pair insert(value_type&& value) noexcept { return insert(value); } + + constexpr iterator insert(const_iterator, const value_type& value) noexcept { return insert(value).first; } + + constexpr iterator insert(const_iterator hint, value_type&& value) noexcept { return insert(hint, value); } + + template + constexpr void insert(InputIt first, InputIt last) noexcept { + while (first != last) { + insert(*first++); + } + } + + constexpr void insert(std::initializer_list ilist) noexcept { + for (auto v : ilist) { + insert(v); + } + } + + template + constexpr std::pair emplace(Args&&... args) noexcept { + return insert({std::forward(args)...}); + } + + template + constexpr iterator emplace_hint(const_iterator, Args&&... args) noexcept { + return emplace(std::forward(args)...).first; + } + + constexpr iterator erase(const_iterator pos) noexcept { + erase(*pos++); + return pos; + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept { + while ((first = erase(first)) != last) { + } + return first; + } + + constexpr size_type erase(const key_type& key) noexcept { + typename container_type::reference ref = a[key]; + bool res = ref; + if (res) { + --s; + } + ref = false; + return res; + } + + template + constexpr std::enable_if_t, size_type> erase(K&& x) noexcept { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last;) { + c += erase(*first++); + } + return c; + } + + void swap(set& other) noexcept { + set cp = *this; + *this = other; + other = cp; + } + + [[nodiscard]] constexpr size_type count(const key_type& key) const noexcept { return index_type::at(key) && a[key]; } + + template + [[nodiscard]] constexpr std::enable_if_t, size_type> count(const K& x) const { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + c += count(*first); + } + return c; + } + + [[nodiscard]] constexpr const_iterator find(const key_type& key) const noexcept { + if (auto i = index_type::at(key); i && a.test(key)) { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> find(const K& x) const { + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + if (a.test(*first)) { + return find(*first); + } + } + return end(); + } + + [[nodiscard]] constexpr bool contains(const key_type& key) const noexcept { return count(key); } + + template + [[nodiscard]] constexpr std::enable_if_t, bool> contains(const K& x) const noexcept { + return count(x) > 0; + } + + [[nodiscard]] constexpr std::pair equal_range(const key_type& key) const noexcept { return {lower_bound(key), upper_bound(key)}; } + + template + [[nodiscard]] constexpr std::enable_if_t, std::pair> equal_range(const K& x) const noexcept { + return {lower_bound(x), upper_bound(x)}; + } + + [[nodiscard]] constexpr const_iterator lower_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + auto it = const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + return a.test(key) ? it : std::next(it); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> lower_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? lower_bound(*first) : end(); + } + + [[nodiscard]] constexpr const_iterator upper_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + return std::next(const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> upper_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? upper_bound(*std::prev(last)) : end(); + } + + [[nodiscard]] constexpr key_compare key_comp() const { return {}; } + + [[nodiscard]] constexpr value_compare value_comp() const { return {}; } + + [[nodiscard]] constexpr friend bool operator==(const set& lhs, const set& rhs) noexcept { return lhs.a == rhs.a; } + + [[nodiscard]] constexpr friend bool operator!=(const set& lhs, const set& rhs) noexcept { return lhs.a != rhs.a; } + + [[nodiscard]] constexpr friend bool operator<(const set& lhs, const set& rhs) noexcept { + if (lhs.s < rhs.s) { + return true; + } + if (rhs.s < lhs.s) { + return false; + } + + for (auto it = index_type::begin(); it != index_type::end(); ++it) { + if (auto c = rhs.contains(*it); c != lhs.contains(*it)) { + return c; + } + } + return false; + } + + [[nodiscard]] constexpr friend bool operator<=(const set& lhs, const set& rhs) noexcept { return !(rhs < lhs); } + + [[nodiscard]] constexpr friend bool operator>(const set& lhs, const set& rhs) noexcept { return rhs < lhs; } + + [[nodiscard]] constexpr friend bool operator>=(const set& lhs, const set& rhs) noexcept { return !(lhs < rhs); } + + template + size_type erase_if(Pred pred) { + auto old_size = size(); + for (auto i = begin(), last = end(); i != last;) { + if (pred(*i)) { + i = erase(i); + } else { + ++i; + } + } + return old_size - size(); + } + + private: + container_type a; + std::size_t s = 0; +}; + +template +explicit set(V starter) -> set; + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&> get(array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&&> get(array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&> get(const array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&&> get(const array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&> get(array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&&> get(array&& a) { + return std::move(a[Enum]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&> get(const array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&&> get(const array&& a) { + return std::move(a[Enum]); +} + +} // namespace magic_enum::containers + +#endif // NEARGYE_MAGIC_ENUM_CONTAINERS_HPP diff --git a/include/magic_enum/magic_enum_flags.hpp b/include/magic_enum/magic_enum_flags.hpp new file mode 100644 index 0000000..f2d81b5 --- /dev/null +++ b/include/magic_enum/magic_enum_flags.hpp @@ -0,0 +1,222 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FLAGS_HPP +#define NEARGYE_MAGIC_ENUM_FLAGS_HPP + +#include "magic_enum.hpp" + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +#elif defined(_MSC_VER) +# pragma warning(push) +#endif + +namespace magic_enum { + +namespace detail { + +template > +constexpr U values_ors() noexcept { + static_assert(S == enum_subtype::flags, "magic_enum::detail::values_ors requires valid subtype."); + + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); + } + + return ors; +} + +} // namespace magic_enum::detail + +// Returns name from enum-flags value. +// If enum-flags value does not have name or value out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value, char_type sep = static_cast('|')) -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + if (const auto n = detail::names_v[i]; !n.empty()) { + check_value |= v; + if (!name.empty()) { + name.append(1, sep); + } + name.append(n.data(), n.size()); + } else { + return {}; // Value out of range. + } + } + } + + if (check_value != 0 && check_value == static_cast(value)) { + return name; + } + return {}; // Invalid value or out of range. +} + +// Obtains enum-flags value from integer value. +// Returns optional with enum-flags value. +template +[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + if constexpr (detail::is_sparse_v) { + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::values_ors(); + + if (value >= min && value <= max) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum-flags value from name. +// Returns optional with enum-flags value. +template > +[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + auto result = U{0}; + while (!value.empty()) { + const auto d = detail::find(value, '|'); + const auto s = (d == string_view::npos) ? value : value.substr(0, d); + auto f = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(i)); + result |= f; + break; + } + } + if (f == U{0}) { + return {}; // Invalid value or out of range. + } + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); + } + + if (result != U{0}) { + return static_cast(result); + } + return {}; // Invalid value or out of range. + } +} + +// Checks whether enum-flags contains value with such value. +template +[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_flags_cast(static_cast(value))); +} + +// Checks whether enum-flags contains value with such integer value. +template +[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value)); +} + +// Checks whether enum-flags contains enumerator with such name. +template > +[[nodiscard]] constexpr auto enum_flags_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value, std::move(p))); +} + +// Checks whether `flags set` contains `flag`. +// Note: If `flag` equals 0, it returns false, as 0 is not a flag. +template +constexpr auto enum_flags_test(E flags, E flag) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return static_cast(flag) && ((static_cast(flags) & static_cast(flag)) == static_cast(flag)); +} + +// Checks whether `lhs flags set` and `rhs flags set` have common flags. +// Note: If `lhs flags set` or `rhs flags set` equals 0, it returns false, as 0 is not a flag, and therfore cannot have any matching flag. +template +constexpr auto enum_flags_test_any(E lhs, E rhs) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return (static_cast(lhs) & static_cast(rhs)) != 0; +} + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_MAGIC_ENUM_FLAGS_HPP diff --git a/include/magic_enum/magic_enum_format.hpp b/include/magic_enum/magic_enum_format.hpp new file mode 100644 index 0000000..4997589 --- /dev/null +++ b/include/magic_enum/magic_enum_format.hpp @@ -0,0 +1,114 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP +#define NEARGYE_MAGIC_ENUM_FORMAT_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#if !defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT) +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT 1 +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +namespace magic_enum::customize { + // customize enum to enable/disable automatic std::format + template + constexpr bool enum_format_enabled() noexcept { + return MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT; + } +} // magic_enum::customize + +#if defined(__cpp_lib_format) + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +template +struct std::formatter> && magic_enum::customize::enum_format_enabled(), char>> : std::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(FMT_VERSION) + +#include + +template +struct fmt::formatter> && magic_enum::customize::enum_format_enabled(), char>> : fmt::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE) +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP diff --git a/include/magic_enum/magic_enum_fuse.hpp b/include/magic_enum/magic_enum_fuse.hpp new file mode 100644 index 0000000..6b2a3ef --- /dev/null +++ b/include/magic_enum/magic_enum_fuse.hpp @@ -0,0 +1,89 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP +#define NEARGYE_MAGIC_ENUM_FUSE_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr optional fuse_one_enum(optional hash, E value) noexcept { + if (hash) { + if (const auto index = enum_index(value)) { + return (*hash << log2((enum_count() << 1) - 1)) | *index; + } + } + return {}; +} + +template +constexpr optional fuse_enum(E value) noexcept { + return fuse_one_enum(0, value); +} + +template +constexpr optional fuse_enum(E head, Es... tail) noexcept { + return fuse_one_enum(fuse_enum(tail...), head); +} + +template +constexpr auto typesafe_fuse_enum(Es... values) noexcept { + enum class enum_fuse_t : std::uintmax_t; + const auto fuse = fuse_enum(values...); + if (fuse) { + return optional{static_cast(*fuse)}; + } + return optional{}; +} + +} // namespace magic_enum::detail + +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +template +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept { + static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse requires enum type."); + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values."); + static_assert((detail::log2(enum_count>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE) + const auto fuse = detail::fuse_enum...>(values...); +#else + const auto fuse = detail::typesafe_fuse_enum...>(values...); +#endif + return MAGIC_ENUM_ASSERT(fuse), fuse; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP diff --git a/include/magic_enum/magic_enum_iostream.hpp b/include/magic_enum/magic_enum_iostream.hpp new file mode 100644 index 0000000..6130dcb --- /dev/null +++ b/include/magic_enum/magic_enum_iostream.hpp @@ -0,0 +1,117 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_IOSTREAM_HPP +#define NEARGYE_MAGIC_ENUM_IOSTREAM_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +namespace magic_enum { + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto name = enum_flags_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } else { + if (const auto name = enum_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value ? (os << *value) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace istream_operators { + +template = 0> +std::basic_istream& operator>>(std::basic_istream& is, E& value) { + using D = std::decay_t; + + std::basic_string s; + is >> s; + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto v = enum_flags_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } else { + if (const auto v = enum_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } + } else { + is.setstate(std::basic_ios::failbit); + } + return is; +} + +} // namespace magic_enum::istream_operators + +namespace iostream_operators { + +using magic_enum::ostream_operators::operator<<; +using magic_enum::istream_operators::operator>>; + +} // namespace magic_enum::iostream_operators + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_IOSTREAM_HPP diff --git a/include/magic_enum/magic_enum_switch.hpp b/include/magic_enum/magic_enum_switch.hpp new file mode 100644 index 0000000..63fc19a --- /dev/null +++ b/include/magic_enum/magic_enum_switch.hpp @@ -0,0 +1,195 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_SWITCH_HPP +#define NEARGYE_MAGIC_ENUM_SWITCH_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +struct default_result_type {}; + +template +struct identity { + using type = T; +}; + +struct nonesuch {}; + +template > +struct invoke_result : identity {}; + +template +struct invoke_result : std::invoke_result {}; + +template +using invoke_result_t = typename invoke_result::type; + +template +constexpr auto common_invocable(std::index_sequence) noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::invocable_index requires enum type."); + + if constexpr (count_v == 0) { + return identity{}; + } else { + return std::common_type[I]>>...>{}; + } +} + +template +constexpr auto result_type() noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::result_type requires enum type."); + + constexpr auto seq = std::make_index_sequence>{}; + using R = typename decltype(common_invocable(seq))::type; + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + return identity{}; + } else { + return identity{}; + } + } else { + if constexpr (std::is_convertible_v) { + return identity{}; + } else if constexpr (std::is_convertible_v) { + return identity{}; + } else { + return identity{}; + } + } +} + +template , typename R = typename decltype(result_type())::type> +using result_t = std::enable_if_t && !std::is_same_v, R>; + +#if !defined(MAGIC_ENUM_ENABLE_HASH) && !defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr decltype(auto) constexpr_switch_impl(F&& f, E value, Def&& def) { + if constexpr(I < End) { + constexpr auto v = enum_constant()>{}; + if (value == v) { + if constexpr (std::is_invocable_r_v) { + return static_cast(std::forward(f)(v)); + } else { + return def(); + } + } else { + return constexpr_switch_impl(std::forward(f), value, std::forward(def)); + } + } else { + return def(); + } +} + +template +constexpr decltype(auto) constexpr_switch(F&& f, E value, Def&& def) { + static_assert(is_enum_v, "magic_enum::detail::constexpr_switch requires enum type."); + + if constexpr (count_v == 0) { + return def(); + } else { + return constexpr_switch_impl<0, count_v, R, E, S>(std::forward(f), value, std::forward(def)); + } +} +#endif + +} // namespace magic_enum::detail + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + detail::default_result_type_lambda); +#else + return detail::constexpr_switch( + std::forward(f), + value, + detail::default_result_type_lambda); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value) { + return enum_switch(std::forward(f), value); +} + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#else + return detail::constexpr_switch( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + return enum_switch(std::forward(f), value, std::forward(result)); +} + +} // namespace magic_enum + +template <> +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +#endif // NEARGYE_MAGIC_ENUM_SWITCH_HPP diff --git a/include/magic_enum/magic_enum_utility.hpp b/include/magic_enum/magic_enum_utility.hpp new file mode 100644 index 0000000..5368b02 --- /dev/null +++ b/include/magic_enum/magic_enum_utility.hpp @@ -0,0 +1,138 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_UTILITY_HPP +#define NEARGYE_MAGIC_ENUM_UTILITY_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr auto for_each(F&& f, std::index_sequence) { + constexpr bool has_void_return = (std::is_void_v[I]>>> || ...); + constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[I]>>> && ...); + + if constexpr (has_void_return) { + (f(enum_constant[I]>{}), ...); + } else if constexpr (all_same_return) { + return std::array{f(enum_constant[I]>{})...}; + } else { + return std::tuple{f(enum_constant[I]>{})...}; + } +} + +template +constexpr bool all_invocable(std::index_sequence) { + if constexpr (count_v == 0) { + return false; + } else { + return (std::is_invocable_v[I]>> && ...); + } +} + +} // namespace magic_enum::detail + +template , typename F, detail::enable_if_t = 0> +constexpr auto enum_for_each(F&& f) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto sep = std::make_index_sequence>{}; + + if constexpr (detail::all_invocable(sep)) { + return detail::for_each(std::forward(f), sep); + } else { + static_assert(detail::always_false_v, "magic_enum::enum_for_each requires invocable of all enum value."); + } +} + +template > +[[nodiscard]] constexpr auto enum_next_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) + n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_next_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) + n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) - n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) - n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_UTILITY_HPP diff --git a/include/module.hpp b/include/module.hpp new file mode 100644 index 0000000..f0f92b5 --- /dev/null +++ b/include/module.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include +#include + +namespace Fig +{ + class Module + { + public: + const FString name; + const FString spec; + const FString path; + + std::shared_ptr context; // module-level context + + /* + + import module -> automatically create a module context and call function `init` if exists + all global functions, variables, structs, etc will be stored in module context + then module context will be linked to the current context + + */ + Module(const FString &moduleName, const FString &moduleSpec, const FString &modulePath) : + name(moduleName), spec(moduleSpec), path(modulePath) + { + context = std::make_shared(FString(std::format("", name.toBasicString())), nullptr); + } + + bool hasSymbol(const FString &symbolName) + { + return context->contains(symbolName); + } + Value getSymbol(const FString &symbolName) + { + auto valOpt = context->get(symbolName); + if (!valOpt.has_value()) + { + throw RuntimeError(FStringView(std::format("Symbol '{}' not found in module '{}'", symbolName.toBasicString(), name.toBasicString()))); + } + return valOpt.value(); + } + }; +}; \ No newline at end of file diff --git a/include/parser.hpp b/include/parser.hpp new file mode 100644 index 0000000..04f5ee2 --- /dev/null +++ b/include/parser.hpp @@ -0,0 +1,265 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Fig +{ + + class Parser + { + private: + Lexer lexer; + std::vector output; + std::vector previousTokens; + + size_t tokenPruduced = 0; + size_t currentTokenIndex = 0; + + std::unique_ptr error; + + Ast::AstAddressInfo currentAAI; + + std::stack exprStack; + + void pushNode(const Ast::AstBase &_node) + { + Ast::AstBase node = _node; + node->setAAI(currentAAI); + output.push_back(std::move(node)); + } + void pushNode(const Ast::AstBase &_node, Ast::AstAddressInfo _aai) + { + Ast::AstBase node = _node; + node->setAAI(_aai); + output.push_back(node); + } + + bool isTokenSymbol(Token tok) + { + return Lexer::symbol_map.contains(tok.getValue()); + } + bool isTokenOp(Token tok) + { + return Ast::TokenToOp.contains(tok.getType()); + } + bool isEOF() + { + if (tokenPruduced == 0) return false; + return currentToken() == EOFTok; + } + + public: + using Precedence = uint32_t; + static const std::unordered_map> opPrecedence; + Parser(const Lexer &_lexer) : + lexer(_lexer) + { + } + + AddressableError* getError() const + { + return error.get(); + } + + template + void throwAddressableError(FStringView msg, size_t line, size_t column, std::source_location loc = std::source_location::current()) + { + static_assert(std::is_base_of_v, + "_ErrT must derive from AddressableError"); + _ErrT spError(msg, line, column, loc); + error = std::make_unique<_ErrT>(spError); + throw spError; + } + template + void throwAddressableError(FStringView msg, std::source_location loc = std::source_location::current()) + { + static_assert(std::is_base_of_v, + "_ErrT must derive from AddressableError"); + // line, column provide by `currentAAI` + _ErrT spError(msg, currentAAI.line, currentAAI.column, loc); + error = std::make_unique<_ErrT>(spError); + throw spError; + } + + template + void throwUnaddressableError(FStringView msg, std::source_location loc = std::source_location::current()) + { + static_assert(std::is_base_of_v, + "_ErrT must derive from AddressableError"); + _ErrT spError(msg, loc); + error = std::make_unique<_ErrT>(spError); + throw spError; + } + + void setCurrentAAI(Ast::AstAddressInfo _aai) + { + currentAAI = std::move(_aai); + } + + Ast::AstAddressInfo getCurrentAAI() const + { + return currentAAI; + } + + inline Token nextToken() + { + // 没有Rollback时, 存在 currentTokenIndex = tokenPruduced - 1 + next(); + return currentToken(); + } + inline void rollback() + { + if (int64_t(currentTokenIndex - 1) < int64_t(0)) + // 同下 next注释 + { + throw std::runtime_error("Internal Error in Parser::rollbackToken, trying to rollback but it's already on the begin"); + } + currentTokenIndex--; + } + inline void next() + { + if (int64_t(currentTokenIndex) < (int64_t(tokenPruduced) - 1)) + { + /* + 必须两个都显示转换为int64_t.否则,负数时会超出范围,变成int64_t max, 并且 CTI也需要显示转换,否则转换完的pruduced又会被转回去,变为 int64_t max + */ + currentTokenIndex++; + setCurrentAAI(Ast::AstAddressInfo{.line = currentToken().line, .column = currentToken().column}); + return; + } + if (isEOF()) return; + const Token &tok = lexer.nextToken(); + tokenPruduced++; + if (tok == IllegalTok) throw lexer.getError(); + currentTokenIndex = tokenPruduced - 1; + setCurrentAAI(Ast::AstAddressInfo{.line = tok.line, .column = tok.column}); + previousTokens.push_back(tok); + } + inline Token currentToken() + { + if (tokenPruduced == 0) return nextToken(); + return previousTokens.at(currentTokenIndex); + } + inline Token rollbackToken() + { + rollback(); + return previousTokens.at(currentTokenIndex); + } + + inline Token peekToken() + { + Token tok = nextToken(); + rollback(); + return tok; + } + + std::pair getBindingPower(Ast::Operator op) + { + return opPrecedence.at(op); + } + Precedence getLeftBindingPower(Ast::Operator op) + { + return getBindingPower(op).first; + } + Precedence getRightBindingPower(Ast::Operator op) + { + return getBindingPower(op).second; + } + + template + std::shared_ptr<_Tp> makeAst(Args &&...args) + { + _Tp node(args...); + node.setAAI(currentAAI); + return std::shared_ptr<_Tp>(new _Tp(node)); + } + + void expectPeek(TokenType type) + { + if (peekToken().getType() != type) + { + throwAddressableError(FStringView(std::format("Expected `{}`, but got `{}`", + magic_enum::enum_name(type), + magic_enum::enum_name(peekToken().getType())))); + } + } + + void expect(TokenType type) + { + if (currentToken().getType() != type) + { + throwAddressableError(FStringView(std::format("Expected `{}`, but got `{}`", + magic_enum::enum_name(type), + magic_enum::enum_name(currentToken().getType())))); + } + } + + void expectPeek(TokenType type, FString expected) + { + if (peekToken().getType() != type) + { + throwAddressableError(FStringView(std::format("Expected `{}`, but got `{}`", + expected.toBasicString(), + magic_enum::enum_name(peekToken().getType())))); + } + } + + void expect(TokenType type, FString expected) + { + if (currentToken().getType() != type) + { + throwAddressableError(FStringView(std::format("Expected `{}`, but got `{}`", + expected.toBasicString(), + magic_enum::enum_name(currentToken().getType())))); + } + } + + bool isNext(TokenType type) + { + return peekToken().getType() == type; + } + bool isThis(TokenType type) + { + return currentToken().getType() == type; + } + + static constexpr FString varDefTypeFollowed = u8"(Followed)"; + + Ast::VarDef __parseVarDef(bool); // entry: current is keyword `var` or `const` (isConst: Bool) + Value __parseValue(); + Ast::ValueExpr __parseValueExpr(); + Ast::FunctionCall __parseFunctionCall(FString); + Ast::FunctionParameters __parseFunctionParameters(); // entry: current is Token::LeftParen + Ast::Statement __parseStatement(); // entry: (idk) + Ast::BlockStatement __parseBlockStatement(); // entry: current is Token::LeftBrace + Ast::VarAssign __parseVarAssign(FString); // entry: current is Token::Assign, para1 is var name + Ast::If __parseIf(); // entry: current is Token::If + Ast::While __parseWhile(); // entry: current is Token::While + Ast::Return __parseReturn(); // entry: current is Token::Return + + Ast::VarExpr __parseVarExpr(FString); + Ast::LambdaExpr __parseLambdaExpr(); + Ast::FunctionDef __parseFunctionDef(bool); // entry: current is Token::Identifier (isPublic: Bool) + Ast::StructDef __parseStructDef(bool); // entry: current is Token::Identifier (struct name) arg(isPublic: bool) + + Ast::BinaryExpr __parseInfix(Ast::Expression, Ast::Operator, Precedence); + Ast::UnaryExpr __parsePrefix(Ast::Operator, Precedence); + + Ast::ListExpr __parseListExpr(); // entry: current is `[` + Ast::Expression __parseTupleOrParenExpr(); // entry: current is `(` + Ast::MapExpr __parseMapExpr(); // entry: current is `{` + + Ast::InitExpr __parseInitExpr(FString); // entry: current is `{`, ahead is struct name. arg (struct name : FString) + + Ast::Expression parseExpression(Precedence, TokenType = TokenType::Semicolon, TokenType = TokenType::Semicolon); + std::vector parseAll(); + }; +}; // namespace Fig \ No newline at end of file diff --git a/include/token.hpp b/include/token.hpp new file mode 100644 index 0000000..f3f1e9b --- /dev/null +++ b/include/token.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Fig +{ + enum class TokenType : int8_t + { + Illegal = -1, + EndOfFile = 0, + + Comments, + + Identifier, + + /* Keywords */ + And, // and + Or, // or + Not, // not + Import, // import + Function, // fun + Variable, // var + Const, // const + Final, // final + While, // while + For, // for + If, // if + Else, // else + Struct, // struct + Interface, // interface + Implement, // implement + Public, // public + Return, // return + + // TypeNull, // Null + // TypeInt, // Int + // TypeString, // String + // TypeBool, // Bool + // TypeDouble, // Double + + /* Literal Types (not keyword)*/ + LiteralNumber, // number (int,float...) + LiteralString, // FString + LiteralBool, // bool (true/false) + LiteralNull, // null (Null的唯一实例) + + /* Punct */ + Plus, // + + Minus, // - + Asterisk, // * + Slash, // / + Percent, // % + Caret, // ^ + Ampersand, // & + Pipe, // | + Tilde, // ~ + ShiftLeft, // << + ShiftRight, // >> + // Exclamation, // ! + Question, // ? + Assign, // = + Less, // < + Greater, // > + Dot, // . + Comma, // , + Colon, // : + Semicolon, // ; + SingleQuote, // ' + DoubleQuote, // " + // Backtick, // ` + // At, // @ + // Hash, // # + // Dollar, // $ + // Backslash, // '\' + // Underscore, // _ + LeftParen, // ( + RightParen, // ) + LeftBracket, // [ + RightBracket, // ] + LeftBrace, // { + RightBrace, // } + // LeftArrow, // <- + RightArrow, // -> + // DoubleArrow, // => + Equal, // == + NotEqual, // != + LessEqual, // <= + GreaterEqual, // >= + PlusEqual, // += + MinusEqual, // -= + AsteriskEqual, // *= + SlashEqual, // /= + PercentEqual, // %= + CaretEqual, // ^= + DoublePlus, // ++ + DoubleMinus, // -- + DoubleAmpersand, // && + DoublePipe, // || + Walrus, // := + Power, // ** + }; + + class Token final + { + friend bool operator==(const Token &l, const Token &r); + + private: + FString value; + TokenType type; + + public: + size_t line, column; + + inline Token() {}; + inline Token(const FString &_value, TokenType _type) : + value(_value), type(_type) {} + inline Token(const FString &_value, TokenType _type, size_t _line, size_t _column) : + value(_value), type(_type) + { + line = _line; + column = _column; + } + Token setPos(size_t _line, size_t _column) + { + line = _line; + column = _column; + return *this; + } + FString getValue() + { + return value; + } + inline FString toString() const + { + return FString(std::format( + "Token('{}',{})", + this->value.toBasicString(), + magic_enum::enum_name(type))); + } + + bool isIdentifier() + { + return type == TokenType::Identifier; + } + bool isLiteral() + { + return type == TokenType::LiteralNull || type == TokenType::LiteralBool || type == TokenType::LiteralNumber || type == TokenType::LiteralString; + } + + TokenType getType() + { + return type; + } + }; + + inline bool operator==(const Token &l, const Token &r) + { + return l.type == r.type and l.value == r.value; + } + + static Token IllegalTok(u8"ILLEGAL", TokenType::Illegal); + static Token EOFTok(u8"EOF", TokenType::EndOfFile); +} // namespace Fig \ No newline at end of file diff --git a/include/utf8_iterator.hpp b/include/utf8_iterator.hpp new file mode 100644 index 0000000..0505e99 --- /dev/null +++ b/include/utf8_iterator.hpp @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +// fuckyou C++ +// i don't know how to deal with unicode string in cpp +// fuck +// generate by Qwen3-Coder: + +namespace Fig +{ + class UTF8Char + { + private: + std::u8string char_data_; + + public: + UTF8Char(const std::u8string &data) : + char_data_(data) {} + + // 获取UTF-8字符的字节长度 + static size_t getUTF8CharLength(char8_t first_byte) + { + if ((first_byte & 0x80) == 0x00) return 1; + if ((first_byte & 0xE0) == 0xC0) return 2; + if ((first_byte & 0xF0) == 0xE0) return 3; + if ((first_byte & 0xF8) == 0xF0) return 4; + return 1; + } + + // 转换为Unicode码点 + char32_t toCodePoint() const + { + if (char_data_.empty()) return 0; + + size_t len = getUTF8CharLength(char_data_[0]); + if (len > char_data_.length()) return 0; + + char32_t code_point = 0; + + switch (len) + { + case 1: + code_point = char_data_[0]; + break; + case 2: + code_point = ((char_data_[0] & 0x1F) << 6) | (char_data_[1] & 0x3F); + break; + case 3: + code_point = ((char_data_[0] & 0x0F) << 12) | ((char_data_[1] & 0x3F) << 6) | (char_data_[2] & 0x3F); + break; + case 4: + code_point = ((char_data_[0] & 0x07) << 18) | ((char_data_[1] & 0x3F) << 12) | ((char_data_[2] & 0x3F) << 6) | (char_data_[3] & 0x3F); + break; + } + return code_point; + } + + inline bool operator==(char32_t ch) + { + return this->toCodePoint() == ch; + } + // 字符分类函数 + bool isAlpha() const + { + char32_t cp = toCodePoint(); + return std::iswalpha(static_cast(cp)); + } + + bool isDigit() const + { + char32_t cp = toCodePoint(); + return std::iswdigit(static_cast(cp)); + } + + bool isAlnum() const + { + char32_t cp = toCodePoint(); + return std::iswalnum(static_cast(cp)); + } + + bool isSpace() const + { + char32_t cp = toCodePoint(); + return std::iswspace(static_cast(cp)); + } + + bool isUpper() const + { + char32_t cp = toCodePoint(); + return std::iswupper(static_cast(cp)); + } + + bool isLower() const + { + char32_t cp = toCodePoint(); + return std::iswlower(static_cast(cp)); + } + + bool isPunct() const + { + char32_t cp = toCodePoint(); + return std::iswpunct(static_cast(cp)); + } + + // 获取底层数据 + const std::u8string &getString() const { return char_data_; } + + // 获取字符长度(字节数) + size_t length() const { return char_data_.length(); } + + // 是否为空 + bool empty() const { return char_data_.empty(); } + }; + + class UTF8Iterator + { + private: + const std::u8string *str_; + size_t pos_; + + // 获取UTF-8字符的字节长度 + static size_t getUTF8CharLength(char8_t first_byte) + { + if ((first_byte & 0x80) == 0x00) return 1; + if ((first_byte & 0xE0) == 0xC0) return 2; + if ((first_byte & 0xF0) == 0xE0) return 3; + if ((first_byte & 0xF8) == 0xF0) return 4; + return 1; + } + + // 获取下一个字符的起始位置 + size_t getNextCharPos(size_t current_pos) const + { + if (current_pos >= str_->length()) return current_pos; + size_t char_len = getUTF8CharLength((*str_)[current_pos]); + return current_pos + char_len; + } + + // 获取前一个字符的起始位置 + size_t getPrevCharPos(size_t current_pos) const + { + if (current_pos == 0) return 0; + + size_t pos = current_pos - 1; + while (pos > 0 && (str_->at(pos) & 0xC0) == 0x80) + { + --pos; + } + return pos; + } + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = UTF8Char; + using difference_type = std::ptrdiff_t; + using pointer = const UTF8Char *; + using reference = const UTF8Char &; + + // 构造函数 + UTF8Iterator(const std::u8string &str, size_t pos = 0) : + str_(&str), pos_(pos) + { + if (pos_ > str_->length()) pos_ = str_->length(); + } + + // 前置递增 + UTF8Iterator &operator++() + { + pos_ = getNextCharPos(pos_); + return *this; + } + + // 后置递增 + UTF8Iterator operator++(int) + { + UTF8Iterator temp = *this; + pos_ = getNextCharPos(pos_); + return temp; + } + + // 前置递减 + UTF8Iterator &operator--() + { + pos_ = getPrevCharPos(pos_); + return *this; + } + + // 后置递减 + UTF8Iterator operator--(int) + { + UTF8Iterator temp = *this; + pos_ = getPrevCharPos(pos_); + return temp; + } + + // 解引用操作符 - 返回当前字符 + UTF8Char operator*() const + { + if (pos_ >= str_->length()) + { + return UTF8Char(std::u8string()); + } + size_t char_len = getUTF8CharLength((*str_)[pos_]); + size_t end_pos = pos_ + char_len; + if (end_pos > str_->length()) + { + end_pos = str_->length(); + } + return UTF8Char(str_->substr(pos_, end_pos - pos_)); + } + UTF8Char peek() const + { + if (pos_ >= str_->length()) + { + return UTF8Char(std::u8string()); + } + + size_t next_pos = getNextCharPos(pos_); + if (next_pos >= str_->length()) + { + return UTF8Char(std::u8string()); + } + + size_t char_len = getUTF8CharLength((*str_)[next_pos]); + size_t end_pos = next_pos + char_len; + if (end_pos > str_->length()) + { + end_pos = str_->length(); + } + + return UTF8Char(str_->substr(next_pos, end_pos - next_pos)); + } + + // 窥探前一个字符 + UTF8Char peekPrev() const + { + if (pos_ == 0) + { + return UTF8Char(std::u8string()); + } + + size_t prev_pos = getPrevCharPos(pos_); + size_t char_len = getUTF8CharLength((*str_)[prev_pos]); + size_t end_pos = prev_pos + char_len; + if (end_pos > str_->length()) + { + end_pos = str_->length(); + } + + return UTF8Char(str_->substr(prev_pos, end_pos - prev_pos)); + } + // 获取当前位置 + size_t position() const { return pos_; } + size_t column() const { return pos_ + 1; } + // 检查是否到达末尾 + bool isEnd() const { return pos_ >= str_->length(); } + }; +} // namespace Fig \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..03743b2 --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,109 @@ +#pragma once +#pragma once +#include +#include +#include +#include +#include +#include + +namespace Fig::Utils +{ + inline std::u32string utf8ToUtf32(const FString &s) + { + std::u32string result; + size_t i = 0; + while (i < s.size()) + { + char32_t codepoint = 0; + unsigned char c = static_cast(s[i]); + + if (c < 0x80) + { + codepoint = c; + i += 1; + } + else if ((c >> 5) == 0x6) + { + codepoint = ((c & 0x1F) << 6) | (static_cast(s[i + 1]) & 0x3F); + i += 2; + } + else if ((c >> 4) == 0xE) + { + codepoint = ((c & 0x0F) << 12) | ((static_cast(s[i + 1]) & 0x3F) << 6) | (static_cast(s[i + 2]) & 0x3F); + i += 3; + } + else if ((c >> 3) == 0x1E) + { + codepoint = ((c & 0x07) << 18) | ((static_cast(s[i + 1]) & 0x3F) << 12) | ((static_cast(s[i + 2]) & 0x3F) << 6) | (static_cast(s[i + 3]) & 0x3F); + i += 4; + } + else + { + i += 1; // 跳过非法字节 + continue; + } + result.push_back(codepoint); + } + return result; + } + + inline FString utf32ToUtf8(const std::u32string &s) + { + FString result; + for (char32_t cp : s) + { + if (cp < 0x80) + { + result.push_back(static_cast(cp)); + } + else if (cp < 0x800) + { + result.push_back(static_cast((cp >> 6) | 0xC0)); + result.push_back(static_cast((cp & 0x3F) | 0x80)); + } + else if (cp < 0x10000) + { + result.push_back(static_cast((cp >> 12) | 0xE0)); + result.push_back(static_cast(((cp >> 6) & 0x3F) | 0x80)); + result.push_back(static_cast((cp & 0x3F) | 0x80)); + } + else + { + result.push_back(static_cast((cp >> 18) | 0xF0)); + result.push_back(static_cast(((cp >> 12) & 0x3F) | 0x80)); + result.push_back(static_cast(((cp >> 6) & 0x3F) | 0x80)); + result.push_back(static_cast((cp & 0x3F) | 0x80)); + } + } + return result; + } + + inline FString toLower(const FString &s) + { + std::u32string u32 = utf8ToUtf32(s); + std::locale loc(""); + for (auto &ch : u32) + { + ch = std::towlower(ch); + } + return utf32ToUtf8(u32); + } + + inline FString toUpper(const FString &s) + { + std::u32string u32 = utf8ToUtf32(s); + std::locale loc(""); + for (auto &ch : u32) + { + ch = std::towupper(ch); + } + return utf32ToUtf8(u32); + } + + template + bool vectorContains(const T &t, const std::vector v) + { + return std::find(v.begin(), v.end(), t) != v.end(); + } +} // namespace Fig::Utils diff --git a/include/value.hpp b/include/value.hpp new file mode 100644 index 0000000..9f6a4b3 --- /dev/null +++ b/include/value.hpp @@ -0,0 +1,373 @@ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace Fig +{ + class Value + { + public: + using VariantType = std::variant; + VariantType data; + + Value() : + data(Null{}) {} + Value(const Null &n) : + data(std::in_place_type, n) {} + Value(const Int &i) : + data(std::in_place_type, i) {} + Value(const Double &d) : + data(std::in_place_type, d) + { + ValueType::IntClass casted = static_cast(d.getValue()); + if (casted == d.getValue()) + { + data.emplace(casted); + } + } + Value(const String &s) : + data(std::in_place_type, s) {} + Value(const Bool &b) : + data(std::in_place_type, b) {} + Value(const Function &f) : + data(std::in_place_type, f) {} + Value(const StructType &s) : + data(std::in_place_type, s) {} + Value(const StructInstance &s) : + data(std::in_place_type, s) {} + + template + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v>> + Value(const T &val) + { + // 不可以用 data = 的形式 + // __ValueWrapper 构造、拷贝有限制 + if constexpr (std::is_same_v) + data.emplace(val); + else if constexpr (std::is_same_v) + { + ValueType::IntClass casted = static_cast(val); + if (casted == val) + { + data.emplace(casted); + } + else + { + data.emplace(val); + } + } + else if constexpr (std::is_same_v) + data.emplace(val); + else if constexpr (std::is_same_v) + data.emplace(val); + else if constexpr (std::is_same_v) + data.emplace(val); + else if constexpr (std::is_same_v) + data.emplace(val); + else if constexpr (std::is_same_v) + data.emplace(val); + } + Value(const Value &) = default; + Value(Value &&) noexcept = default; + Value &operator=(const Value &) = default; + Value &operator=(Value &&) noexcept = default; + + static Value defaultValue(TypeInfo ti) + { + if (ti == ValueType::Int) + return Value(Int(0)); + else if (ti == ValueType::Double) + return Value(Double(0.0)); + else if (ti == ValueType::String) + return Value(String(u8"")); + else if (ti == ValueType::Bool) + return Value(Bool(false)); + else if (ti == ValueType::Function) + return getNullInstance(); + else if (ti == ValueType::StructType) + return getNullInstance(); + else if (ti == ValueType::StructInstance) + return getNullInstance(); + else + return getNullInstance(); + } + + template + bool is() const + { + return std::holds_alternative(data); + } + + template + T &as() + { + return std::get(data); + } + + template + const T &as() const + { + return std::get(data); + } + + static Value getNullInstance() + { + static Value v(Null{}); + return v; + } + + TypeInfo getTypeInfo() const + { + return std::visit([](auto &&val) { return val.ti; }, data); + } + + bool isNull() const { return is(); } + bool isNumeric() const { return is() || is(); } + + ValueType::DoubleClass getNumericValue() const + { + if (is()) + return static_cast(as().getValue()); + else if (is()) + return as().getValue(); + else + throw RuntimeError(u8"getNumericValue: Not a numeric value"); + } + + FString toString() const + { + if (is()) return FString(u8"null"); + if (is()) return FString(std::to_string(as().getValue())); + if (is()) return FString(std::to_string(as().getValue())); + if (is()) return as().getValue(); + if (is()) return as().getValue() ? FString(u8"true") : FString(u8"false"); + if (is()) + { + return FString(std::format("", + as().getValue().id, + static_cast(as().data.get()))); + } + if (is()) + { + return FString(std::format("", + as().getValue().id, + static_cast(as().data.get()))); + } + if (is()) + { + return FString(std::format("().getValue().structName.toBasicString(), + static_cast(as().data.get()))); + } + return FString(u8""); + } + + private: + static std::string makeTypeErrorMessage(const char *prefix, const char *op, + const Value &lhs, const Value &rhs) + { + auto lhs_type = std::visit([](auto &&v) { return v.ti.name.toBasicString(); }, lhs.data); + auto rhs_type = std::visit([](auto &&v) { return v.ti.name.toBasicString(); }, rhs.data); + return std::format("{}: {} '{}' {}", prefix, lhs_type, op, rhs_type); + } + + public: + // math + friend Value operator+(const Value &lhs, const Value &rhs) + { + if (lhs.isNull() || rhs.isNull()) + throw ValueError(FStringView(makeTypeErrorMessage("Cannot add", "+", lhs, rhs))); + if (lhs.isNumeric() and rhs.isNumeric()) + return lhs.getNumericValue() + rhs.getNumericValue(); + if (lhs.is() && rhs.is()) + return Value(ValueType::StringClass(lhs.as().getValue() + rhs.as().getValue())); + + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "+", lhs, rhs))); + } + + friend Value operator-(const Value &lhs, const Value &rhs) + { + if (lhs.isNull() || rhs.isNull()) + throw ValueError(FStringView(makeTypeErrorMessage("Cannot subtract", "-", lhs, rhs))); + if (lhs.isNumeric() and rhs.isNumeric()) + return lhs.getNumericValue() - rhs.getNumericValue(); + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "-", lhs, rhs))); + } + + friend Value operator*(const Value &lhs, const Value &rhs) + { + if (lhs.isNull() || rhs.isNull()) + throw ValueError(FStringView(makeTypeErrorMessage("Cannot multiply", "*", lhs, rhs))); + if (lhs.isNumeric() and rhs.isNumeric()) + return lhs.getNumericValue() * rhs.getNumericValue(); + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "*", lhs, rhs))); + } + + friend Value operator/(const Value &lhs, const Value &rhs) + { + if (lhs.isNull() || rhs.isNull()) + throw ValueError(FStringView(makeTypeErrorMessage("Cannot divide", "/", lhs, rhs))); + if (lhs.isNumeric() and rhs.isNumeric()) + { + auto rnv = rhs.getNumericValue(); + if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Division by zero", "/", lhs, rhs))); + return lhs.getNumericValue() / rnv; + } + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "/", lhs, rhs))); + } + + friend Value operator%(const Value &lhs, const Value &rhs) + { + if (lhs.isNull() || rhs.isNull()) + throw ValueError(FStringView(makeTypeErrorMessage("Cannot modulo", "%", lhs, rhs))); + if (lhs.isNumeric() and rhs.isNumeric()) + { + auto rnv = rhs.getNumericValue(); + if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Modulo by zero", "%", lhs, rhs))); + return fmod(lhs.getNumericValue(), rhs.getNumericValue()); + } + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "%", lhs, rhs))); + } + + // logic + friend Value operator&&(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Logical AND requires bool", "&&", lhs, rhs))); + return Value(lhs.as().getValue() && rhs.as().getValue()); + } + + friend Value operator||(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Logical OR requires bool", "||", lhs, rhs))); + return Value(lhs.as().getValue() || rhs.as().getValue()); + } + + friend Value operator!(const Value &v) + { + if (!v.is()) + throw ValueError(FStringView(std::format("Logical NOT requires bool: '{}'", + std::visit([](auto &&val) { return val.ti.name.toBasicString(); }, v.data)))); + return Value(!v.as().getValue()); + } + + friend Value operator-(const Value &v) + { + if (v.isNull()) + throw ValueError(FStringView(std::format("Unary minus cannot be applied to null"))); + if (v.is()) + return Value(-v.as().getValue()); + if (v.is()) + return Value(-v.as().getValue()); + throw ValueError(FStringView(std::format("Unary minus requires int or double: '{}'", + std::visit([](auto &&val) { return val.ti.name.toBasicString(); }, v.data)))); + } + friend Value operator~(const Value &v) + { + if (!v.is()) + throw ValueError(FStringView(std::format("Bitwise NOT requires int: '{}'", + std::visit([](auto &&val) { return val.ti.name.toBasicString(); }, v.data)))); + return Value(~v.as().getValue()); + } + + // compare → now returns bool + friend bool operator==(const Value &lhs, const Value &rhs) + { + return lhs.data == rhs.data; + } + + friend bool operator!=(const Value &lhs, const Value &rhs) + { + return !(lhs.data == rhs.data); + } + + friend bool operator<(const Value &lhs, const Value &rhs) + { + if (lhs.isNumeric() and rhs.isNumeric()) + return lhs.getNumericValue() < rhs.getNumericValue(); + if (lhs.is() && rhs.is()) return lhs.as().getValue() < rhs.as().getValue(); + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported comparison", "<", lhs, rhs))); + } + + friend bool operator<=(const Value &lhs, const Value &rhs) + { + return lhs == rhs or lhs < rhs; + } + + friend bool operator>(const Value &lhs, const Value &rhs) + { + if (lhs.isNumeric() and rhs.isNumeric()) + return lhs.getNumericValue() > rhs.getNumericValue(); + if (lhs.is() && rhs.is()) return lhs.as().getValue() > rhs.as().getValue(); + throw ValueError(FStringView(makeTypeErrorMessage("Unsupported comparison", ">", lhs, rhs))); + } + + friend bool operator>=(const Value &lhs, const Value &rhs) + { + return lhs == rhs or lhs > rhs; + } + + // bitwise + friend Value bit_and(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Bitwise AND requires int", "&", lhs, rhs))); + return Value(lhs.as().getValue() & rhs.as().getValue()); + } + + friend Value bit_or(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Bitwise OR requires int", "|", lhs, rhs))); + return Value(lhs.as().getValue() | rhs.as().getValue()); + } + + friend Value bit_xor(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Bitwise XOR requires int", "^", lhs, rhs))); + return Value(lhs.as().getValue() ^ rhs.as().getValue()); + } + + friend Value bit_not(const Value &v) + { + if (!v.is()) + throw ValueError(FStringView(std::format("Bitwise NOT requires int: '{}'", + std::visit([](auto &&val) { return val.ti.name.toBasicString(); }, v.data)))); + return Value(~v.as().getValue()); + } + + friend Value shift_left(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Shift left requires int", "<<", lhs, rhs))); + return Value(lhs.as().getValue() << rhs.as().getValue()); + } + + friend Value shift_right(const Value &lhs, const Value &rhs) + { + if (!lhs.is() || !rhs.is()) + throw ValueError(FStringView(makeTypeErrorMessage("Shift right requires int", ">>", lhs, rhs))); + return Value(lhs.as().getValue() >> rhs.as().getValue()); + } + }; + + using Any = Value; +} // namespace Fig diff --git a/include/warning.hpp b/include/warning.hpp new file mode 100644 index 0000000..a71d316 --- /dev/null +++ b/include/warning.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include +#include + +namespace Fig +{ + class Warning + { + private: + size_t id; // the id (standard) of warning + FString msg; + size_t line, column; + public: + static const std::unordered_map standardWarnings; + Warning(size_t _id, FString _msg) + { + id = _id; + msg = std::move(_msg); + } + Warning(size_t _id, FString _msg, size_t _line, size_t _column) + { + id = _id; + msg = std::move(_msg); + line = _line; + column = _column; + } + + auto getIDName() + { + return standardWarnings.at(id); + } + + auto getID() + { + return id; + } + auto getMsg() + { + return msg; + } + auto getLine() + { + return line; + } + auto getColumn() + { + return column; + } + + }; + +}; \ No newline at end of file diff --git a/src/evaluator.cpp b/src/evaluator.cpp new file mode 100644 index 0000000..4c380fa --- /dev/null +++ b/src/evaluator.cpp @@ -0,0 +1,488 @@ +#include +#include +#include + +namespace Fig +{ + Value Evaluator::__evalOp(Ast::Operator op, const Value &lhs, const Value &rhs) + { + using Fig::Ast::Operator; + switch (op) + { + case Operator::Add: return lhs + rhs; + case Operator::Subtract: return lhs - rhs; + case Operator::Multiply: return lhs * rhs; + case Operator::Divide: return lhs / rhs; + case Operator::Modulo: return lhs % rhs; + + case Operator::And: return lhs && rhs; + case Operator::Or: return lhs || rhs; + case Operator::Not: return !lhs; + + case Operator::Equal: return Value(lhs == rhs); + case Operator::NotEqual: return Value(lhs != rhs); + case Operator::Less: return lhs < rhs; + case Operator::LessEqual: return lhs <= rhs; + case Operator::Greater: return lhs > rhs; + case Operator::GreaterEqual: return lhs >= rhs; + + case Operator::BitAnd: return bit_and(lhs, rhs); + case Operator::BitOr: return bit_or(lhs, rhs); + case Operator::BitXor: return bit_xor(lhs, rhs); + case Operator::BitNot: return bit_not(lhs); + case Operator::ShiftLeft: return shift_left(lhs, rhs); + case Operator::ShiftRight: return shift_right(lhs, rhs); + + case Operator::Walrus: { + static constexpr char WalrusErrorName[] = "WalrusError"; + throw EvaluatorError(FStringView(u8"Walrus operator is not supported"), currentAddressInfo); // using parent address info for now + } + default: + throw RuntimeError(FStringView(u8"Unsupported operator")); + } + } + + Value Evaluator::evalBinary(const Ast::BinaryExpr &binExp) + { + return __evalOp(binExp->op, eval(binExp->lexp), eval(binExp->rexp)); + } + Value Evaluator::evalUnary(const Ast::UnaryExpr &unExp) + { + using Fig::Ast::Operator; + switch (unExp->op) + { + case Operator::Not: + return !eval(unExp->exp); + case Operator::Subtract: + return -eval(unExp->exp); + case Operator::BitNot: + return bit_not(eval(unExp->exp)); + default: + throw RuntimeError(FStringView(std::format("Unsupported unary operator: {}", magic_enum::enum_name(unExp->op)))); + } + } + + Value Evaluator::eval(Ast::Expression exp) + { + using Fig::Ast::AstType; + switch (exp->getType()) + { + case AstType::ValueExpr: { + auto valExp = std::dynamic_pointer_cast(exp); + return valExp->val; + } + case AstType::VarExpr: { + auto varExp = std::dynamic_pointer_cast(exp); + auto val = currentContext->get(varExp->name); + if (val.has_value()) + { + return val.value(); + } + throw RuntimeError(FStringView(std::format("Variable '{}' not defined", varExp->name.toBasicString()))); + } + case AstType::BinaryExpr: { + auto binExp = std::dynamic_pointer_cast(exp); + return evalBinary(binExp); + } + case AstType::UnaryExpr: { + auto unExp = std::dynamic_pointer_cast(exp); + return evalUnary(unExp); + } + case AstType::FunctionCall: { + // std::cerr << "Eval: function call...\n"; + auto fnCall = std::dynamic_pointer_cast(exp); + FString fnName = fnCall->name; + if (Builtins::isBuiltinFunction(fnName)) + { + std::vector callArgs; + if (fnCall->arg.getLength() != Builtins::getBuiltinFunctionParamCount(fnName) and Builtins::getBuiltinFunctionParamCount(fnName) != -1) // -1 means variadic + { + static constexpr char BuiltinArgumentMismatchErrorName[] = "BuiltinArgumentMismatchError"; + throw EvaluatorError(FStringView(std::format("Builtin function '{}' expects {} arguments, but {} were provided", fnName.toBasicString(), Builtins::getBuiltinFunctionParamCount(fnName), callArgs.size())), currentAddressInfo); + } + for (const auto &argExp : fnCall->arg.argv) + { + callArgs.push_back(eval(argExp)); + } + return Builtins::getBuiltinFunction(fnName)(callArgs); + } + + auto fnValOpt = currentContext->get(fnName); + if (!fnValOpt.has_value()) + { + static constexpr char FunctionNotFoundErrorName[] = "FunctionNotFoundError"; + throw EvaluatorError(FStringView(std::format("Function '{}' not defined", fnName.toBasicString())), currentAddressInfo); + } + Value fnVal = fnValOpt.value(); + if (!fnVal.is()) + { + static constexpr char NotAFunctionErrorName[] = "NotAFunctionError"; + throw EvaluatorError(FStringView(std::format("'{}' is not a function or callable", fnName.toBasicString())), currentAddressInfo); + } + FunctionStruct fnStruct = fnVal.as().getValue(); + // check argument, all types of parameters + Ast::FunctionParameters fnParas = fnStruct.paras; + Ast::FunctionArguments fnArgs = fnCall->arg; + if (fnArgs.getLength() < fnParas.posParas.size() || fnArgs.getLength() > fnParas.size()) + { + static constexpr char ArgumentMismatchErrorName[] = "ArgumentMismatchError"; + throw EvaluatorError(FStringView(std::format("Function '{}' expects {} to {} arguments, but {} were provided", fnName.toBasicString(), fnParas.posParas.size(), fnParas.size(), fnArgs.getLength())), currentAddressInfo); + } + + Ast::FunctionCallArgs evaluatedArgs; + + // positional parameters type check + size_t i; + for (i = 0; i < fnParas.posParas.size(); i++) + { + TypeInfo expectedType(fnParas.posParas[i].second); // look up type info, if exists a type with the name, use it, else throw + Value argVal = eval(fnArgs.argv[i]); + TypeInfo actualType = argVal.getTypeInfo(); + if (expectedType != actualType and expectedType != ValueType::Any) + { + static constexpr char ArgumentTypeMismatchErrorName[] = "ArgumentTypeMismatchError"; + throw EvaluatorError(FStringView(std::format("In function '{}', argument '{}' expects type '{}', but got type '{}'", fnName.toBasicString(), fnParas.posParas[i].first.toBasicString(), expectedType.toString().toBasicString(), actualType.toString().toBasicString())), currentAddressInfo); + } + evaluatedArgs.argv.push_back(argVal); + } + // default parameters type check + for (; i < fnArgs.getLength(); i++) + { + size_t defParamIndex = i - fnParas.posParas.size(); + TypeInfo expectedType = fnParas.defParas[defParamIndex].second.first; + + Value defaultVal = eval(fnParas.defParas[defParamIndex].second.second); + if (expectedType != defaultVal.getTypeInfo() and expectedType != ValueType::Any) + { + static constexpr char DefaultParameterTypeErrorName[] = "DefaultParameterTypeError"; + throw EvaluatorError(FStringView(std::format("In function '{}', default parameter '{}' has type '{}', which does not match the expected type '{}'", fnName.toBasicString(), fnParas.defParas[defParamIndex].first.toBasicString(), defaultVal.getTypeInfo().toString().toBasicString(), expectedType.toString().toBasicString())), currentAddressInfo); + } + + Value argVal = eval(fnArgs.argv[i]); + TypeInfo actualType = argVal.getTypeInfo(); + if (expectedType != actualType and expectedType != ValueType::Any) + { + static constexpr char ArgumentTypeMismatchErrorName[] = "ArgumentTypeMismatchError"; + throw EvaluatorError(FStringView(std::format("In function '{}', argument '{}' expects type '{}', but got type '{}'", fnName.toBasicString(), fnParas.defParas[defParamIndex].first.toBasicString(), expectedType.toString().toBasicString(), actualType.toString().toBasicString())), currentAddressInfo); + } + evaluatedArgs.argv.push_back(argVal); + } + // default parameters filling + for (; i < fnParas.size(); i++) + { + size_t defParamIndex = i - fnParas.posParas.size(); + Value defaultVal = eval(fnParas.defParas[defParamIndex].second.second); + evaluatedArgs.argv.push_back(defaultVal); + } + // create new context for function call + auto newContext = std::make_shared(FString(std::format("", fnName.toBasicString())), currentContext); + auto previousContext = currentContext; + currentContext = newContext; + // define parameters in new context + for (size_t j = 0; j < fnParas.size(); j++) + { + FString paramName; + TypeInfo paramType; + if (j < fnParas.posParas.size()) + { + paramName = fnParas.posParas[j].first; + paramType = fnParas.posParas[j].second; + } + else + { + size_t defParamIndex = j - fnParas.posParas.size(); + paramName = fnParas.defParas[defParamIndex].first; + paramType = fnParas.defParas[defParamIndex].second.first; + } + AccessModifier argAm = AccessModifier::Const; + currentContext->def(paramName, paramType, argAm, evaluatedArgs.argv[j]); + } + // execute function body + Value retVal = Value::getNullInstance(); + for (const auto &stmt : fnStruct.body->stmts) + { + StatementResult sr = evalStatement(stmt); + if (sr.shouldReturn()) + { + retVal = sr.result; + break; + } + } + currentContext = previousContext; + if (fnStruct.retType != retVal.getTypeInfo() and fnStruct.retType != ValueType::Any) + { + static constexpr char ReturnTypeMismatchErrorName[] = "ReturnTypeMismatchError"; + throw EvaluatorError(FStringView(std::format("Function '{}' expects return type '{}', but got type '{}'", fnName.toBasicString(), fnStruct.retType.toString().toBasicString(), retVal.getTypeInfo().toString().toBasicString())), currentAddressInfo); + } + return retVal; + } + case AstType::ListExpr: { + auto listexpr = std::dynamic_pointer_cast(exp); + } + default: + throw RuntimeError(FStringView("Unknown expression type:" + std::to_string(static_cast(exp->getType())))); + return Value::getNullInstance(); + } + } + + StatementResult Evaluator::evalStatement(const Ast::Statement &stmt) + { + using Fig::Ast::AstType; + switch (stmt->getType()) + { + case AstType::VarDefSt: { + auto varDef = std::dynamic_pointer_cast(stmt); + if (currentContext->contains(varDef->name)) + { + static constexpr char RedeclarationErrorName[] = "RedeclarationError"; + throw EvaluatorError(FStringView(std::format("Variable '{}' already defined in this scope", varDef->name.toBasicString())), currentAddressInfo); + } + Value val; + TypeInfo varTypeInfo; + if (varDef->typeName == Parser::varDefTypeFollowed) + { + // has expr + val = eval(varDef->expr); + varTypeInfo = val.getTypeInfo(); + } + else if (varDef->expr) + { + val = eval(varDef->expr); + if (varDef->typeName != ValueType::Any.name) + { + TypeInfo expectedType(varDef->typeName); + TypeInfo actualType = val.getTypeInfo(); + if (expectedType != actualType and expectedType != ValueType::Any) + { + static constexpr char VariableTypeMismatchErrorName[] = "VariableTypeMismatchError"; + throw EvaluatorError(FStringView(std::format("Variable '{}' expects type '{}', but got type '{}'", varDef->name.toBasicString(), expectedType.toString().toBasicString(), actualType.toString().toBasicString())), currentAddressInfo); + } + } + } + else if (!varDef->typeName.empty()) + { + varTypeInfo = TypeInfo(varDef->typeName); // may throw + val = Value::defaultValue(varTypeInfo); + } + AccessModifier am = (varDef->isPublic ? (varDef->isConst ? AccessModifier::PublicConst : AccessModifier::Public) : (varDef->isConst ? AccessModifier::Const : AccessModifier::Normal)); + currentContext->def(varDef->name, varTypeInfo, am, val); + return StatementResult::normal(); + } + case AstType::ExpressionStmt: { + auto exprSt = std::dynamic_pointer_cast(stmt); + eval(exprSt->exp); + return StatementResult::normal(); + }; + case AstType::BlockStatement: { + auto blockSt = std::dynamic_pointer_cast(stmt); + auto newContext = std::make_shared(FString(std::format("", blockSt->getAAI().line, blockSt->getAAI().column)), currentContext); + auto previousContext = currentContext; + currentContext = newContext; + StatementResult lstResult = StatementResult::normal(); + for (const auto &s : blockSt->stmts) + { + StatementResult sr = evalStatement(s); + if (!sr.isNormal()) + { + lstResult = sr; + break; + } + } + currentContext = previousContext; + return lstResult; + }; + case AstType::FunctionDefSt: { + auto fnDef = std::dynamic_pointer_cast(stmt); + if (currentContext->contains(fnDef->name)) + { + static constexpr char RedeclarationErrorName[] = "RedeclarationError"; + throw EvaluatorError(FStringView(std::format("Function '{}' already defined in this scope", fnDef->name.toBasicString())), currentAddressInfo); + } + AccessModifier am = (fnDef->isPublic ? AccessModifier::PublicConst : AccessModifier::Const); + currentContext->def( + fnDef->name, + ValueType::Function, + am, + Value(Function( + fnDef->paras, + TypeInfo(fnDef->retType), + fnDef->body))); + return StatementResult::normal(); + }; + case AstType::StructSt: { + auto stDef = std::dynamic_pointer_cast(stmt); + if (currentContext->contains(stDef->name)) + { + static constexpr char RedeclarationErrorName[] = "RedeclarationError"; + throw EvaluatorError(FStringView(std::format("Structure '{}' already defined in this scope", stDef->name.toBasicString())), currentAddressInfo); + } + std::vector fields; + std::vector _fieldNames; + for (Ast::StructDefField field : stDef->fields) + { + if (Utils::vectorContains(field.fieldName, _fieldNames)) + { + static constexpr char RedeclarationErrorName[] = "RedeclarationError"; + throw EvaluatorError(FStringView(std::format("Field '{}' already defined in structure '{}'", field.fieldName.toBasicString(), stDef->name.toBasicString())), currentAddressInfo); + } + fields.push_back(Field(field.am, field.fieldName, TypeInfo(field.tiName), field.defaultValueExpr)); + } + ContextPtr defContext(currentContext); + AccessModifier am = (stDef->isPublic ? AccessModifier::PublicConst : AccessModifier::Const); + currentContext->def( + stDef->name, + ValueType::StructType, + am, + Value(StructType( + defContext, + fields))); + return StatementResult::normal(); + } + case AstType::VarAssignSt: { + auto varAssign = std::dynamic_pointer_cast(stmt); + if (!currentContext->contains(varAssign->varName)) + { + static constexpr char VariableNotFoundErrorName[] = "VariableNotFoundError"; + throw EvaluatorError(FStringView(std::format("Variable '{}' not defined", varAssign->varName.toBasicString())), currentAddressInfo); + } + if (!currentContext->isVariableMutable(varAssign->varName)) + { + static constexpr char ConstAssignmentErrorName[] = "ConstAssignmentError"; + throw EvaluatorError(FStringView(std::format("Cannot assign to constant variable '{}'", varAssign->varName.toBasicString())), currentAddressInfo); + } + Value val = eval(varAssign->valueExpr); + if (currentContext->getTypeInfo(varAssign->varName) != ValueType::Any) + { + TypeInfo expectedType = currentContext->getTypeInfo(varAssign->varName); + TypeInfo actualType = val.getTypeInfo(); + if (expectedType != actualType) + { + static constexpr char VariableTypeMismatchErrorName[] = "VariableTypeMismatchError"; + throw EvaluatorError(FStringView(std::format("assigning: Variable '{}' expects type '{}', but got type '{}'", varAssign->varName.toBasicString(), expectedType.toString().toBasicString(), actualType.toString().toBasicString())), currentAddressInfo); + } + } + currentContext->set(varAssign->varName, val); + return StatementResult::normal(); + }; + case AstType::IfSt: { + auto ifSt = std::dynamic_pointer_cast(stmt); + Value condVal = eval(ifSt->condition); + if (condVal.getTypeInfo() != ValueType::Bool) + { + static constexpr char ConditionTypeErrorName[] = "ConditionTypeError"; + throw EvaluatorError(FStringView(u8"If condition must be boolean"), currentAddressInfo); + } + if (condVal.as().getValue()) + { + return evalStatement(ifSt->body); + } + // else + for (const auto &elif : ifSt->elifs) + { + Value elifCondVal = eval(elif->condition); + if (elifCondVal.getTypeInfo() != ValueType::Bool) + { + static constexpr char ConditionTypeErrorName[] = "ConditionTypeError"; + throw EvaluatorError(FStringView(u8"Else-if condition must be boolean"), currentAddressInfo); + } + if (elifCondVal.as().getValue()) + { + return evalStatement(elif->body); + } + } + if (ifSt->els) + { + return evalStatement(ifSt->els->body); + } + return StatementResult::normal(); + }; + case AstType::WhileSt: { + auto whileSt = std::dynamic_pointer_cast(stmt); + while (true) + { + Value condVal = eval(whileSt->condition); + if (condVal.getTypeInfo() != ValueType::Bool) + { + static constexpr char ConditionTypeErrorName[] = "ConditionTypeError"; + throw EvaluatorError(FStringView(u8"While condition must be boolean"), currentAddressInfo); + } + if (!condVal.as().getValue()) + { + break; + } + StatementResult sr = evalStatement(whileSt->body); + if (sr.shouldReturn()) + { + return sr; + } + if (sr.shouldBreak()) + { + break; + } + if (sr.shouldContinue()) + { + continue; + } + } + return StatementResult::normal(); + }; + case AstType::ReturnSt: { + if (!currentContext->parent) + { + static constexpr char ReturnOutsideFunctionErrorName[] = "ReturnOutsideFunctionError"; + throw EvaluatorError(FStringView(u8"'return' statement outside function"), currentAddressInfo); + } + std::shared_ptr fc = currentContext; + while (fc->parent) + { + if (fc->getScopeName().find(u8"parent; + } + if (fc->getScopeName().find(u8"(FStringView(u8"'return' statement outside function"), currentAddressInfo); + } + auto returnSt = std::dynamic_pointer_cast(stmt); + return StatementResult::returnFlow(eval(returnSt->retValue)); + }; + default: + throw RuntimeError(FStringView(std::string("Unknown statement type:") + magic_enum::enum_name(stmt->getType()).data())); + } + return StatementResult::normal(); + } + + void Evaluator::run() + { + for (auto ast : asts) + { + currentAddressInfo = ast->getAAI(); + if (std::dynamic_pointer_cast(ast)) + { + auto exprAst = std::dynamic_pointer_cast(ast); + Ast::Expression exp = exprAst->exp; + eval(exp); + } + else if (dynamic_cast(ast.get())) + { + auto stmtAst = std::dynamic_pointer_cast(ast); + evalStatement(stmtAst); + } + else + { + throw RuntimeError(FStringView(u8"Unknown AST type")); + } + } + } + + void Evaluator::printStackTrace() const + { + if (currentContext) + currentContext->printStackTrace(); + else + std::cerr << "[STACK TRACE] (No context has been loaded)\n"; + } +} // namespace Fig \ No newline at end of file diff --git a/src/lexer.cpp b/src/lexer.cpp new file mode 100644 index 0000000..2e22e1d --- /dev/null +++ b/src/lexer.cpp @@ -0,0 +1,524 @@ +#include +#include +#include +#include + +#include +#include + +namespace Fig +{ + + const std::unordered_map Lexer::symbol_map{ + // 双字符 + {FString(u8"=="), TokenType::Equal}, + {FString(u8"!="), TokenType::NotEqual}, + {FString(u8"<="), TokenType::LessEqual}, + {FString(u8">="), TokenType::GreaterEqual}, + {FString(u8"<<"), TokenType::ShiftLeft}, + {FString(u8">>"), TokenType::ShiftRight}, + {FString(u8"+="), TokenType::PlusEqual}, + {FString(u8"-="), TokenType::MinusEqual}, + {FString(u8"*="), TokenType::AsteriskEqual}, + {FString(u8"/="), TokenType::SlashEqual}, + {FString(u8"%="), TokenType::PercentEqual}, + {FString(u8"^="), TokenType::CaretEqual}, + {FString(u8"++"), TokenType::DoublePlus}, + {FString(u8"--"), TokenType::DoubleMinus}, + {FString(u8"&&"), TokenType::DoubleAmpersand}, + {FString(u8"||"), TokenType::DoublePipe}, + {FString(u8":="), TokenType::Walrus}, + {FString(u8"**"), TokenType::Power}, + {FString(u8"->"), TokenType::RightArrow}, + + // 单字符 + {FString(u8"+"), TokenType::Plus}, + {FString(u8"-"), TokenType::Minus}, + {FString(u8"*"), TokenType::Asterisk}, + {FString(u8"/"), TokenType::Slash}, + {FString(u8"%"), TokenType::Percent}, + {FString(u8"^"), TokenType::Caret}, + {FString(u8"&"), TokenType::Ampersand}, + {FString(u8"|"), TokenType::Pipe}, + {FString(u8"~"), TokenType::Tilde}, + {FString(u8"="), TokenType::Assign}, + {FString(u8"<"), TokenType::Less}, + {FString(u8">"), TokenType::Greater}, + {FString(u8"."), TokenType::Dot}, + {FString(u8","), TokenType::Comma}, + {FString(u8":"), TokenType::Colon}, + {FString(u8";"), TokenType::Semicolon}, + {FString(u8"'"), TokenType::SingleQuote}, + {FString(u8"\""), TokenType::DoubleQuote}, + {FString(u8"("), TokenType::LeftParen}, + {FString(u8")"), TokenType::RightParen}, + {FString(u8"["), TokenType::LeftBracket}, + {FString(u8"]"), TokenType::RightBracket}, + {FString(u8"{"), TokenType::LeftBrace}, + {FString(u8"}"), TokenType::RightBrace}}; + + const std::unordered_map Lexer::keyword_map{ + {FString(u8"and"), TokenType::And}, + {FString(u8"or"), TokenType::Or}, + {FString(u8"not"), TokenType::Not}, + {FString(u8"import"), TokenType::Import}, + {FString(u8"fun"), TokenType::Function}, + {FString(u8"var"), TokenType::Variable}, + {FString(u8"const"), TokenType::Const}, + {FString(u8"final"), TokenType::Final}, + {FString(u8"while"), TokenType::While}, + {FString(u8"for"), TokenType::For}, + {FString(u8"if"), TokenType::If}, + {FString(u8"else"), TokenType::Else}, + {FString(u8"struct"), TokenType::Struct}, + {FString(u8"interface"), TokenType::Interface}, + {FString(u8"implement"), TokenType::Implement}, + {FString(u8"public"), TokenType::Public}, + {FString(u8"return"), TokenType::Return}, + + + // {FString(u8"Null"), TokenType::TypeNull}, + // {FString(u8"Int"), TokenType::TypeInt}, + // {FString(u8"String"), TokenType::TypeString}, + // {FString(u8"Bool"), TokenType::TypeBool}, + // {FString(u8"Double"), TokenType::TypeDouble}, + }; + void Lexer::skipLine() + { + while (*it != U'\n' and hasNext()) + { + next(); + } + next(); // skip '\n' + ++line; + } + Token Lexer::scanIdentifier() + { + FString identifier; + + while (hasNext()) + { + UTF8Char c = *it; + if (c.isAlnum() || c == U'_') + { + identifier += c.getString(); + next(); + } + else + { + break; + } + } + if (this->keyword_map.contains(identifier)) + { + return Token(identifier, this->keyword_map.at(identifier)); + } + else if (identifier == u8"true" || identifier == u8"false") + { + return Token(identifier, TokenType::LiteralBool); + } + else if (identifier == u8"null") + { + // null instance + return Token(identifier, TokenType::LiteralNull); + } + if (keyword_map.contains(Utils::toLower(identifier))) + { + pushWarning(1, identifier); // Identifier is too similar to a keyword or a primitive type + } + if (identifier.length() <= 1) + { + pushWarning(2, identifier); // The identifier is too abstract + } + return Token(identifier, TokenType::Identifier); + } + Token Lexer::scanString() + { + FString str; + bool unterminated = true; + size_t str_start_col = it.column() - 1; + while (hasNext()) + { + UTF8Char c = *it; + if (c == U'"' || c == U'\n') + { + next(); + unterminated = false; + break; + } + else if (c == U'\\') // c is '\' + { + if (it.isEnd()) + { + error = SyntaxError(u8"Unterminated FString", this->line, it.column()); + return IllegalTok; + } + next(); + UTF8Char ec = *it; + if (ec == U'n') + { + next(); + str += u8"\n"; + } + else if (ec == U't') + { + next(); + str += u8"\t"; + } + else if (ec == U'v') + { + next(); + str += u8"\v"; + } + else if (ec == U'b') + { + next(); + str += u8"\b"; + } + else if (ec == U'"') + { + next(); + str += u8"\""; + } + else if (ec == U'\'') + { + next(); + str += u8"'"; + } + else + { + error = SyntaxError(FStringView( + std::format( + "Unsupported escape character: {}", + FString(ec.getString()).toBasicString())), + this->line, + it.column()); + return IllegalTok; + } + } + else + { + str += c.getString(); + next(); + } + } + if (unterminated) + { + error = SyntaxError(u8"Unterminated FString", this->line, str_start_col); + return IllegalTok; + } + return Token(str, TokenType::LiteralString); + } + Token Lexer::scanRawString() + { + FString str; + bool unterminated = true; + size_t str_start_col = it.column() - 1; + while (hasNext()) + { + UTF8Char c = *it; + if (c == U'"' || c == U'\n') + { + next(); + unterminated = false; + break; + } + else + { + str += c.getString(); + next(); + } + } + if (unterminated) + { + error = SyntaxError(u8"Unterminated FString", this->line, str_start_col); + return IllegalTok; + } + return Token(str, TokenType::LiteralString); + } + Token Lexer::scanMultilineString() + { + FString str; + bool unterminated = true; + + uint8_t end = 0; + size_t str_start_col = it.column() - 1; + while (hasNext()) + { + UTF8Char c = *it; + if (c == U'"') + { + if (end == 3) + { + next(); + unterminated = false; + break; + } + end++; + next(); + continue; + } + else if (c == U'\\') // c is '\' + { + if (it.isEnd()) + { + error = SyntaxError(u8"Unterminated FString", this->line, it.column()); + return IllegalTok; + } + next(); + UTF8Char ec = *it; + if (ec == U'n') + { + next(); + str += u8"\n"; + } + else if (ec == U't') + { + next(); + str += u8"\t"; + } + else if (ec == U'v') + { + next(); + str += u8"\v"; + } + else if (ec == U'b') + { + next(); + str += u8"\b"; + } + else if (ec == U'"') + { + next(); + str += u8"\""; + } + else if (ec == U'\'') + { + next(); + str += u8"'"; + } + else if (ec == U'\\') + { + next(); + str += u8"\\"; + } + else + { + error = SyntaxError(FStringView( + std::format( + "Unsupported escape character: {}", + FString(ec.getString()).toBasicString())), + this->line, + it.column()); + return IllegalTok; + } + } + else + { + str += c.getString(); + } + end = 0; + } + if (unterminated) + { + error = SyntaxError(u8"Unterminated FString", this->line, str_start_col); + return IllegalTok; + } + return Token(str, TokenType::LiteralString); + } + Token Lexer::scanNumber() + { + FString numStr; + bool hasPoint = false; + // 负号(减号) 直接交由 scanSymbol处理,在parser中被分类->与数字结合/变为操作数 + while (hasNext()) + { + UTF8Char ch = *it; + if (ch.isDigit() or ch == U'e') // . / e / - for scientific counting + { + numStr += ch.getString(); + next(); + } + else if (ch == U'-' and numStr.ends_with(U'-')) + { + numStr += ch.getString(); + next(); + } + else if (ch == U'.' and not hasPoint) + { + hasPoint = true; + numStr += ch.getString(); + next(); + } + else + { + break; + } + } + // Numbers in Fig-lang + /* + 114514 + 1145.14 + 1.14e3 -> 1140 + 1.14e-3 -> 0.00114 + .3 -> 0.3 + */ + // checking legality + if ((*numStr.end()) == u'e') // e 后面必须跟整数表示科学计数 + { + error = SyntaxError(FStringView( + std::format("Ellegal number literal: {}", numStr.toBasicString())), + this->line, it.column()); + return IllegalTok; + } + return Token(numStr, TokenType::LiteralNumber); + } + Token Lexer::scanSymbol() + { + FString sym; + UTF8Char ch = *it; + + sym += ch.getString(); + UTF8Char peek = UTF8Char(u8""); + if (hasNext() and (peek = it.peek()).isPunct()) // 窥探下一个操作符 + { + FString symd = FString(sym + peek.getString()); + if (this->symbol_map.contains(symd)) + { + // Operator length is 2 + next(); + sym = symd; + } + // Operator length is 1 + else if (!this->symbol_map.contains(sym)) + { + // check legality + error = SyntaxError(FStringView( + std::format("No such a operator: {}", sym.toBasicString())), + this->line, it.column()); + } + } + next(); + return Token(sym, this->symbol_map.at(sym)); // const object 'symbol_map', operator[] call is invalid + } + Token Lexer::scanComments() + { + // entry: when iterator current char is '/' and peek is '/' or '*' + // current char is '/' + FString comment; + if (it.peek() == U'/') + { + next(); + next(); + UTF8Char c = *it; + while (c != U'\n' and hasNext()) + { + comment += c.getString(); + next(); + } + next(); + } + else + { + next(); + next(); + UTF8Char c = *it; + bool terminated = false; + while (hasNext()) + { + if (c == U'*' and hasNext() and it.peek() == U'/') + { + next(); // skip '*' + next(); // skip '/' + next(); // to next char + terminated = true; + break; + } + else + { + comment += c.getString(); + next(); + } + } + if (!terminated) + { + error = SyntaxError(FStringView(u8"Unterminated multiline comment"), this->line, it.column()); + next(); + return IllegalTok; + } + } + return Token(comment, TokenType::Comments); + } + Token Lexer::nextToken() + { + if (!hasNext()) + { + return EOFTok; + } + UTF8Char ch = *it; + while (ch.isSpace()) + { + next(); + ch = *it; + if (!hasNext()) + { + return EOFTok.setPos(getCurrentLine(), getCurrentColumn()); + } + } + last_line = getCurrentLine(); + last_column = getCurrentColumn(); + if (ch == U'r' and hasNext() and it.peek() == U'"') + { + // r"" + // raw FString + next(); + next(); + return scanRawString().setPos(last_line, last_column); + } + if (ch.isAlpha() || ch == U'_') + { + return scanIdentifier().setPos(last_line, last_column); + } + else if (ch == U'"') + { + next(); + return scanString().setPos(last_line, last_column); + } + else if (ch.isDigit()) + { + return scanNumber().setPos(last_line, last_column); + } + else if (ch == U'/') + { + UTF8Char c{u8""}; + if (!hasNext()) + { + next(); + return Token(u8"/", this->symbol_map.at(u8"/")).setPos(last_line, last_column); + } + c = it.peek(); + if (c != U'/' and c != U'*') + { + next(); + return Token(u8"/", this->symbol_map.at(u8"/")).setPos(last_line, last_column); + } + return scanComments().setPos(last_line, last_column); + } + else if (ch.isPunct()) + { + return scanSymbol().setPos(last_line, last_column); + } + else + { + error = SyntaxError(FStringView( + std::format("Cannot tokenize char: '{}'", FString(ch.getString()).toBasicString())), + this->line, it.column()); + if (hasNext()) + { + next(); + } + return IllegalTok.setPos(last_line, last_column); + } + } + +} // namespace Fig \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a652563 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,187 @@ +/* + ███████████ █████ █████ ██████████ ███████████ █████ █████████ █████ █████████ ██████ █████ █████████ █████ █████ █████████ █████████ ██████████ +░█░░░███░░░█░░███ ░░███ ░░███░░░░░█ ░░███░░░░░░█░░███ ███░░░░░███ ░░███ ███░░░░░███ ░░██████ ░░███ ███░░░░░███░░███ ░░███ ███░░░░░███ ███░░░░░███░░███░░░░░█ +░ ░███ ░ ░███ ░███ ░███ █ ░ ░███ █ ░ ░███ ███ ░░░ ░███ ░███ ░███ ░███░███ ░███ ███ ░░░ ░███ ░███ ░███ ░███ ███ ░░░ ░███ █ ░ + ░███ ░███████████ ░██████ ░███████ ░███ ░███ ░███ ░███████████ ░███░░███░███ ░███ ░███ ░███ ░███████████ ░███ ░██████ + ░███ ░███░░░░░███ ░███░░█ ░███░░░█ ░███ ░███ █████ ░███ ░███░░░░░███ ░███ ░░██████ ░███ █████ ░███ ░███ ░███░░░░░███ ░███ █████ ░███░░█ + ░███ ░███ ░███ ░███ ░ █ ░███ ░ ░███ ░░███ ░░███ ░███ █ ░███ ░███ ░███ ░░█████ ░░███ ░░███ ░███ ░███ ░███ ░███ ░░███ ░░███ ░███ ░ █ + █████ █████ █████ ██████████ █████ █████ ░░█████████ ███████████ █████ █████ █████ ░░█████ ░░█████████ ░░████████ █████ █████ ░░█████████ ██████████ + ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░ + + + .---. + . __.....__ .--. | | _..._ __.....__ + .'| .-'' '. _.._ |__| .--./) | | .' '. .--./) .--./) .-'' '. + .| < | / .-''"'-. `. .' .._|.--. /.''\\ | | . .-. . /.''\\ /.''\\ / .-''"'-. `. + .' |_ | | / /________\ \ | ' | || | | | | | __ | ' ' || | | | __ | | | |/ /________\ \ + .' || | .'''-. | | __| |__ | | \`-' / | | .:--.'. | | | | \`-' / _ _ .:--.'. \`-' / | | +'--. .-'| |/.'''. \\ .-------------' |__ __| | | /("'` | |/ | \ | | | | | /("'` | ' / | / | \ | /("'` \ .-------------' + | | | / | | \ '-.____...---. | | | | \ '---. | |`" __ | | | | | | \ '---. .' | .' | `" __ | | \ '---. \ '-.____...---. + | | | | | | `. .' | | |__| /'""'.\ | | .'.''| | | | | | /'""'.\ / | / | .'.''| | /'""'.\ `. .' + | '.'| | | | `''-...... -' | | || || '---'/ / | |_| | | | || ||| `'. | / / | |_|| || `''-...... -' + | / | '. | '. | | \'. __// \ \._,\ '/| | | | \'. __// ' .'| '/\ \._,\ '/\'. __// + `'-' '---' '---' |_| `'---' `--' `" '--' '--' `'---' `-' `--' `--' `" `'---' + +Copyright (C) 2020-2025 PuqiAR + +This software is licensed under the MIT License. See LICENSE.txt for details. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static size_t addressableErrorCount = 0; +static size_t unaddressableErrorCount = 0; + +std::vector splitSource(FString source) +{ + UTF8Iterator it(source); + std::vector lines; + FString currentLine; + while (!it.isEnd()) + { + UTF8Char c = *it; + if (c == U'\n') + { + lines.push_back(currentLine); + currentLine = FString(u8""); + } + else + { + currentLine += c.getString(); + } + ++it; + } + if (!currentLine.empty()) + { + lines.push_back(currentLine); + } + return lines; +} + +int main(int argc, char **argv) +{ + argparse::ArgumentParser program("Fig Interpreter", Fig::Core::VERSION.data()); + program.add_argument("source") + .help("source file to be interpreted"); + // interpreter + + try + { + program.parse_args(argc, argv); + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + return 1; + } + + Fig::FString sourcePath(program.get("source")); + std::ifstream file(sourcePath.toBasicString()); + if (!file.is_open()) + { + std::cerr << "Could not open file: " << sourcePath.toBasicString() << '\n'; + return 1; + } + std::string source((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + Fig::Lexer lexer((Fig::FString(source))); + Fig::Parser parser(lexer); + std::vector ast; + + std::vector sourceLines = splitSource(Fig::FString(source)); + + try + { + ast = parser.parseAll(); + } + catch (const Fig::AddressableError &e) + { + addressableErrorCount++; + ErrorLog::logAddressableError(e, sourcePath, sourceLines); + return 1; + } + catch (const Fig::UnaddressableError &e) + { + unaddressableErrorCount++; + ErrorLog::logUnaddressableError(e); + return 1; + } + catch (const std::exception &e) + { + std::cerr << "uncaught exception of: " << e.what() << '\n'; + return 1; + } + + // Token tok; + // while ((tok = lexer.nextToken()).getType() != TokenType::EndOfFile) + // { + // std::println("{}", tok.toString().toBasicString()); + // } + + // AstPrinter printer; + // std::print(" AST:\n"); + // for (const auto &node : ast) + // { + // printer.print(node); + // } + + Fig::Evaluator evaluator(ast); + try + { + evaluator.run(); + } + catch (const Fig::AddressableError &e) + { + addressableErrorCount++; + ErrorLog::logAddressableError(e, sourcePath, sourceLines); + evaluator.printStackTrace(); + return 1; + } + catch (const Fig::UnaddressableError &e) + { + unaddressableErrorCount++; + ErrorLog::logUnaddressableError(e); + evaluator.printStackTrace(); + return 1; + } + + // try + // { + // std::vector ast = parser.parseAll(); + // AstPrinter printer; + + // std::print(" AST:\n"); + // for (const auto &node : ast) + // { + // printer.print(node); + // } + + // Fig::Evaluator evaluator(ast); + // evaluator.run(); + // } + // catch (const Fig::AddressableError &e) + // { + // std::cerr << e.what() << '\n'; + // return 1; + // } + // catch (const Fig::UnaddressableError &e) + // { + // std::cerr << e.what() << '\n'; + // return 1; + // } + // catch (const std::exception &e) + // { + // std::cerr << e.what() << '\n'; + // return 1; + // } +} diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..2f2d382 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,848 @@ +#include + +namespace Fig +{ + // Operator : pair + + const std::unordered_map> Parser::opPrecedence = { + // 算术 + {Ast::Operator::Add, {10, 11}}, + {Ast::Operator::Subtract, {10, 11}}, + {Ast::Operator::Multiply, {20, 21}}, + {Ast::Operator::Divide, {20, 21}}, + {Ast::Operator::Modulo, {20, 21}}, + {Ast::Operator::Power, {30, 29}}, + + // 逻辑 + {Ast::Operator::And, {5, 6}}, + {Ast::Operator::Or, {4, 5}}, + {Ast::Operator::Not, {30, 31}}, // 一元 + + // 比较 + {Ast::Operator::Equal, {7, 8}}, + {Ast::Operator::NotEqual, {7, 8}}, + {Ast::Operator::Less, {8, 9}}, + {Ast::Operator::LessEqual, {8, 9}}, + {Ast::Operator::Greater, {8, 9}}, + {Ast::Operator::GreaterEqual, {8, 9}}, + + // 位运算 + {Ast::Operator::BitAnd, {6, 7}}, + {Ast::Operator::BitOr, {4, 5}}, + {Ast::Operator::BitXor, {5, 6}}, + {Ast::Operator::BitNot, {30, 31}}, // 一元 + {Ast::Operator::ShiftLeft, {15, 16}}, + {Ast::Operator::ShiftRight, {15, 16}}, + + // 海象运算符 + {Ast::Operator::Walrus, {2, 1}}, // 右结合 + + // 点运算符 + {Ast::Operator::Dot, {40, 41}}, + }; + + Ast::VarDef Parser::__parseVarDef(bool isPublic) + { + // entry: current is keyword `var` or `const` + bool isConst = (currentToken().getType() == TokenType::Const ? true : false); + next(); + expect(TokenType::Identifier); + FString name = currentToken().getValue(); + next(); + FString tiName = ValueType::Any.name; + bool hasSpecificType = false; + if (isThis(TokenType::Colon)) // : + { + expectPeek(TokenType::Identifier, FString(u8"Type name")); + next(); + tiName = currentToken().getValue(); + next(); + hasSpecificType = true; + } + if (isThis(TokenType::Semicolon)) + { + next(); + return makeAst(isPublic, isConst, name, tiName, nullptr); + } + if (!isThis(TokenType::Assign) and !isThis(TokenType::Walrus)) expect(TokenType::Assign, u8"assign or walrus"); + if (isThis(TokenType::Walrus)) + { + if (hasSpecificType) throwAddressableError(FStringView(u8"")); + tiName = Parser::varDefTypeFollowed; + } + next(); + Ast::Expression exp = parseExpression(0); + expect(TokenType::Semicolon); + next(); + return makeAst(isPublic, isConst, name, tiName, exp); + } + + Value Parser::__parseValue() + { + FString _val = currentToken().getValue(); + if (currentToken().getType() == TokenType::LiteralNumber) + { + if (_val.contains(u8'.') || _val.contains(u8'e')) + { + // 非整数 + ValueType::DoubleClass d; + try + { + d = std::stod(_val.toBasicString()); + } + catch (...) + { + throwAddressableError(FStringView(u8"Illegal number literal")); + } + return Value(d); + } + else + { + // 整数 + ValueType::IntClass i; + try + { + i = std::stoi(_val.toBasicString()); + } + catch (...) + { + throwAddressableError(FStringView(u8"Illegal number literal")); + } + return Value(i); + } + } + else if (currentToken().getType() == TokenType::LiteralString) + { + return Value(_val); + } + else if (currentToken().getType() == TokenType::LiteralBool) + { + return Value((_val == u8"true" ? true : false)); + } + else if (currentToken().getType() == TokenType::LiteralNull) + { + return Value::getNullInstance(); + } + else + { + throw std::runtime_error(std::string("Internal Error at: ") + std::string(__func__)); + } + } + + Ast::ValueExpr Parser::__parseValueExpr() + { + return Ast::ValueExpr(new Ast::ValueExprAst(__parseValue())); + } + Ast::FunctionParameters Parser::__parseFunctionParameters() + { + // entry: current is Token::LeftParen + // stop: current is `)` next one + // *note: must called when parsing function + + next(); // skip `(` + + Ast::FunctionParameters::PosParasType pp; + Ast::FunctionParameters::DefParasType dp; + while (true) + { + if (isThis(TokenType::RightParen)) + { + next(); + return Ast::FunctionParameters(pp, dp); + } + expect(TokenType::Identifier, FString(u8"Identifier or `)`")); // check current + FString pname = currentToken().getValue(); + next(); // skip pname + if (isThis(TokenType::Assign)) // = + { + next(); + dp.push_back({pname, {ValueType::Any.name, parseExpression(0, TokenType::Comma)}}); + if (isThis(TokenType::Comma)) + { + next(); // only skip `,` when it's there + } + } + else if (isThis(TokenType::Colon)) // : + { + next(); // skip `:` + expect(TokenType::Identifier, FString(u8"Type name")); + FString ti(currentToken().getValue()); + next(); // skip type name + if (isThis(TokenType::Assign)) // = + { + next(); // skip `=` + dp.push_back({pname, {ti, parseExpression(0, TokenType::Comma)}}); + if (isThis(TokenType::Comma)) + { + next(); // only skip `,` when it's there + } + } + else + { + pp.push_back({pname, ti}); + if (isThis(TokenType::Comma)) + { + next(); // only skip `,` when it's there + } + } + } + else + { + pp.push_back({pname, ValueType::Any.name}); + if (isThis(TokenType::Comma)) + { + next(); // only skip `,` when it's there + } + } + } + } + Ast::FunctionDef Parser::__parseFunctionDef(bool isPublic) + { + FString funcName = currentToken().getValue(); + next(); + expect(TokenType::LeftParen); + Ast::FunctionParameters params = __parseFunctionParameters(); + FString retTiName = ValueType::Any.name; + if (isThis(TokenType::RightArrow)) // -> + { + next(); // skip `->` + expect(TokenType::Identifier); + retTiName = currentToken().getValue(); + next(); // skip return type + } + expect(TokenType::LeftBrace); + Ast::BlockStatement body = __parseBlockStatement(); + return makeAst(funcName, params, isPublic, retTiName, body); + } + Ast::StructDef Parser::__parseStructDef(bool isPublic) + { + // entry: current is struct name + FString structName = currentToken().getValue(); + next(); + expect(TokenType::LeftBrace, u8"struct body"); + next(); + bool braceClosed = false; + + /* + public name + public const name + public final name + + const name + final name + + name + */ + + auto __parseStructField = [this](bool isPublic) -> Ast::StructDefField { + AccessModifier am = AccessModifier::Normal; + FString fieldName; + if (isThis(TokenType::Identifier)) + { + fieldName = currentToken().getValue(); + next(); + am = (isPublic ? AccessModifier::Public : AccessModifier::Normal); + } + else if (isThis(TokenType::Final)) + { + next(); + expect(TokenType::Identifier, u8"field name"); + fieldName = currentToken().getValue(); + am = (isPublic ? AccessModifier::PublicFinal : AccessModifier::Final); + } + else if (isThis(TokenType::Const)) + { + next(); + expect(TokenType::Identifier, u8"field name"); + fieldName = currentToken().getValue(); + am = (isPublic ? AccessModifier::PublicConst : AccessModifier::Const); + } + else + { + throwAddressableError(FStringView(std::format("expect field name or field attribute"))); + } + FString tiName = ValueType::Any.name; + if (isThis(TokenType::Colon)) + { + next(); + expect(TokenType::Identifier, u8"type name"); + tiName = currentToken().getValue(); + next(); + } + Ast::Expression initExpr = nullptr; + if (isThis(TokenType::Assign)) + { + next(); + if (isEOF()) throwAddressableError(FStringView(u8"expect an expression")); + initExpr = parseExpression(0); + } + expect(TokenType::Semicolon); + next(); // consume `;` + return Ast::StructDefField(am, fieldName, tiName, initExpr); + }; + std::vector stmts; + std::vector fields; + + while (!isEOF()) + { + if (isThis(TokenType::RightBrace)) + { + braceClosed = true; + next(); // consume `}` + break; + } + if (isThis(TokenType::Identifier)) + { + fields.push_back(__parseStructField(false)); + } + else if (isThis(TokenType::Public)) + { + if (isNext(TokenType::Const) or isNext(TokenType::Final)) + { + next(); + fields.push_back(__parseStructField(true)); + } + else if (isNext(TokenType::Function)) + { + next(); // consume `public` + next(); // consume `function` + stmts.push_back(__parseFunctionDef(true)); + } + else if (isNext(TokenType::Struct)) + { + next(); // consume `public` + next(); // consume `struct` + stmts.push_back(__parseStructDef(true)); + } + else if (isNext(TokenType::Identifier)) + { + next(); // consume `public` + fields.push_back(__parseStructField(true)); + } + else + { + throwAddressableError(FStringView("Invalid syntax")); + } + } + else if (isThis(TokenType::Function)) + { + next(); + stmts.push_back(__parseFunctionDef(false)); + } + else if (isThis(TokenType::Struct)) + { + next(); // consume `struct` + stmts.push_back(__parseStructDef(false)); + } + else if (isThis(TokenType::Const) or isThis(TokenType::Final)) + { + fields.push_back(__parseStructField(false)); + } + else if (isThis(TokenType::Variable)) + { + throwAddressableError(FStringView("Variables are not allowed to be defined within a structure.")); + } + else + { + throwAddressableError(FStringView("Invalid syntax")); + } + } + if (!braceClosed) + { + throwAddressableError(FStringView("braces are not closed")); + } + return makeAst(isPublic, structName, fields, makeAst(stmts)); + } + Ast::Statement Parser::__parseStatement() + { + Ast::Statement stmt; + if (isThis(TokenType::EndOfFile)) { return makeAst(); } + if (isThis(TokenType::Public)) + { + // stmt = __parseVarDef(); + // expect(TokenType::Semicolon); + // next(); + if (isNext(TokenType::Variable) || isNext(TokenType::Const)) + { + next(); // consume `public` + stmt = __parseVarDef(true); + } + else if (isNext(TokenType::Function)) + { + next(); // consume `public` + expectPeek(TokenType::Identifier); + next(); + stmt = __parseFunctionDef(true); + } + else if (isNext(TokenType::Struct)) + { + stmt = __parseStructDef(true); + } + else + { + throwAddressableError(FStringView(u8"Expected `var`, `const`, `function` or `struct` after `public`")); + } + } + else if (isThis(TokenType::Variable) || isThis(TokenType::Const)) + { + stmt = __parseVarDef(false); + } + else if (isThis(TokenType::Function)) + { + expectPeek(TokenType::Identifier, u8"function name"); + next(); + stmt = __parseFunctionDef(false); + } + else if (isThis(TokenType::Struct)) + { + expectPeek(TokenType::Identifier, u8"struct name"); + next(); + stmt = __parseStructDef(false); + } + else if (isThis(TokenType::Identifier) and isNext(TokenType::Assign)) + { + FString varName = currentToken().getValue(); + next(); // consume identifier + stmt = __parseVarAssign(varName); + } + else if (isThis(TokenType::If)) + { + stmt = __parseIf(); + } + else if (isThis(TokenType::Else)) + { + throwAddressableError(FStringView(u8"`else` without matching `if`")); + } + else if (isThis(TokenType::LeftBrace)) + { + stmt = __parseBlockStatement(); + } + else if (isThis(TokenType::While)) + { + stmt = __parseWhile(); + } + else if (isThis(TokenType::Return)) + { + stmt = __parseReturn(); + } + else + { + // expression statement + Ast::Expression exp = parseExpression(0); + expect(TokenType::Semicolon); + next(); + stmt = makeAst(exp); + } + return stmt; + } + Ast::BlockStatement Parser::__parseBlockStatement() + { + // entry: current is `{` + // stop: current is `}` next one + next(); // consume `{` + std::vector stmts; + while (true) + { + if (isThis(TokenType::RightBrace)) + { + next(); + return makeAst(stmts); + } + stmts.push_back(__parseStatement()); + } + } + Ast::VarAssign Parser::__parseVarAssign(FString varName) + { + // entry: current is `=` + next(); // consume `=` + Ast::Expression exp = parseExpression(0); + expect(TokenType::Semicolon); + next(); // consume `;` + return makeAst(varName, exp); + } + + Ast::If Parser::__parseIf() + { + // entry: current is `if` + next(); // consume `if` + Ast::Expression condition; + if (isThis( TokenType::LeftParen)) + { + next(); // consume `(` + condition = parseExpression(0, TokenType::RightParen); + expect(TokenType::RightParen); + next(); // consume `)` + } + else + { + condition = parseExpression(0); + } + // parenthesis is not required + expect(TokenType::LeftBrace); // { + Ast::BlockStatement body = __parseBlockStatement(); + std::vector elifs; + Ast::Else els = nullptr; + while (isThis(TokenType::Else)) + { + next(); // consume `else` + if (isThis(TokenType::If)) + { + // else if + next(); // consume `if` + Ast::Expression elifCondition = parseExpression(0); + expect(TokenType::LeftBrace); // { + Ast::BlockStatement elifBody = __parseBlockStatement(); + elifs.push_back(makeAst(elifCondition, elifBody)); + } + else + { + expect(TokenType::LeftBrace); // { + Ast::BlockStatement elseBody = __parseBlockStatement(); + els = makeAst(elseBody); + break; + } + } + return makeAst(condition, body, elifs, els); + } + Ast::While Parser::__parseWhile() + { + // entry: current is `while` + next(); // consume `while` + Ast::Expression condition = parseExpression(0); + expect(TokenType::LeftBrace); // { + Ast::BlockStatement body = __parseBlockStatement(); + return makeAst(condition, body); + } + Ast::Return Parser::__parseReturn() + { + // entry: current is `return` + next(); // consume `return` + Ast::Expression retValue = parseExpression(0); + expect(TokenType::Semicolon); + next(); // consume `;` + return makeAst(retValue); + } + + Ast::FunctionCall Parser::__parseFunctionCall(FString funcName) + { + // entry: current at '(' + next(); // consume '(' + std::vector args; + if (!isThis(TokenType::RightParen)) + { + while (true) + { + args.push_back(parseExpression(0, TokenType::Comma, TokenType::RightParen)); + if (isThis(TokenType::Comma)) + { + next(); // consume ',' + continue; + } + break; + } + } + expect(TokenType::RightParen); + next(); // consume ')' + return makeAst(funcName, Ast::FunctionArguments(args)); + } + + Ast::VarExpr Parser::__parseVarExpr(FString name) + { + return makeAst(name); + } + + Ast::LambdaExpr Parser::__parseLambdaExpr() + { + // entry: current tok Token::LeftParen and last is Token::Function + /* + Lambda in Fig like: + fun (params) -> {...} + */ + Ast::FunctionParameters params = __parseFunctionParameters(); + // if OK, the current token is `)` next one + FString tiName = ValueType::Any.name; + if (isThis(TokenType::RightArrow)) // -> + { + next(); + expect(TokenType::Identifier); + tiName = currentToken().getValue(); + next(); + } + expect(TokenType::LeftBrace); // `{` + return makeAst(params, tiName, __parseBlockStatement()); + } + + Ast::UnaryExpr Parser::__parsePrefix(Ast::Operator op, Precedence bp) + { + return makeAst(op, parseExpression(bp)); + } + Ast::BinaryExpr Parser::__parseInfix(Ast::Expression lhs, Ast::Operator op, Precedence bp) + { + return makeAst(lhs, op, parseExpression(bp)); + } + + Ast::ListExpr Parser::__parseListExpr() + { + // entry: current is `[` + next(); // consume `[` + std::vector val; + while (!isThis(TokenType::RightBracket)) + { + val.push_back(parseExpression(0, TokenType::RightBracket, TokenType::Comma)); + if (isThis(TokenType::Comma)) + { + next(); // consume `,` + } + } + expect(TokenType::RightBracket); + next(); // consume `]` + return makeAst(val); + } + + Ast::MapExpr Parser::__parseMapExpr() + { + // entry: current is `{` + next(); // consume `{` + std::map val; + while (!isThis(TokenType::RightBrace)) + { + expect(TokenType::Identifier, FString(u8"key (identifier)")); + FString key = currentToken().getValue(); + if (val.contains(key)) throwAddressableError(FStringView(std::format( + "Redefinition of immutable key {} in mapping literal", + key.toBasicString()))); + next(); // consume key + expect(TokenType::Colon); + next(); // consume `:` + val[key] = parseExpression(0, TokenType::RightBrace, TokenType::Comma); + if (isThis(TokenType::Comma)) + { + next(); // consume `,` + } + } + expect(TokenType::RightBrace); + next(); // consume `}` + return makeAst(val); + } + + Ast::InitExpr Parser::__parseInitExpr(FString structName) + { + // entry: current is `{` + next(); // consume `{` + std::vector> args; + /* + 3 ways of calling constructor + .1 Person {"Fig", 1, "IDK"}; + .2 Person {name: "Fig", age: 1, sex: "IDK"}; // can be unordered + .3 Person {name, age, sex}; + */ + uint8_t mode = 0; // 0=undetermined, 1=positional, 2=named, 3=shorthand + + while (!isThis(TokenType::RightBrace)) + { + if (mode == 0) + { + if (isThis(TokenType::Identifier) && isNext(TokenType::Colon)) + { + mode = 2; + } + else if (isThis(TokenType::Identifier) && (isNext(TokenType::Comma) || isNext(TokenType::RightBrace))) + { + mode = 3; + } + else + { + mode = 1; + } + } + + if (mode == 1) + { + // 1 Person {"Fig", 1, "IDK"}; + Ast::Expression expr = parseExpression(0); + args.push_back({FString(), std::move(expr)}); + } + else if (mode == 2) + { + // 2 Person {name: "Fig", age: 1, sex: "IDK"}; + expect(TokenType::Identifier); + FString fieldName = currentToken().getValue(); + next(); // consume identifier + expect(TokenType::Colon); + next(); // consume colon + Ast::Expression expr = parseExpression(0); + args.push_back({fieldName, std::move(expr)}); + } + else if (mode == 3) + { + // 3 Person {name, age, sex}; + expect(TokenType::Identifier); + FString fieldName = currentToken().getValue(); + Ast::Expression expr = makeAst(fieldName); + args.push_back({fieldName, std::move(expr)}); + next(); // consume identifier + } + + if (isThis(TokenType::Comma)) + { + next(); // consume comma + } + else if (!isThis(TokenType::RightBrace)) + { + throwAddressableError(u8"Expected comma or right brace"); + } + } + expect(TokenType::RightBrace); + next(); // consume `}` + return makeAst(structName, args); + } + Ast::Expression Parser::__parseTupleOrParenExpr() + { + next(); + + if (currentToken().getType() == TokenType::RightParen) + { + next(); // consume ')' + return makeAst(); + } + + Ast::Expression firstExpr = parseExpression(0); + + if (currentToken().getType() == TokenType::Comma) + { + std::vector elements; + elements.push_back(firstExpr); + + while (currentToken().getType() == TokenType::Comma) + { + next(); // consume ',' + + if (currentToken().getType() == TokenType::RightParen) + break; + + elements.push_back(parseExpression(0)); + } + + expect(TokenType::RightParen); + next(); // consume ')' + + return makeAst(std::move(elements)); + } + else if (currentToken().getType() == TokenType::RightParen) + { + next(); // consume ')' + return firstExpr; + } + else + { + throwAddressableError(FStringView(u8"Expect ')' or ',' after expression in parentheses")); + } + return nullptr; // to suppress compiler warning + } + Ast::Expression Parser::parseExpression(Precedence bp, TokenType stop, TokenType stop2) + { + Ast::Expression lhs; + Ast::Operator op; + + Token tok = currentToken(); + if (tok == EOFTok) + throwAddressableError(FStringView(u8"Unexpected end of expression")); + if (tok.getType() == stop || tok.getType() == stop2) + { + if (lhs == nullptr) throwAddressableError(FStringView(u8"Expected expression")); + return lhs; + } + if (tok.getType() == TokenType::LeftBracket) + { + lhs = __parseListExpr(); // auto consume + } + else if (tok.getType() == TokenType::LeftParen) + { + lhs = __parseTupleOrParenExpr(); // auto consume + } + else if (tok.getType() == TokenType::LeftBrace) + { + lhs = __parseMapExpr(); // auto consume + } + else if (tok.isLiteral()) + { + lhs = __parseValueExpr(); + next(); + } + else if (tok.isIdentifier()) + { + FString id = tok.getValue(); + next(); + if (currentToken().getType() == TokenType::LeftParen) + { + lhs = __parseFunctionCall(id); // foo(...) + } + else if (currentToken().getType() == TokenType::LeftBrace) + { + lhs = __parseInitExpr(id); // a_struct{init...} + } + else + { + lhs = __parseVarExpr(id); + } + } + else if (isTokenOp(tok) && isOpUnary((op = Ast::TokenToOp.at(tok.getType())))) + { + // prefix + next(); + lhs = __parsePrefix(op, getRightBindingPower(op)); + } + else + { + throwAddressableError(FStringView(u8"Unexpected token in expression")); + } + + // infix / (postfix) ? + while (true) + { + tok = currentToken(); + if (tok.getType() == TokenType::Semicolon || tok == EOFTok) break; + + // ternary + if (tok.getType() == TokenType::Question) + { + next(); // consume ? + Ast::Expression trueExpr = parseExpression(0, TokenType::Colon); + expect(TokenType::Colon); + next(); // consume : + Ast::Expression falseExpr = parseExpression(0, TokenType::Semicolon, stop2); + lhs = makeAst(lhs, trueExpr, falseExpr); + continue; + } + + if (!isTokenOp(tok)) break; + + op = Ast::TokenToOp.at(tok.getType()); + Precedence lbp = getLeftBindingPower(op); + if (bp >= lbp) break; + + next(); // consume op + lhs = __parseInfix(lhs, op, getRightBindingPower(op)); + } + + return lhs; + } + + std::vector Parser::parseAll() + { + output.clear(); + Token tok = currentToken(); + if (tok == EOFTok) + { + return output; + } + + // TODO: Package/Module Import Support + while (!isEOF()) + { + pushNode(__parseStatement()); + } + return output; + } + +} // namespace Fig \ No newline at end of file diff --git a/src/value.cpp b/src/value.cpp new file mode 100644 index 0000000..de82964 --- /dev/null +++ b/src/value.cpp @@ -0,0 +1,39 @@ +#include + +// #include + +namespace Fig +{ + std::map TypeInfo::typeMap = {}; + + TypeInfo::TypeInfo() : // only allow use in evaluate time !! <---- dynamic type system requirement + id(1), name(FString(u8"Any")) {} + TypeInfo::TypeInfo(FString _name, bool reg) + { + static size_t id_count = 0; + name = std::move(_name); + // std::cerr << "TypeInfo constructor called for type name: " << name.toBasicString() << "\n"; + if (reg) + { + typeMap[name] = ++id_count; + id = id_count; + } + else + { + id = typeMap.at(name); // may throw + } + } + + const TypeInfo ValueType::Any(FString(u8"Any"), true); // id: 1 + const TypeInfo ValueType::Null(FString(u8"Null"), true); // id: 2 + const TypeInfo ValueType::Int(FString(u8"Int"), true); // id: 3 + const TypeInfo ValueType::String(FString(u8"String"), true); // id: 4 + const TypeInfo ValueType::Bool(FString(u8"Bool"), true); // id: 5 + const TypeInfo ValueType::Double(FString(u8"Double"), true); // id: 6 + const TypeInfo ValueType::Function(FString(u8"Function"), true); // id: 7 + const TypeInfo ValueType::StructType(FString(u8"StructType"), true); // id: 8 + const TypeInfo ValueType::StructInstance(FString(u8"StructInstance"), true); // id: 9 + const TypeInfo ValueType::List(FString(u8"List"), true); // id: 10 + const TypeInfo ValueType::Map(FString(u8"Map"), true); // id: 11 + const TypeInfo ValueType::Tuple(FString(u8"Tuple"), true); // id: 12 +} // namespace Fig \ No newline at end of file diff --git a/src/waring.cpp b/src/waring.cpp new file mode 100644 index 0000000..93693a8 --- /dev/null +++ b/src/waring.cpp @@ -0,0 +1,9 @@ +#include + +namespace Fig +{ + const std::unordered_map Warning::standardWarnings = { + {1, FString(u8"Identifier is too similar to a keyword or a primitive type")}, + {2, FString(u8"The identifier is too abstract")} + }; +}; \ No newline at end of file diff --git a/test.fig b/test.fig new file mode 100644 index 0000000..7db07aa --- /dev/null +++ b/test.fig @@ -0,0 +1 @@ +var x = 10 \ No newline at end of file diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..433a66c --- /dev/null +++ b/xmake.lua @@ -0,0 +1,21 @@ +add_rules("mode.debug", "mode.release") +add_rules("plugin.compile_commands.autoupdate", {outputdir = ".vscode"}) + +set_policy("run.autobuild", false) + +target("Fig") + set_kind("binary") + set_languages("c++2b") + + set_plat("mingw") + --set_toolchains("clang") + + add_cxxflags("-static") + add_cxxflags("-stdlib=libc++") + + add_files("src/*.cpp") + add_includedirs("include") + + set_warnings("all") + + add_defines("__FCORE_COMPILE_TIME=\"" .. os.date("%Y-%m-%d %H:%M:%S") .. "\"") \ No newline at end of file