diff --git a/tests/lang/README.md b/tests/lang/README.md index 8ebba7f0c7f..17f7c636a92 100644 --- a/tests/lang/README.md +++ b/tests/lang/README.md @@ -45,11 +45,19 @@ Actual error message ## Detailed! explanation for the error cause and possible solutions to it. block working_example: - + # One or more examples of working code block failing_example: # Example of the failing code - + ``` + + +## File naming + +- `t01_feature_name.nim` - start file name with number to explicitly order features. +- `t01_feature_name_run_fail.nim` - show example of the runtime failure +- `t01_feature_name_comp_fail.nim` - show compilation error related to the feature +- `t01_feature_name_warning.nim` - show compilation warning related to the feature \ No newline at end of file diff --git a/tests/lang/s01_basics/README.md b/tests/lang/s01_basics/README.md index bdb9ed5abb9..6708ef85e9d 100644 --- a/tests/lang/s01_basics/README.md +++ b/tests/lang/s01_basics/README.md @@ -2,11 +2,16 @@ Here we have tests for the very simple parts of the language, that we often would not think to test. Mostly this captures minor trivia around: + - comments - literals -- if statements -- etc +- if, while and case statements +- code block and scopes +- primitive expressions +- Pure-nim user-defined types, without going into details about interfacing + with C and C++ objects, pragmas and any other complications. -Things that we barely think about -- if it fits better somewhere else put it -there. This is a a place for the things too small, rather than too big and fit -nowhere else. \ No newline at end of file +Tests in this section are mostly used to document how most basic interactions +are done - stuff you would find in almost any regular imperative programming +language. Things that are more nim-specific should be placed in `s02_core` or +other sections. \ No newline at end of file diff --git a/tests/lang/s01_basics/s00_atoms/readme.md b/tests/lang/s01_basics/s00_atoms/readme.md new file mode 100644 index 00000000000..b0977bbee0f --- /dev/null +++ b/tests/lang/s01_basics/s00_atoms/readme.md @@ -0,0 +1,13 @@ +# Atoms + +This directory contains specifications for basic language components: + +- What is an expression +- What is a statement +- What is "compile time" + +For a lot of these, it is difficult to demonstrate without using +concepts from subsequent specification tests; but, it was decided to put them +as a first entry in the specification. When more "advanced" entries are used, +these should be referenced. + diff --git a/tests/lang/s01_basics/s00_atoms/t00_builtins.nim b/tests/lang/s01_basics/s00_atoms/t00_builtins.nim new file mode 100644 index 00000000000..02a16624586 --- /dev/null +++ b/tests/lang/s01_basics/s00_atoms/t00_builtins.nim @@ -0,0 +1,60 @@ +discard """ +description: ''' +This test specifies basic operations used in other tests. +''' +targets:"c cpp js" +""" + +block builtin_assert: + ## `doAssert` command is used throughout the specification to demonstrate + ## the correctness of an expression; ergo, it `doAssert` evaluates the + ## expression which must return `true`. + + ## If the expression is evaluated as true then the assertion is successful + doAssert true + + ## `0 == 0` is evaluated as true, the assertion is succesful + doAssert 0 == 0 + +block built_integer_operations: + ## Built-in operations on integer values + + ## Declare integer variable with initial value of `0` + var value: int = 0 + + ## Assert equality of the variable using `==` operator + doAssert value == 0 + + ## Increment value of the variale using `inc` command + inc value + + doAssert value == 1 + + ## Decrement value using `dec` command + dec value + + ## Value of the variable returned to 0 + doAssert value == 0 + +block assert_type_of_the_expression: + ## Every expression has a type. This can be checked for using `is` operator + ## (or it's reverse `isnot`). + + ## Declare variable of type `int` and check if expression `value` has this + ## type. + var value: int = 0 + + doAssert value is int + doAssert value == 0 + +block get_type_of_the_expression_explicitly: + ## Using `typeof()` procedure, you can get the type of the expression explicitly. + ## Name of the type can be converted to string using `$` operator. + ## The `$` operator is an inbuilt operation that converts built-in types to + ## string. This will be discussed further in subsequent documents. + + var value: int = 0 + + doAssert value is int + doAssert $int == "int" + doAssert $typeof(value) == "int" \ No newline at end of file diff --git a/tests/lang/s01_basics/s00_atoms/t01_statement.nim b/tests/lang/s01_basics/s00_atoms/t01_statement.nim new file mode 100644 index 00000000000..efb849b89d8 --- /dev/null +++ b/tests/lang/s01_basics/s00_atoms/t01_statement.nim @@ -0,0 +1,246 @@ +discard """ +description: ''' +This test covers statements, sequential evaluation and control flow. +''' +joinable: false +targets:"c cpp js" +""" + +## This section of specification explains what *statemetn* is, and two different +## types of statemets - "basic" and "noreturn". Examples of the noreturn statement +## should not be confused with general specification of control-flow alteration that +## is documented in :idx:`s09_control_flow/*.nim` + +block sequential_control_flow: + ## Multiple statements placed on the same level will be sequentially executed + ## unless interrupted by a noreturn statement. + + ## Statement 1 - declares a variable + var value = 0 + + ## Statement 2 - assign 1 to a variable + value = 1 + + ## Statement 3 - asserts value of the variable is equal to 1 + doAssert value == 1 + +block noreturn_statement: + ## noreturn statements enable manipulation of control flow for the program in + ## different ways (depending on the context). Each specific type of the control + ## flow-altering statement is more thoroughly discussed in their respective + ## sections; here we will provide a basic overview of the subject. + + block break_statement: + ## `break` statement can be used inside of loops - (`for`, `while`) and in + ## `block` expressions. + + block break_unnamed_block: + # Entered `block break_unnamed_block` path + var value = 0 + block: + # Entered `block` path + value = 1 + break # `break` out of `block` path -> return to previous path of execution. + value = 2 + # We now resume `block break_unnamed_block` path of execution + + ## Only single increment took place - `break` ended the flow of + ## the block and resumed its parent flow; therefore, it prevented + ## the second expression `value = 2` from being executed. + doAssert value == 1 + + block break_named_block: + ## Unless specified by the `break` operation, it will break only its + ## local path of execution. By specifying the name of the `block` you + ## wish to break, you can escape several paths of execution at the same + ## time. + + # enter `break_named_block` path + var value = 0 + block name_1: + # enter `name_1` path + value = 1 + block name_2: + # enter `name_2` path + value = 2 + break name_1 # break out of `name_1` path; since `name_2` is a branch + # of `name_1`, we logically must end its execution too. + value = 3 + value = 4 + + ## `break name_1` prevented third and fourth assignment from being executed. + doAssert value == 2 + + ## Should we have instead used `break` or `break name_2` instead of + ## `break name_1`, this doAssert would fail; the correct assertion would + ## instead be `doAssert value == 4`. + + block break_while_loop: + ## `break` in a `while` loop immediately stops the cyclic path execution. For more + ## details and interactions see tests for `while` statements. + var value = 0 + while true: + ## Break in the `while` loop terminates it's execution. + break + value = 1 + + doAssert value == 0 + + block break_for_loop: + ## Break in the `for` loop operates identically to the `while` case - when + ## `break` is reached, the loop 'body' or 'path' of execution is stopped. For more details see + ## specification on `iterators`. + + var value = 0 + for i in 0 .. 10: + ## Break in the `for` loop terminates it's execution. + break + value = 2 + + doAssert value == 0 + + block continue_statement: + ## `continue` statement can be used in the `while` and `for` loops to skip + ## execution of the current loop. It effectively ends the current cyclic path + ## of execution and returns to the beginning of the cyclic path. This is different + ## to `break` which ends the cyclic path altogether and instead returns to + ## its parent path. + + block continue_in_while: + var value = 0 + var cond = true + + while cond: + ## This cyclic path is only entered if `cond` is true. + + ## First statement in the loop is executed. + cond = false + ## Setting this variable to false will mean the next iteration of the + ## cyclic path will not proceed since `cond` is no longer true. + + ## Upon reaching `continue`, control flow is transferred to the next + ## iteration. + continue + ## The control flow will now return to the beginning of the cyclic path; + ## it will re-evaluate `cond` as false and therefore not proceed with + ## the next iteration of the cyclic path. + + + ## Assignment to value is never reached. + value = 1 + + doAssert value == 0 + + block continue_in_for: + ## `continue` in the `for` loop is identical to the `while` + ## loop - when reached, it transfers control to the next iteration. For + ## more details see specification for the `iteratrors`. + var preContinue = 0 + var postContinue = 0 + + for i in 0 .. 4: + ## Statement before `continue` is executed + preContinue = preContinue + i + ## Upon reaching `continue`, control flow is returned to the beginning + ## of the cyclic path for the next iteration. + continue + postContinue = 9 + + ## Statement placed after continue is never reached + doAssert postContinue == 0 + + ## Statement placed before `continue` is executed on every loop iteration + doAssert preContinue == 0 + 1 + 2 + 3 + 4 + ## If you flatten out the for loop; this is what you would see: + ## ```nim + ## i = 0 + ## preContinue = preContinue + i + ## # 1, 2, 3... + ## i = 4 + ## preContinue = preContinue + i + ## ``` + ## + ## If we did not have the `continue` operation; we would instead see this: + ## ```nim + ## i = 0 + ## preContinue = preContinue + i + ## postContinue = 9 + ## # 1, 2, 3... + ## i = 4 + ## preContinue = preContinue + i + ## postContinue = 9 + ## ``` + + block return_statement: + ## `return` can be used in procedures or macro declaration bodies. + ## When reached, it immediately transfers control flow back to the caller of the + ## function body (outside the function body). `return` can also be used to + ## set resulting value of a procedure or macros - this functionality is + ## covered in their respective sections. + + var value = 0 + + value = 1 + + # Unlike with `block` expressions, we do not immediately enter the `proc` + # path/flow of execution. We first define it, and then enter it when the + # procedure is called. Subsequently, breaking that path of execution would + # return control to where the path was entered, not where it was defined. + + proc execReturn() = + value = 2 + ## When `return` is reached, control flow is immediately transferred out + ## of the procedure body + return + value = 3 + + ## We execute the `execReturn` procedure - effectively we have entered the + ## path of executions for that procedure. Procedures are looked at in more + ## detail in their respective section. + execReturn() + # Path of operations continues when the procedure path reaches its end or + # is `return`ed. + + doAssert value == 2 + + block raise_statement: + ## `raise` statement can be used inside of a procedure body or macro to + ## create an exception. Concrete details about exceptions, their types and + ## handling can be seen in their respective section of the specification. + + var value = 0 + proc raiseInside() = + value = 1 + + ## Specific type of the exception is not important in this case - + ## `ValueError` would not be any different from `OSError` and alike + raise newException(ValueError, "") + value = 2 + + try: + ## When exception is raised it propagates through all function calls (if + ## any) until it reaches the topmost level (main module/path from which the first + ## function was called) or a `try` statement. For particular details on + ## how exceptions affect control flow see respective part of the + ## specification. In this case `try` was needed so subsequent `doAssert` + ## would be executed correctly. + raiseInside() + + except: + # `try`, `except`, `finally`, will be explored in their respective section. + discard + + doAssert value == 1 + + block noreturn_annotation: + ## Another type of a noreturn statement is a `quit()` procedure - it + ## immediatelly terminates whole execution of the program once called. + ## If looking at this from a paths or flow perspective, this is effectively + ## ending every path of the program. + + quit() + + ## This statement will never be reached because `quit()` was called before. + doAssert false + ## If the statement was reached, then the assertion would fail, and the + ## test would return a FAIL. diff --git a/tests/lang/s01_basics/s00_atoms/t02_expression.nim b/tests/lang/s01_basics/s00_atoms/t02_expression.nim new file mode 100644 index 00000000000..9854506d8fc --- /dev/null +++ b/tests/lang/s01_basics/s00_atoms/t02_expression.nim @@ -0,0 +1,108 @@ +discard """ +description: ''' +Test expression evaluation - via blocks, noreturn statements, regular expressions. +''' +targets:"c cpp js" +""" + +## Basic expressions. This section explains and tests basic expressions - +## without using `if/when/case` expressions, procedure calls and so on. + + + +block basic_expression: + ## Any literal is an expression. Concrete type of the expression depends on + ## the type of literal used. + + ## String literal is an expression of type `string` + doAssert "literal" is string + + ## Standalone integer literal is an expression of type `int` + doAssert 12 is int + + ## Floating point value literal is an expression of type `float` + doAssert 3.14 is float + + ## It is posssible to explicitly specify type of the numeric literal (integer + ## or float) using suffix - + + ## Literal of type "unsigned 8-bit integer" + doAssert 1'u8 is uint8 + + ## "signed 8-bit integer" + doAssert 1'i8 is int8 + + ## Other types of integer literals include `16`, `32` and `64`-bit integers. + + doAssert 1'u16 is uint16 + doAssert 1'i16 is int16 + + doAssert 1'u32 is uint32 + doAssert 1'i32 is int32 + + doAssert 1'u64 is uint64 + doassert 1'i64 is int64 + +block block_expressions: + doAssert( + block: + ## Block expression might contain multiple statements, including comments + discard + ## Value of the last expression in block will be used as a resulting value + ## in the whole expression. + true + ) + + doAssert( + block: + block: + block: + ## It is possible to nest block expressions when needed + true + ) + + + ## It is also possible to put multiple preceding statements on a single line + ## using semicolon (`;`) as a separator. + + var value = 0 + + ## This expression allows to implement pre-increment action + doAssert value == 0 + doAssert ((inc value; value)) == 1 # QUESTION why double parens are necessary? + doAssert value == 1 + + ## This one is similar, but does a post-increment + doAssert value == 1 + doAssert ((let tmp = value; inc value; tmp)) == 1 + doAssert value == 2 + + +block statements_as_expressions: + ## It is possible to use `if`, `case`, `try` statements as expressions. + + ## `if` can be used as inline expressions. For more examples on this see + ## specification for if statement. + doAssert(if 12 == 12: true else: false) + doAssert(if 12 == 13: true elif 12 == 12: true else: false) + + ## Case statement cannot be used as a single-line expression, but it is still + ## possible to use it in any other form. For more examples and specific rules + ## on the case expression see specification for case statement. + let vcase = case range[0..2](0): + of 0: 13 + of 1: 14 + of 2: 15 + + doAssert vcase == 13 + + ## `try` can be used as a single-line expression as well. For more examples on + ## and specific rules see specification for exception handling. + let vtry = try: + raise (ref OSError)() + 222 + + except: + 12 + + doAssert vtry == 12 diff --git a/tests/lang/s01_basics/s00_atoms/t03_compiletime.nim b/tests/lang/s01_basics/s00_atoms/t03_compiletime.nim new file mode 100644 index 00000000000..83d619795d7 --- /dev/null +++ b/tests/lang/s01_basics/s00_atoms/t03_compiletime.nim @@ -0,0 +1,58 @@ +discard """ +description: ''' +Execution of the code at compilation time. +''' +output: ''' +echo outside of static +''' +targets:"c cpp js" +""" + +# Given this is a specification I don't think it is an appropriate place to +# start a lenghtly introduction to what "compile time is", but considering it is +# a *fundamental* part of the nim language it would not hurt to start with some +# definitions before going into concrete details. + +static: + ## This code will be executed *during compilation* of the project + echo "echo inside of static" + +## This code will be executed only when compiled binary is run. +echo "echo outside of static" + +static: + ## This code will also be executed during compilation. + echo "second echo inside of static" + +## It is possible to define compile-time variable using `{.compiletime.}` pragma +## in the variable declaration. This variable will not be accessible from +## regular runtime code, but can be used in `macro` or `static:`. + +var globalCompiletimeInt {.compiletime.}: int = 12 + +macro compiletimeGetter() = + echo "current value of the compiletime: ", globalCompiletimeInt + +compiletimeGetter() + +static: + globalCompiletimeInt = 12 + +compiletimeGetter() + +## Data can be moved from `compiletime` variables into `const` + +const canAccessAtRuntime = globalCompiletimeInt + +doAssert canAccessAtRuntime == 12 + +## Note that subsequent modifications of the compiletime variable will not +## affect value of the constant after it has been declared. + +static: + globalCompiletimeInt = 24 + +const newConst = globalCompiletimeInt + +doAssert newConst == 24 +doAssert canAccessAtRuntime == 12 diff --git a/tests/lang/s01_basics/s02_identifiers/t01_fail_keywords.nim b/tests/lang/s01_basics/s02_identifiers/t01_keywords_fail.nim similarity index 100% rename from tests/lang/s01_basics/s02_identifiers/t01_fail_keywords.nim rename to tests/lang/s01_basics/s02_identifiers/t01_keywords_fail.nim diff --git a/tests/lang/s01_basics/s02_identifiers/t02_1_case_sensitivity.nim b/tests/lang/s01_basics/s02_identifiers/t02_case_sensitivity.nim similarity index 100% rename from tests/lang/s01_basics/s02_identifiers/t02_1_case_sensitivity.nim rename to tests/lang/s01_basics/s02_identifiers/t02_case_sensitivity.nim diff --git a/tests/lang/s01_basics/s02_identifiers/t02_fail2_case_sensitivity_error_first_character.nim b/tests/lang/s01_basics/s02_identifiers/t02_case_sensitivity_error_first_character_comp_fail.nim similarity index 100% rename from tests/lang/s01_basics/s02_identifiers/t02_fail2_case_sensitivity_error_first_character.nim rename to tests/lang/s01_basics/s02_identifiers/t02_case_sensitivity_error_first_character_comp_fail.nim diff --git a/tests/lang/s01_basics/s02_identifiers/t03_fail2_double_underscore_as_trailing.nim b/tests/lang/s01_basics/s02_identifiers/t03_underscore_double_as_trailing_comp_fail.nim similarity index 100% rename from tests/lang/s01_basics/s02_identifiers/t03_fail2_double_underscore_as_trailing.nim rename to tests/lang/s01_basics/s02_identifiers/t03_underscore_double_as_trailing_comp_fail.nim diff --git a/tests/lang/s01_basics/s02_identifiers/t03_fail1_trailing_underscore.nim b/tests/lang/s01_basics/s02_identifiers/t03_underscore_trailing_comp_fail.nim similarity index 100% rename from tests/lang/s01_basics/s02_identifiers/t03_fail1_trailing_underscore.nim rename to tests/lang/s01_basics/s02_identifiers/t03_underscore_trailing_comp_fail.nim diff --git a/tests/lang/s01_basics/s03_literals/s1_string/t1_escape_sequences.nim b/tests/lang/s01_basics/s03_literals/s1_string/t01_escape_sequences.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s1_string/t1_escape_sequences.nim rename to tests/lang/s01_basics/s03_literals/s1_string/t01_escape_sequences.nim diff --git a/tests/lang/s01_basics/s03_literals/s1_string/t2_triplequoted.nim b/tests/lang/s01_basics/s03_literals/s1_string/t02_triplequoted.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s1_string/t2_triplequoted.nim rename to tests/lang/s01_basics/s03_literals/s1_string/t02_triplequoted.nim diff --git a/tests/lang/s01_basics/s03_literals/s1_string/t3_raw_and_generalized.nim b/tests/lang/s01_basics/s03_literals/s1_string/t03_raw_and_generalized.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s1_string/t3_raw_and_generalized.nim rename to tests/lang/s01_basics/s03_literals/s1_string/t03_raw_and_generalized.nim diff --git a/tests/lang/s01_basics/s03_literals/s1_string/t3_raw_no_triplequote_fail.nim b/tests/lang/s01_basics/s03_literals/s1_string/t03_raw_no_triplequote_fail.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s1_string/t3_raw_no_triplequote_fail.nim rename to tests/lang/s01_basics/s03_literals/s1_string/t03_raw_no_triplequote_fail.nim diff --git a/tests/lang/s01_basics/s03_literals/s2_character/t1_escape_sequences.nim b/tests/lang/s01_basics/s03_literals/s2_character/t01_escape_sequences.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s2_character/t1_escape_sequences.nim rename to tests/lang/s01_basics/s03_literals/s2_character/t01_escape_sequences.nim diff --git a/tests/lang/s01_basics/s03_literals/s3_numeric/t1_literals.nim b/tests/lang/s01_basics/s03_literals/s3_numeric/t01_literals.nim similarity index 100% rename from tests/lang/s01_basics/s03_literals/s3_numeric/t1_literals.nim rename to tests/lang/s01_basics/s03_literals/s3_numeric/t01_literals.nim diff --git a/tests/lang/s01_basics/s04_const_let_var/t1_const_let_var.nim b/tests/lang/s01_basics/s04_varables/t01_const_let_var.nim similarity index 100% rename from tests/lang/s01_basics/s04_const_let_var/t1_const_let_var.nim rename to tests/lang/s01_basics/s04_varables/t01_const_let_var.nim diff --git a/tests/lang/s01_basics/s04_varables/t02_block_and_scopes.nim b/tests/lang/s01_basics/s04_varables/t02_block_and_scopes.nim new file mode 100644 index 00000000000..7c3df3303e3 --- /dev/null +++ b/tests/lang/s01_basics/s04_varables/t02_block_and_scopes.nim @@ -0,0 +1,57 @@ +block sequential_control_flow: + var value = 0 + inc value + inc value + + doAssert value == 2 + +block variable_shadowing: + var value = 0 + block: + var value = 0 + inc value + doAssert value == 1 + + inc value + + doAssert value == 1 + +block variable_shadowing_different_type: + ## It is possible to declare new variable with the same but different name in + ## the new scope. + + ## Declare integer variable `value` in the scope + let value: int = 0 + doAssert value is int + + ## Block starts new scope + block: + ## It is allowed to declare *new* variable with the same name but different + ## type in the scope. + let value: string = "" + + ## Until scope is finished `value` will refer to the string variable. + doAssert value is string + doAssert value == "" + + ## After scope has ended `value` will refer to the first declared variable + doAssert value is int + doAssert value == 0 + +block scope_nesting_is_unlimited: + ## There is no hard built-in limit on the maximum nesting of scopes + let a = 1 + block: + let a = 2 + block: + let a = 3 + block: + let a = 4 + block: + let a = 5 + + doAssert a == 5 + doAssert a == 4 + doAssert a == 3 + doAssert a == 2 + doAssert a == 1 diff --git a/tests/lang/s01_basics/s06_data_types/t1_integer.nim b/tests/lang/s01_basics/s05_data_types/t1_integer.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t1_integer.nim rename to tests/lang/s01_basics/s05_data_types/t1_integer.nim diff --git a/tests/lang/s01_basics/s06_data_types/t2_unsigned_backwards_compat.nim b/tests/lang/s01_basics/s05_data_types/t2_unsigned_backwards_compat.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t2_unsigned_backwards_compat.nim rename to tests/lang/s01_basics/s05_data_types/t2_unsigned_backwards_compat.nim diff --git a/tests/lang/s01_basics/s06_data_types/t3_boolean.nim b/tests/lang/s01_basics/s05_data_types/t3_boolean.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t3_boolean.nim rename to tests/lang/s01_basics/s05_data_types/t3_boolean.nim diff --git a/tests/lang/s01_basics/s06_data_types/t4_char.nim b/tests/lang/s01_basics/s05_data_types/t4_char.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t4_char.nim rename to tests/lang/s01_basics/s05_data_types/t4_char.nim diff --git a/tests/lang/s01_basics/s06_data_types/t7_strings.nim b/tests/lang/s01_basics/s05_data_types/t7_strings.nim similarity index 99% rename from tests/lang/s01_basics/s06_data_types/t7_strings.nim rename to tests/lang/s01_basics/s05_data_types/t7_strings.nim index ef50c44e6fa..c1831f76b1a 100644 --- a/tests/lang/s01_basics/s06_data_types/t7_strings.nim +++ b/tests/lang/s01_basics/s05_data_types/t7_strings.nim @@ -41,10 +41,10 @@ block string_concatenation: # indexing, assignment as copy, and mutation var strA = "abd" let strB = strA - + doAssert strA == strB, "strings should start out the same" doAssert strA[2] == 'd', "indexing into a string" - strA[2] = 'c' # change the last letter, strA and strB are no longer equal + strA[2] = 'c' # change the last letter, strA and strB are no longer equal doAssert strA != strB, "strings copy on assignment, therefore not equal" doAssert strA == "abc", "after changing strings are lexographically equal" @@ -56,5 +56,5 @@ block string_unicode_handling: block string_copy_on_assignment: # indexing, assignment as copy, and mutation let strA = "ab" & "c" - + doAssert strA == "abc", "strings are concatenated and equal" diff --git a/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim b/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim new file mode 100644 index 00000000000..15c6c929bdc --- /dev/null +++ b/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim @@ -0,0 +1,92 @@ +discard """ +description: ''' +Describe the basics of arrays: +- fixed size list of a single type +- literals +- indexing +- length +''' +target: "c cpp js" +""" + +block literal: + ## It is possible to construct an empty array literal + let a: array[0, int] = [] + + let b = [1, 2, 3] + doAssert a == [], "two empty arrays are equal" + doAssert b == [1, 2, 3], "array elements are compared by element pair" + doAssert b != [1, 2, 3, 4], "array's don't partially match" + +block array_len: + var + a: array[0, int] + b: array[10, int] + doAssert a.len == 0, "an empty array has len 0" + doAssert b.len == 10, "arrays reserve the space upfront" + +block array_indexing: + var a = [1, 2] + + doAssert a[0] == 1, "arrays by default are zero based indexed" + a[0] = 4 + doAssert a[0] == 4, "arrays update in place" + +block array_indexing_with_offset: + var index: array[2 .. 10, int] = [0, 1, 2, 3, 4, 5, 6, 7, 8] + + doAssert len(index) == 10 - 1 + doAssert high(index) == 10 + doAssert low(index) == 2 + + doAssert index[2] == 0 + doAssert index[10] == 8 + +block curly_literal: + block single_pairs: + let curly = { + "key1": "value1", + "key2": "value2" + } + + doAssert curly is array[2, (string, string)], "{} is an array literal as well" + doAssert curly[0] == ("key1", "value1") + doAssert curly[1] == ("key2", "value2") + + block multi_key: + var used = 0 + when not defined(js) or defined(tryBrokenSpecification): + # FIXME this code behaves completely differently on the JS backend - it + # results in `[("key1", 3), ("key2", 3), ("key3", 3)]` + let curly = { + "key1", "key2": (inc used; used), + "key3": (inc used; used) + } + + + doAssert used == 3, "Expression is evaluated for each key" + doAssert curly == [ + ("key1", 1), + ("key2", 2), + ("key3", 3) + ] + +block key_value_bracket: + let bracket = [ + 0: "val0", + 1: "val1", + 2: "val2" + ] + + doAssert bracket is array[3, string] + doAssert bracket == ["val0", "val1", "val2"] + +block array_different_types: + # arrays can have more than just the `int` type + var + a = ["foo", "bar", "bar"] + b = [[1], [2], [3]] + doAssert a[2] == "bar" + a[2] = "baz" + doAssert a[2] == "baz", "element was updated" + doAssert b[1][0] == 2, "chain index look ups work" diff --git a/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim.bin b/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim.bin new file mode 100755 index 00000000000..e6966d2c0c1 Binary files /dev/null and b/tests/lang/s01_basics/s06_arrays_sequences/t01_array.nim.bin differ diff --git a/tests/lang/s01_basics/s07_arrays_sequences/t2_sequence.nim b/tests/lang/s01_basics/s06_arrays_sequences/t02_sequence.nim similarity index 100% rename from tests/lang/s01_basics/s07_arrays_sequences/t2_sequence.nim rename to tests/lang/s01_basics/s06_arrays_sequences/t02_sequence.nim diff --git a/tests/lang/s01_basics/s06_arrays_sequences/t02_sequence.nim.bin b/tests/lang/s01_basics/s06_arrays_sequences/t02_sequence.nim.bin new file mode 100755 index 00000000000..506d157a12d Binary files /dev/null and b/tests/lang/s01_basics/s06_arrays_sequences/t02_sequence.nim.bin differ diff --git a/tests/lang/s01_basics/s06_data_types/t5_enumeration.nim b/tests/lang/s01_basics/s06_data_types/t5_enumeration.nim deleted file mode 100644 index 261735458bc..00000000000 --- a/tests/lang/s01_basics/s06_data_types/t5_enumeration.nim +++ /dev/null @@ -1,82 +0,0 @@ -discard """ -description: ''' -An enumeration defines a type with a closed set of values. These values -have an identifier, are ordered, and have corresponding integer and string -values. -''' -target: "c cpp js" -""" - -block enum_default_int_values: - # enums int values are zero based indexed by default, `ord` is used - # to retrieve their int value - type - Abc = enum - a, b, c - doAssert ord(Abc.a) == 0, "1st enum int value corresponds to 0" - doAssert ord(Abc.b) == 1, "2nd enum int value corresponds to 1" - doAssert ord(Abc.c) == 2, "3rd enum int value corresponds to 2" - -block enum_unqualified_access: - # access only needs to be qualified if ambiguous -- here it isn't - # xxx: cover pure elsewhere, it's a legacy thing that should be removed - type - Abc = enum - a, b, c - doAssert ord(b) == 1, "unqualified access" - -block enum_default_string_values: - # enum string values are derived from their name by default, use `$` for - # conversion to a string - type - Abc = enum - a, b, c - doAssert $a == "a", "value's string derives from the name by default - a" - doAssert $b == "b", "value's string derives from the name by default - b" - doAssert $c == "c", "value's string derives from the name by default - c" - -block enum_assigned_int_values: - # enum int values can be assigned - type - Abc = enum - a = 2, - b = 3, - c = 5 - doAssert ord(a) == 2, "enum assigned int value - a" - doAssert ord(b) == 3, "enum assigned int value - b" - doAssert ord(c) == 5, "enum assigned int value - c" - -block enum_assigned_string_values: - # enum string values can be assigned - type - Abc = enum - a = "apple", - b = "zebra", - c = "panda" - doAssert $a == "apple", "enum assigned string value - a" - doAssert $b == "zebra", "enum assigned string value - b" - doAssert $c == "panda", "enum assigned string value - c" - -block enum_default_behaviour_retention: - # enum int and string values can be assigned and gaps are filled, int is - # incremented and strings revert to deriving from the name - type - Abc = enum - a = (0, "ape"), - b = ("see"), - c = 5 - doAssert ord(a) == 0 and $a == "ape", "assigned int or string values - a" - doAssert ord(b) == 1 and $b == "see", "assigned int or string values - b" - doAssert ord(c) == 5 and $c == "c", "assigned int or string values - c" - -block enum_assigned_equal_string_values: - # enum values can be assigned the same string values, and they can be empty - # xxx: this seems like a bug/design flaw - type - Abc = enum - a = "", - b = "", - c = "" - doAssert $a == "", "enum assigned an empty string value - a" - doAssert $b == "", "enum assigned an empty string value - b" - doAssert $c == "", "enum assigned an empty string value - c" diff --git a/tests/lang/s01_basics/s07_arrays_sequences/t1_array.nim b/tests/lang/s01_basics/s07_arrays_sequences/t1_array.nim deleted file mode 100644 index c5b472f06b6..00000000000 --- a/tests/lang/s01_basics/s07_arrays_sequences/t1_array.nim +++ /dev/null @@ -1,42 +0,0 @@ -discard """ -description: ''' -Describe the basics of arrays: -- fixed size list of a single type -- literals -- indexing -- length -''' -target: "c cpp js" -""" - -block literal: - let - a: array[0, int] = [] - b = [1, 2, 3] - doAssert a == [], "two empty arrays are equal" - doAssert b == [1, 2, 3], "array elements are compared by element pair" - doAssert b != [1, 2, 3, 4], "array's don't partially match" - -block array_len: - var - a: array[0, int] - b: array[10, int] - doAssert a.len == 0, "an empty array has len 0" - doAssert b.len == 10, "arrays reserve the space upfront" - -block array_indexing: - var a = [1, 2] - - doAssert a[0] == 1, "arrays by default are zero based indexed" - a[0] = 4 - doAssert a[0] == 4, "arrays update in place" - -block array_different_types: - # arrays can have more than just the `int` type - var - a = ["foo", "bar", "bar"] - b = [[1], [2], [3]] - doAssert a[2] == "bar" - a[2] = "baz" - doAssert a[2] == "baz", "element was updated" - doAssert b[1][0] == 2, "chain index look ups work" diff --git a/tests/lang/s01_basics/s05_operators/t1_operators.nim b/tests/lang/s01_basics/s07_operators/t1_operators.nim similarity index 100% rename from tests/lang/s01_basics/s05_operators/t1_operators.nim rename to tests/lang/s01_basics/s07_operators/t1_operators.nim diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t01_tuples.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t01_tuples.nim index d6c46eace0b..73a76b07dce 100644 --- a/tests/lang/s01_basics/s08_user_defined_data_types/t01_tuples.nim +++ b/tests/lang/s01_basics/s08_user_defined_data_types/t01_tuples.nim @@ -89,6 +89,20 @@ block unpacking_tuples: doAssert asgn1 == 12 doAssert asgn2 == "123" + + ## It is also possible to unpack tuples from `if` expressions + let (if1, if2) = if true: (1, 2) else: (3, 4) + + doAssert if1 == 1 + doAssert if2 == 2 + + + let (case1, case2) = + case 1: + of 0: ("name1", "name2") + of 1: ("name3", "name3") + of 2: ("name4", "name5") + else: ("default", "xxx") block unpack_to_mutable: diff --git a/tests/lang/s01_basics/s06_data_types/t6_enumeration_fail2_types_not_values.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t02_enum_fail2_types_not_values.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t6_enumeration_fail2_types_not_values.nim rename to tests/lang/s01_basics/s08_user_defined_data_types/t02_enum_fail2_types_not_values.nim diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim new file mode 100644 index 00000000000..dce92358a33 --- /dev/null +++ b/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim @@ -0,0 +1,218 @@ +discard """ +description: ''' +An enumeration defines a type with a closed set of values. These values +have an identifier, are ordered, and have corresponding integer and string +values. +''' +target: "c cpp js" +""" + +block enum_definition_syntax: + type SimpleEnum = enum name1, name2, name3 + +block enum_values: + ## Each enum constnat can be assigned and integer value, and constant's string + ## representation can be overriden. + + block enum_default_int_values: + ## enums int values are zero based indexed by default, `ord` is used + ## to retrieve their int value + type + Abc = enum + a + b + c + + doAssert ord(a) == 0, "1st enum int value corresponds to 0" + doAssert ord(b) == 1, "2nd enum int value corresponds to 1" + doAssert ord(c) == 2, "3rd enum int value corresponds to 2" + + block enum_default_int_values: + ## It is possible to override single value in the enum - all subsequent + ## ones will be incremented by one + type + Abc = enum + a = 12 + b + c + + doAssert ord(a) == 12, "1st enum int value corresponds to 0" + doAssert ord(b) == 12 + 1, "2nd enum int value corresponds to 1" + doAssert ord(c) == 12 + 2, "3rd enum int value corresponds to 2" + + + block enum_unqualified_access: + ## access only needs to be qualified if ambiguous -- here it isn't + type + Abc = enum + a + b + c + doAssert ord(b) == 1, "unqualified access" + + block enum_default_string_values: + ## enum string values are derived from their name by default, use `$` for + ## conversion to a string + type + Abc = enum + a + b + c + + doAssert $a == "a", "value's string derives from the name by default - a" + doAssert $b == "b", "value's string derives from the name by default - b" + doAssert $c == "c", "value's string derives from the name by default - c" + + block enum_assigned_int_values: + ## Enum constants can be assigned with arbitrary values, but they have to + ## be placed in increasing order. So `2, 3, 5` is ok, but `2, 4, 3` is + ## not allowed. + type + Abc = enum + a = 2 + b = 3 + c = 5 + + doAssert ord(a) == 2, "enum assigned int value - a" + doAssert ord(b) == 3, "enum assigned int value - b" + doAssert ord(c) == 5, "enum assigned int value - c" + + block enum_assigned_string_representation: + ## In addition to overriding integer value of the enum constant, you can also + ## override it's string representation (without affecting the value) + type + Abc = enum + a = "apple" + b = "zebra" + c = "panda" + + doAssert $a == "apple", "enum assigned string value - a" + doAssert $b == "zebra", "enum assigned string value - b" + doAssert $c == "panda", "enum assigned string value - c" + + block enum_default_behaviour_retention: + ## enum int and string representation can be assigned and gaps are filled, int is + ## incremented and strings revert to deriving from the name + type + Abc = enum + a = (0, "ape") + b = ("see") + c = 5 + + doAssert ord(a) == 0 and $a == "ape", "assigned int or string representation - a" + doAssert ord(b) == 1 and $b == "see", "assigned int or string representation - b" + doAssert ord(c) == 5 and $c == "c", "assigned int or string representation - c" + + block enum_assigned_equal_string_representation: + # enum values can be assigned the same string representation, and they can be empty + # xxx: this seems like a bug/design flaw + type + Abc = enum + a = "" + b = "" + c = "" + + doAssert $a == "", "enum assigned an empty string value - a" + doAssert $b == "", "enum assigned an empty string value - b" + doAssert $c == "", "enum assigned an empty string value - c" + + + +## Enum type can be used in sets just like any other ordinal +## type. In addition to sets it can also serve as an index to +## array type. + +block enum_sets: + type Enum = enum en1, en2, en3, en4, en5 + + ## Enum sets support all built-in operations and are not different + ## from set with any other type. + + ## `in` operator + doAssert en1 in {en1, en2} + + ## `notin` + doAssert en2 notin {en1} + + ## Set intersection + doAssert {en1, en2} * {en1, en3} == {en1} + + ## And so on + doAssert {en1} is set[Enum] + + +block enum_arrays: + ## In order to use enum as an index to array it must not contain + ## gaps (holes) in values. + type + Index = enum + idx1 + idx2 + idx3 + + block full_array_index: + var index: array[Index, string] + + index[idx1] = "hello" + index[idx2] = "world" + + ## Note - when using enum as an index for arrays it is no longer possible to + ## access values with integer-based indices (because integers are not implicitly + ## convertible to enum) + doAssert index[Index(0)] == "hello" + + ## Size of the enum-indexed array follows regular rules (len(array) * sizeof(object)) + doAssert sizeof(index) == sizeof(string) * len(index) + doAssert sizeof(index) == sizeof(string) * 3 + + block partial_aray_index: + ## It is also possible to index array using enum *slice* - delimiting based + ## on start/end values. In this case size of the array would be smaller as well. + + var index: array[idx1 .. idx2, string] + index[idx1] = "idx1" + index[idx2] = "idx2" + + ## Indexing restructions still apply here + doAssert index[Index(0)] == "idx1" + doAssert index[Index(1)] == "idx2" + + block partial_aray_index_offset: + ## Enum slice can start and end with any enumeration value + var index: array[idx2 .. idx3, string] + index[idx2] = "idx1" + index[idx3] = "idx2" + + doAssert index[Index(1)] == "idx1" + doAssert index[Index(2)] == "idx2" + + block enum_indexed_literal: + ## It is also possible to construct enum-indexed array literals + ## directly. + + block full: + ## If all the keys in array literal are supplied it functions as + ## regular `array[Enum, V]` array. + let index = [idx1: 1, idx2: 22, idx3: 333] + + doAssert index is array[Index, int] + + doAssert index[idx1] == 1 + doAssert index[idx2] == 22 + doAssert index[idx3] == 333 + + block partial: + ## Array literals that map only slice of the enumeration values are + ## also supported, but keys **must* be placed in sequential order and + ## don't contain holes. `idx1: , idx2:` is allowed, as well as `idx2:, idx3`, + ## but not `idx1:, idx3` + let index = [idx1: 1, idx2: 22] + + doAssert index is array[idx1 .. idx2, int] + + doAssert index[idx1] == 1 + doAssert index[idx2] == 22 + + + + diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim.bin b/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim.bin new file mode 100755 index 00000000000..b587d112a37 Binary files /dev/null and b/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum.nim.bin differ diff --git a/tests/lang/s01_basics/s06_data_types/t6_enumeration_fail1_ordering.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t04_enum_fail1_ordering.nim similarity index 100% rename from tests/lang/s01_basics/s06_data_types/t6_enumeration_fail1_ordering.nim rename to tests/lang/s01_basics/s08_user_defined_data_types/t04_enum_fail1_ordering.nim diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t04_variant_objects.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t05_variant_objects.nim similarity index 100% rename from tests/lang/s01_basics/s08_user_defined_data_types/t04_variant_objects.nim rename to tests/lang/s01_basics/s08_user_defined_data_types/t05_variant_objects.nim diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t04_variant_objects_fail1.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t05_variant_objects_fail1.nim similarity index 100% rename from tests/lang/s01_basics/s08_user_defined_data_types/t04_variant_objects_fail1.nim rename to tests/lang/s01_basics/s08_user_defined_data_types/t05_variant_objects_fail1.nim diff --git a/tests/lang/s01_basics/s08_user_defined_data_types/t05_distinct.nim b/tests/lang/s01_basics/s08_user_defined_data_types/t06_distinct.nim similarity index 100% rename from tests/lang/s01_basics/s08_user_defined_data_types/t05_distinct.nim rename to tests/lang/s01_basics/s08_user_defined_data_types/t06_distinct.nim diff --git a/tests/lang/s01_basics/s09_control_flow/readme.md b/tests/lang/s01_basics/s09_control_flow/readme.md new file mode 100644 index 00000000000..5882156c2e4 --- /dev/null +++ b/tests/lang/s01_basics/s09_control_flow/readme.md @@ -0,0 +1,15 @@ +Language elements that can affect control flow of the code execution. + +- `break` +- `if` +- `while` +- `case` +- `return`, `try/except/finally` and general procedure calls. Note - this + section only specifies how control flow is transferred between bodies of + the functions - everything related to the procedures themselves (argument + passing, returning values, generic procedures and so on) is documented in + the "core" section of the specification. + +For statement is not specified since it's behavior heavily depends on the +implementation of a specific iterator that has been used. + diff --git a/tests/lang/s01_basics/s09_control_flow/t01_break.nim b/tests/lang/s01_basics/s09_control_flow/t01_break.nim new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/lang/s01_basics/s09_control_flow/t02_if.nim b/tests/lang/s01_basics/s09_control_flow/t02_if.nim new file mode 100644 index 00000000000..588659aefac --- /dev/null +++ b/tests/lang/s01_basics/s09_control_flow/t02_if.nim @@ -0,0 +1,134 @@ +discard """ +description: ''' +If statements, if expressions +''' +targets:"c cpp js" +""" + +#[ + - nested + - mutliple statements + - nested as expression + - together with named block and break (simplest `{.noreturn.}`) +]# + +block single_branch_if: + var value = 0 + if true: + value = 12 + + doAssert value == 12 + + if false: + value = 24 + + doAssert value == 12 + +block if_else_statement: + var value = 0 + if true: + value = 12 + + else: + value = 24 + + doAssert value == 12 + + if false: + value = 36 + + else: + value = 48 + + doAssert value == 48 + +block if_elif_statement: + var value = 0 + if true: + value = 20 + + elif true: + value = 30 + + doAssert value == 20 + + if false: + value = 40 + + elif true: + value = 90 + + doAssert value == 90 + + if false: + value = 100 + + elif false: + value = 120 + + doAssert value == 90 + + +block if_elif_else_statement: + var value = 0 + if true: + value = 1 + + elif true: + value = 2 + + else: + value = 3 + + doAssert value == 1 + + +block if_can_have_unlimited_number_of_elifs: + var value = 0 + + if false: value = 1 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif false: value = 2 + elif true: value = 4 + elif false: value = 2 + elif false: value = 2 + else: value = 3 + + doAssert value == 4 + +block if_expression: + let value = if true: 12 else: 3 + +block if_expression_noreturn: + proc impl() = + let value = + if true: + 12 + + else: + doAssert false, "This statement should never be reached" + return + + doAssert value == 12 + + while true: + let value = if true: 12 else: doAssert(false); break + doAssert value == 12 + break \ No newline at end of file diff --git a/tests/lang/s01_basics/s09_control_flow/t03_case.nim b/tests/lang/s01_basics/s09_control_flow/t03_case.nim new file mode 100644 index 00000000000..92a19e95fac --- /dev/null +++ b/tests/lang/s01_basics/s09_control_flow/t03_case.nim @@ -0,0 +1,163 @@ +discard """ +description: ''' +Case statements, case expressions +''' +""" + +#[ + - nested + - mutliple statements + - nested as expression + - together with named block and break (simplest `{.noreturn.}`) + - min and max allowed ranges + - unhandled values +]# + +## `case` statement is used to match value of the expression against multiple +## other values and execute first branch that matched.A + +## Case is very commonly used with enums - they are covered in "user-defined +## data types" section of the specification, but to simplify provided examples +## most test here specify `case` behavior using `enum` unless test is explicitly +## meant to illustrate other part of behavior. + +type SmallEnum = enum small1, small2, small3 +type BigEnum = enum big1, big2, big3, big4, big5, big6, big7 + +block case_syntax: + ## Case statement can be written in different forms - colon after expression + ## is optional, as well as indentation for the `of` branches inside the body. + ## Spec tests use indented version without omitting colon after expressions. + + case small3: + of small1: discard + of small2, small3: discard + + case small3: + of small1: discard + of small2, small3: discard + + case small3: + of small1: discard + of small2, small3: discard + + case small3 + of small1: discard + of small2, small3: discard + + + ## Body of the case expression might also contain `else`, `elif` branches that would + ## be sequentially executed if expression value does not match any of the `of` branches. + + case small3: + of small1: discard + elif false: discard + else: discard + + +block case_statement: + var value = 0 + + ## Expression in case statement is evaluated once and then matched against. + ## Each `of` branch in the `case` body consists of a **constant** list of + ## values to match against - it means the values must be known at a compile + ## time, by either being specified as a literal value, or provided via `const`. + case small3: + of small1: value = 12 + of small2: value = 13 + of small3: value = 14 + + doAssert value == 14 + +block case_expression: + ## It is possible to use case as an expression, but only when all of the + ## values are explicitly handled and each branch body is either and expression + ## or noreturn statement. Value of the each branch must be + + block case_break: + block name_1: + let value = case small1: + of small1: 12 + of small2: 13 + else: break name_1 + + doAssert value == 12 + + block name_2: + let value = case small3: + of small1: 12 + of small2: 13 + else: + doAssert true + break name_2 + + ## This assert will never be exectude because previous statement executed + ## `break name_2` statement. + doAssert false + + block case_return: + proc casewrap() = + let value = case small1: + of small1: 12 + of small2: 13 + else: return + + doAssert value == 12 + + casewrap() + +block case_for_strings: + ## In addition to handling ordinal values, `case` also supports matching + ## against strings. + + var value = 0 + case "string": + of "str": + doAssert false + + of "string": + value = 1 + + doAssert value == 1 + + ## When used with strings, `case` is not checked for exhaustiveness. It is + ## still necessary to include an `else` branch in order to be able to use + ## `case` over strings in expressions. + + let scase = case "string": + of "str": 12 + of "string": 13 + else: 0 + + doAssert scase == 13 + + + +block case_branches: + ## Each branch of a case statement might contain one or more literal, set + ## expression, range of values or an array of values. + + block of_array: + ## Using array of string values in case statement branch. + const stringArray: array[2, string] = ["value1", "value2"] + + var value = 0 + case "value1": + of "other", stringArray: + value = 1 + + else: + value = 12 + + doAssert value == 1 + + block of_set: + var value = 1 + case small3: + of small1: + value = 2 + + of {small2, small3}: + value = 3 + + doAssert value == 3 diff --git a/tests/lang/s01_basics/s09_control_flow/t04_when.nim b/tests/lang/s01_basics/s09_control_flow/t04_when.nim new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/lang/s01_basics/s09_control_flow/t05_while.nim b/tests/lang/s01_basics/s09_control_flow/t05_while.nim new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/lang/s01_basics/s09_control_flow/t06_procedure_calls.nim b/tests/lang/s01_basics/s09_control_flow/t06_procedure_calls.nim new file mode 100644 index 00000000000..066252de8c6 --- /dev/null +++ b/tests/lang/s01_basics/s09_control_flow/t06_procedure_calls.nim @@ -0,0 +1,5 @@ +## This specifices how control flow is transferred in the procedure body. It +## also shows how exceptions, `defer` and `return` affect the control flow. +## Note - even though exceptions are used in this section. it is **not** a specification +## on how exceptions in general are to be used - (used-defined exceptions, coercion to a +## parent exception types in `as` blocks, exception message, stack trace and so on). \ No newline at end of file diff --git a/tests/lang/s04_warnings_and_hints/readme.md b/tests/lang/s04_warnings_and_hints/readme.md new file mode 100644 index 00000000000..09b54056b66 --- /dev/null +++ b/tests/lang/s04_warnings_and_hints/readme.md @@ -0,0 +1,2 @@ +Tests in this section are used to describe and illustrate compiler +warnings and hints in one place. \ No newline at end of file diff --git a/tests/lang/s05_pragmas/readme.md b/tests/lang/s05_pragmas/readme.md new file mode 100644 index 00000000000..5b8398c3b96 --- /dev/null +++ b/tests/lang/s05_pragmas/readme.md @@ -0,0 +1,4 @@ +This directory contains specification of various pragmas, including ones that are +only related to procedure or object declaration. The only type of pragmas that are +left out of this section are *macro* pragmas - they are fully user-defined and +work a lot more like regular macros than any other pragmas (which are compiler-defined). \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s01_interop/readme.md b/tests/lang/s05_pragmas/s01_interop/readme.md new file mode 100644 index 00000000000..347aa2098d8 --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/readme.md @@ -0,0 +1,4 @@ +This directory contains specification of the pragmas that are used for +interfacing with C, C++ and Js code - `importcpp`, `importc`, `importc`, +`dynlib`, `export`, `cdecl` and so on. It should also be used as a +reference example on how interfacing with different code can be done. \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim new file mode 100644 index 00000000000..01deb5f7d9f --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim @@ -0,0 +1,59 @@ +discard """ +description: ''' +Pragmas for wrapping C code in nim. +''' +targets: "c" +cmd: "nim c -r $options $file" + +joinable: false +""" + +{.compile("t01_c_interop.nim.c", "-DNIM_DEFINED_FLAG").} + +const h = "t01_c_interop.nim.h" + + +block wrap_void_proc: + proc cVoid() {.importc: "c_void", header: "t01_c_interop.nim.h".} + +block wrap_prec_with_value: + proc c_return_int(): cint {.importc, header: h.} + + doAssert c_return_int() == 12 + +block wrap_variadic_c_function: + proc sumVariadic(count: cint): cint {.importc: "c_sum_variadic", varargs, header: h.} + + doAssert sumVariadic(2, 3, 1) == 3 + 1 + doAssert sumVariadic(3, 0, 0, 0) == 0 + 0 + 0 + +block wrap_struct_with_no_typedef: + type + NoTypedef {.importc: "struct NoTypedef".} = object + field1: cint + field2: cint + field3 {.importc: "__field3".}: cint + + proc returnNoTypedef(f1, f2, f3: cint): NoTypedef {.importc: "c_return_no_typedef", header: h.} + + let res = returnNoTypedef(1, 2, 3) + + doAssert res.field1 == 1 + doAssert res.field2 == 2 + doAssert res.field3 == 3 + +block wrap_proc_without_header: + proc compileOnly(arg: cint): cint {.importc: "c_compiled_only".} + + doAssert compileOnly(1) == 1 * 2 + + proc inDefine(): cint {.importc: "c_in_define".} + + doAssert inDefine() == 228 + +block wrap_typedefed_struct: + type + WithTypedef {.importc.} = object + field1: cint + field2: cint + field3 {.importc: "__field3".}: cint \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.c b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.c new file mode 100644 index 00000000000..947a57af876 --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.c @@ -0,0 +1,5 @@ +int c_compiled_only(int arg) { return arg * 2; } + +#ifdef NIM_DEFINED_FLAG +int c_in_define() { return 228; }; +#endif \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.h b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.h new file mode 100644 index 00000000000..99eb47b6a7d --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t01_c_interop.nim.h @@ -0,0 +1,55 @@ +#ifndef __C_HEADER_H_ +#define __C_HEADER_H_ + +#include + +struct NoTypedef { + int field1; + int field2; + int __field3; +}; + +typedef struct WithTypedef { + int field1; + const int field2; + int __field3; +} WithTypedef; + +void c_void() {} + +int c_return_int() { return 12; } + +int c_increment_by_two(int arg) { (void)(arg); return arg + 2; } + +int c_sum_variadic(int count, ...) { + int result = 0; + + va_list args; + va_start(args, count); + + for (int i = 0; i < count; ++i) { + result += va_arg(args, int); + } + + va_end(args); + + return result; +} + +struct NoTypedef c_return_no_typedef(int f1, int f2, int f3) { + struct NoTypedef result; + result.field1 = f1; + result.field2 = f2; + result.__field3 = f3; + + return result; +} + +WithTypedef c_return_with_typedef(int f1, int f2, int f3) { + WithTypedef result = {.field1 = f1, .field2 = f2, .__field3 = f3}; + return result; +} + + + +#endif // __C_HEADER_H_ diff --git a/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim b/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim new file mode 100644 index 00000000000..1583f743161 --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim @@ -0,0 +1,122 @@ +discard """ +description: ''' +Pragmas for wrapping C++ code in nim. +''' +targets: "cpp" +#specifying target does not work when I do `testament run` +cmd: "nim cpp -r $options $file" +joinable: false +""" + +const h = "t02_cxx_interop.nim.hpp" + +block complete_struct: + ## If C or C++ type has been mapped *exactly*, 1:1, it is possible to annotate + ## nim wrapper with `completeStruct` pragma - in this case `sizeof()` operation + ## would work correctly (that is - match with `sizeof()` on the C++ side). + type + CxxComplete {.importcpp, header: h, completeStruct.} = object + field1: cint + field2: cint + + ## `selfSizeof()` is needed in order to test `sizeof()` correctness - it simply + ## returns size of the object as seen on the C++ side. + proc selfSizeof(it: CxxComplete): cint {.importcpp: "#.selfSizeof()".} + + doAssert sizeof(CxxComplete) == sizeof(cint) * 2 + doAssert sizeof(CxxComplete) == CxxComplete().selfSizeof() + + ## If sizeo of the object is know it is even possible to perform low-level + ## cast operations. Note that first element in the array has to be explicitly + ## converted to the `cint`, since default `int` type does guarantee full + ## binary compatibility with C integger type. + let values = cast[array[2, CxxComplete]]([cint(1), 2, 3, 4]) + + doAssert values[0] == CxxComplete(field1: 1, field2: 2) + doAssert values[1] == CxxComplete(field1: 3, field2: 4) + +block wrapping_operators: + type + TestOperators {.importcpp, header: h, bycopy.} = object + value: cint + + block addition: + proc `+`(lhs, rhs: TestOperators): TestOperators {.importcpp: "(# + #)".} + + doAssert (TestOperators(value: 1) + TestOperators(value: 2)).value == 3 + + block set_to_array: + ## Nim does not have an alternative to C++ lvalvue reference type (T& is returned + ## by `[]` operator when resulting value needs to be modified). In order to be + ## able to set value to array index it needs to be wrapped using `#[#] = #` pattern + proc `[]=`(lhs: var TestOperators, idx: int, value: int) {.importcpp: "#[#] = #".} + + var test = TestOperators(value: 12) + doAssert test.value == 12 + + test[1] = 2 + + doAssert test.value == 2 + + ## It is also possible to wrap `[]` operator as pure getter as well. + proc `[]`(lhs: var TestOperators, idx: int): int {.importcpp: "#[#]".} + + let value = test[10] + + doAssert test.value == 2 + 10 + doAssert value == test.value + + +block wrapping_template_cxx_types: + type + CxxTemplate[T1, T2] {.importcpp: "CxxTemplate", header: h.} = object + field1: T1 + field2: T2 + + block wrap_default_constructor: + proc initCxxTemplate[T1, T2](): CxxTemplate[T1, T2] = + proc aux(tmp1: typedesc[T1], tmp2: typedesc[T2]): CxxTemplate[T1, T2] + {.importcpp: "CxxTemplate<'*1, '*2>()".} + + return aux(T1, T2) + + var wrap = initCxxTemplate[int, float]() + wrap.field1 = 0 + wrap.field2 = 0.0 + + block wrap_constructor_with_arguments: + proc initCxxTemplate[T1, T2](arg1: T1, arg2: T2): CxxTemplate[T1, T2] + {.importcpp: "CxxTemplate<'*1, '*2>(@)".} + + let wrap = initCxxTemplate(12, 2) + + doAssert wrap.field1 == 12 + doAssert wrap.field2 == 2 + +block wrapping_cxx_type_with_default_constructor: + type + CxxWithDefault {.importcpp, header: h.} = object + field: cint + + + proc initCxxWithDefault(): CxxWithDefault {.constructor, importcpp: "CxxWithDefault()".} + + doAssert initCxxWithDefault().field == 12 + + let val = initCxxWithDefault() + doAssert val.field == 12 + + doAssert CxxWithDefault(field: 13).field == 13 + +block wrapping_cxx_type_without_default_constructor: + type + CxxNoDefault {.importcpp, header: h.} = object + field: cint + + proc initCxxNoDefault(arg: cint): CxxNoDefault {.importcpp: "CxxNoDefault(@)", constructor.} + + when false: # FIXME this code fails with Cxx codegen error - `constructor` does not work properly + # it still creates a temporary variable. + doAssert initCxxNoDefault(10).field == 10 + + diff --git a/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim.hpp b/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim.hpp new file mode 100644 index 00000000000..f981d56c4e3 --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t02_cxx_interop.nim.hpp @@ -0,0 +1,62 @@ +#ifndef __CXX_HEADER_H_ +#define __CXX_HEADER_H_ + +struct CxxStruct {}; + +struct CxxForward; + +struct __cxx_internal{ + int __field; + CxxForward* forward; +}; + +struct CxxComplete { + int field1; + int field2; + + const int selfSizeof() { + return sizeof(*this); + } +}; + +struct TestOperators { + int value; + + int& operator[](int idx) { + value += idx; + return value; + } +}; + +TestOperators operator+(TestOperators lhs, TestOperators rhs) { + return {lhs.value + rhs.value}; +} + + + + +template +struct CxxTemplate { + T1 field1; + T2 field2; + + CxxTemplate() {} + CxxTemplate(T1 arg1, T2 arg2) : field1(arg1), field2(arg2) {} + CxxTemplate(T1 arg1) : field1(arg1), field2(12) {} +}; + +struct CxxWithDefault { + int field = 12; +}; + + +struct CxxNoDefault { + int field; + CxxNoDefault(int arg) {} + + const int getField() { return field; } + void setField(int val) { field = val; } + +}; + +#endif // __CXX_HEADER_H_ diff --git a/tests/lang/s05_pragmas/s01_interop/t03_emit.nim b/tests/lang/s05_pragmas/s01_interop/t03_emit.nim new file mode 100644 index 00000000000..e15d399f19d --- /dev/null +++ b/tests/lang/s05_pragmas/s01_interop/t03_emit.nim @@ -0,0 +1,26 @@ +block emit_type: + {.emit: """/*TYPESECTION*/ +struct CStruct { int field; }; +""".} + + type + CStruct {.importc: "struct CStruct".} = object + field: cint + + + var struct = CStruct() + struct.field = 12 + +block interpolate_variables: + proc impl() = + var nimValue: cint = 0 + + doAssert nimValue == 0 + {.emit: [nimValue, " += 2;"].} + + doAssert nimValue == 2 + + {.emit: "`nimValue` += 2;".} + doAssert nimValue == 4 + + impl() \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s02_misc/readme.md b/tests/lang/s05_pragmas/s02_misc/readme.md new file mode 100644 index 00000000000..2e16b61902b --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/readme.md @@ -0,0 +1,2 @@ +This section contains specification of pragmas that cannot be characterized as +purely "interop" \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s02_misc/t01_ident_pragmas.nim b/tests/lang/s05_pragmas/s02_misc/t01_ident_pragmas.nim new file mode 100644 index 00000000000..02b05d6f02c --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t01_ident_pragmas.nim @@ -0,0 +1,29 @@ +discard """ +description: ''' +Test pragma annotations that can be used on identifier declaratios. +''' + +cmd: "nim c -r -d:strdefineUsed='test' -d:intdefineUsed=2 -d:booldefineUsed=false $options $file" + +""" + +## It is possible to put pragma annotations on different variable declarations. + +block const_define: + const strdefineDefault {.strdefine.} = "default value" + const strdefineUsed {.strdefine.} = "default value" + + doAssert strdefineDefault == "default value" + doAssert strdefineUsed == "test" + + const intdefineDefault {.intdefine.} = 12 + const intdefineUsed {.intdefine.} = 12 + + doAssert intdefineDefault == 12 + doAssert intdefineUsed == 2 + + const booldefineDefault {.booldefine.} = true + const booldefineUsed {.booldefine.} = true + + doAssert booldefineDefault == true + doAssert booldefineUsed == false diff --git a/tests/lang/s05_pragmas/s02_misc/t02_flag_pragmas.nim b/tests/lang/s05_pragmas/s02_misc/t02_flag_pragmas.nim new file mode 100644 index 00000000000..429a743a05a --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t02_flag_pragmas.nim @@ -0,0 +1,67 @@ +discard """ +description: ''' +Disable or enable safety checks +''' +""" + +## The listed pragmas here can be used to override the code +## generation options for a proc/method/converter. + +block enable_bound_checking: + {.push boundChecks:on.} + proc impl() = + var gotDefect = false + try: + var a: array[10, int] + let idx = 9 + assert cast[ptr array[9, int]](a.addr)[idx] == 0 + + except IndexDefect: + gotDefect = true + + doAssert gotDefect + + {.pop.} + + impl() + +block disable_bound_checking: + {.push boundChecks:off.} + proc impl() = + + var a: array[10, int] + let idx = 9 + a[idx] = 10 + assert cast[ptr array[9, int]](a.addr)[idx] == 10 + + + {.pop.} + + impl() + +block enable_overflow_checks: + {.push overflowChecks:on.} + proc impl() = + var gotDefect = false + try: + var value = high(int) + inc value + + except OverflowDefect: + gotDefect = true + + doAssert gotDefect + + {.pop.} + + impl() + +block disable_overflow_checks: + {.push overflowChecks:off.} + proc impl() = + var value = high(int) + inc value + + {.pop.} + + impl() \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s02_misc/t03_hint_pragmas.nim b/tests/lang/s05_pragmas/s02_misc/t03_hint_pragmas.nim new file mode 100644 index 00000000000..3942d417997 --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t03_hint_pragmas.nim @@ -0,0 +1,39 @@ +discard """ +description: ''' +`{.hint.}` pragma test +''' + +nimout: ''' +t03_hint_pragmas.nim(15, 7) Hint: User-provided hint message [User] +t03_hint_pragmas.nim(19, 9) Hint: gen1 size of the argument is 8 [User] +t03_hint_pragmas.nim(31, 6) Hint: 'declaredButNotUsed' is declared but not used [XDeclaredButNotUsed] +''' + + +""" + +{.hint: "User-provided hint message".} + + +proc gen1[T](arg: T) = + {.hint: "gen1 size of the argument is " & $sizeof(arg).} + +gen1[int](1) + +when defined(tryBrokenSpecification): + proc gen2[T](arg: T) = + {.line: instantiationInfo().}: + {.hint: "gen2 size of the argument is " & $sizeof(arg).} + + gen2[int](1) + + +proc declaredButNotUsed() = discard + +when defined(tryBrokenSpecification): + # FIXME - does not work, hint is still emitted + {.push hint[XDeclaredButNotUsed]:off.} + + proc declaredButNotUsedOff() = discard + + {.pop.} \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s02_misc/t04_warning.nim b/tests/lang/s05_pragmas/s02_misc/t04_warning.nim new file mode 100644 index 00000000000..ce590d1c46b --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t04_warning.nim @@ -0,0 +1,23 @@ +discard """ +description: ''' +`{.warning.}` pragma test +''' + +nimout: ''' +t04_warning.nim(17, 10) Warning: User-provided warning message [User] +t04_warning.nim(22, 5) template/generic instantiation of `gen1` from here +t04_warning.nim(20, 12) Warning: gen1 size of the argument is 8 [User] +t04_warning.nim(23, 5) template/generic instantiation of `gen1` from here +t04_warning.nim(20, 12) Warning: gen1 size of the argument is 8 [User] +''' + + +""" + +{.warning: "User-provided warning message".} + +proc gen1[T](arg: T) = + {.warning: "gen1 size of the argument is " & $sizeof(arg).} + +gen1[int](1) +gen1[float](1.0) diff --git a/tests/lang/s05_pragmas/s02_misc/t05_procedure.nim b/tests/lang/s05_pragmas/s02_misc/t05_procedure.nim new file mode 100644 index 00000000000..7351e4b2849 --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t05_procedure.nim @@ -0,0 +1,9 @@ +proc getGlobal(): int = + ## `state` variable is stored globally (that is - not destroyed when procedure finishes + ## execution), but can only be accessed inside of the procedure body. + var state {.global.}: int + inc state + return state + +doAssert getGlobal() == 1 +doAssert getGlobal() == 2 \ No newline at end of file diff --git a/tests/lang/s05_pragmas/s02_misc/t06_object.nim b/tests/lang/s05_pragmas/s02_misc/t06_object.nim new file mode 100644 index 00000000000..344f209260f --- /dev/null +++ b/tests/lang/s05_pragmas/s02_misc/t06_object.nim @@ -0,0 +1,24 @@ +block union_pragma: + ## The union pragma can be applied to any object type. It means all of the object's + ## fields are overlaid in memory.This produces a union instead of a struct in the + ## generated C/C++ code. + type + Union {.union.} = object + field1: int + field2: array[12, int] + field3: tuple[idx1, idx2: int] + + doAssert sizeof(Union) == max(sizeof(int), sizeof(array[12, int])) + + var uni = Union() + + uni.field1 = 10 + + doAssert uni.field2[0] == 10 + + doAssert uni.field3.idx1 == 10 + + uni = Union(field2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + + doAssert uni.field1 == 1 + doAssert uni.field3 == (1, 2) \ No newline at end of file