Compare commits

...

14 Commits

Author SHA1 Message Date
Chris Harding
ab1f3e3a03
Merge 0c5eb762c4 into 495272cff9 2024-09-28 00:39:46 +00:00
Jason Klebes
495272cff9
[fortran/en] Fixed overly long comment lines (#5126)
Some checks failed
Trigger site build / deploy (push) Has been cancelled
CI / lint (push) Has been cancelled
* [haskell/de] Added missing comment line about index operator

* [fortran/en] Fixed overly long comment lines

Including minor rewording and rearranging.  A comment was added at 'subroutine'
2024-09-25 11:45:27 +02:00
Chris Harding
0c5eb762c4
Update c.html.markdown
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:27:45 -04:00
Chris Harding
db551c63ac
c.html.markdown: line break comment in better spot
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:27:33 -04:00
Chris Harding
e346cbded5
c.html.markdown: copyedits
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:26:55 -04:00
Chris Harding
74f097c2ca
c.html.markdown: remove pragma once mention
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:26:17 -04:00
Chris Harding
29610d715d
c.html.markdown: line break comment in better spot
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:25:52 -04:00
Chris Harding
35a319b3f7
c.html.markdown: better wording about null statement
Co-authored-by: Boris Verkhovskiy <boris.verk@gmail.com>
2024-05-21 20:21:46 -04:00
Chris Harding
ac2ada4cc3 [c/en] add contrib 2023-07-10 14:04:42 -04:00
Chris Harding
122964ebed [c/en] copy; pragma once; note about static inline funcs in headers 2023-07-10 14:03:59 -04:00
Chris Harding
27de08d069 [c/en] mention field initializers for structs 2023-07-10 14:03:37 -04:00
Chris Harding
02ea02e8e8 [c/en] Note about control blocks w/out braces
Semicolon inside empty braces is not required
2023-07-10 13:57:29 -04:00
Chris Harding
790fd6ec72 [c/en] Note about functions w/out void params 2023-07-10 13:41:44 -04:00
Chris Harding
6691524748 [c/en] fix word wrapping- limit lines to 80 chars 2023-07-10 13:31:34 -04:00
2 changed files with 150 additions and 101 deletions

View File

@ -11,6 +11,7 @@ contributors:
- ["Joshua Li", "https://github.com/JoshuaRLi"]
- ["Dragos B. Chirila", "https://github.com/dchirila"]
- ["Heitor P. de Bittencourt", "https://github.com/heitorPB/"]
- ["Chris Harding", "https://github.com/sjrct"]
---
Ah, C. Still **the** language of modern high-performance computing.
@ -27,7 +28,8 @@ memory management and C will take you as far as you need to go.
>
> `-Wall -Wextra -Werror -O2 -std=c99 -pedantic`
>
> For information on what these flags do as well as other flags, consult the man page for your C compiler (e.g. `man 1 gcc`) or just search online.
> For information on what these flags do as well as other flags, consult the man
> page for your C compiler (e.g. `man 1 gcc`) or just search online.
```c
// Single-line comments start with // - only available in C99 and later.
@ -63,26 +65,28 @@ enum days {SUN = 1, MON, TUE, WED = 99, THU, FRI, SAT};
// libraries for the headers.
// For your own headers, use double quotes instead of angle brackets, and
// provide the path:
#include "my_header.h" // local file
#include "my_header.h" // local file
#include "../my_lib/my_lib_header.h" //relative path
// Declare function signatures in advance in a .h file, or at the top of
// your .c file.
void function_1();
int function_2(void);
void function_1(void);
int function_2(int a, float b);
// At a minimum, you must declare a 'function prototype' before its use in any function.
// Normally, prototypes are placed at the top of a file before any function definition.
// At a minimum, you must declare a 'function prototype' before its use in any
// function. Normally, prototypes are placed at the top of a file before any
// function definition.
int add_two_ints(int x1, int x2); // function prototype
// although `int add_two_ints(int, int);` is also valid (no need to name the args),
// it is recommended to name arguments in the prototype as well for easier inspection
// although `int add_two_ints(int, int);` is also valid (no need to name the
// args), it is recommended to name arguments in the prototype as well for
// easier inspection
// Function prototypes are not necessary if the function definition comes before
// any other function that calls that function. However, it's standard practice to
// always add the function prototype to a header file (*.h) and then #include that
// file at the top. This prevents any issues where a function might be called
// before the compiler knows of its existence, while also giving the developer a
// clean header file to share with the rest of the project.
// any other function that calls that function. However, it's standard practice
// to always add the function prototype to a header file (*.h) and then #include
// that file at the top. This prevents any issues where a function might be
// called before the compiler knows of its existence, while also giving the
// developer a clean header file to share with the rest of the project.
// Your program's entry point is a function called "main". The return type can
// be anything, however most operating systems expect a return type of `int` for
@ -125,7 +129,8 @@ int main (int argc, char** argv)
short x_short = 0;
// chars are defined as the smallest addressable unit for a processor.
// This is usually 1 byte, but for some systems it can be more (ex. for TMS320 from TI it's 2 bytes).
// This is usually 1 byte, but for some systems it can be more
// (ex. for TMS320 from TI it's 2 bytes).
char x_char = 0;
char y_char = 'y'; // Char literals are quoted with ''
@ -153,8 +158,8 @@ int main (int argc, char** argv)
// sizeof(obj) yields the size of the expression (variable, literal, etc.).
printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words)
// If the argument of the `sizeof` operator is an expression, then its argument
// is not evaluated (except VLAs (see below)).
// If the argument of the `sizeof` operator is an expression, then its
// argument is not evaluated (except VLAs (see below)).
// The value it yields in this case is a compile-time constant.
int a = 1;
// size_t is an unsigned integer type of at least 2 bytes used to represent
@ -171,15 +176,15 @@ int main (int argc, char** argv)
// You can initialize an array of twenty ints that all equal 0 thusly:
int my_array[20] = {0};
// where the "{0}" part is called an "array initializer".
// All elements (if any) past the ones in the initializer are initialized to 0:
// Elements (if any) past the ones in the initializer are initialized to 0:
int my_array[5] = {1, 2};
// So my_array now has five elements, all but the first two of which are 0:
// [1, 2, 0, 0, 0]
// NOTE that you get away without explicitly declaring the size
// of the array IF you initialize the array on the same line:
// NOTE that you get away without explicitly declaring the size of the
// array IF you initialize the array on the same line:
int my_array[] = {0};
// NOTE that, when not declaring the size, the size of the array is the number
// of elements in the initializer. With "{0}", my_array is now of size one: [0]
// NOTE that, when not declaring the size, the array's size is the number of
// elements in the initializer. With "{0}", my_array is now of size one: [0]
// To evaluate the size of the array at run-time, divide its byte size by the
// byte size of its element type:
size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]);
@ -249,7 +254,7 @@ int main (int argc, char** argv)
i2 * i1; // => 2
i1 / i2; // => 0 (0.5, but truncated towards 0)
// You need to cast at least one integer to float to get a floating-point result
// You need to cast at least one int to float to get a floating-point result
(float)i1 / i2; // => 0.5f
i1 / (double)i2; // => 0.5 // Same with double
f1 / f2; // => 0.5, plus or minus epsilon
@ -366,13 +371,17 @@ int main (int argc, char** argv)
printf("\n");
// *****NOTES*****:
// Loops and Functions MUST have a body. If no body is needed:
// If the body of control expression is only one statement then parentheses
// are optional, but recommended:
for (jj = 0; jj < 5; jj++)
printf("%d\n", jj);
// Loops and if statements MUST have a body. If no body is needed:
int i;
for (i = 0; i <= 5; i++) {
; // use semicolon to act as the body (null statement)
}
// Or
for (i = 0; i <= 5; i++);
for (i = 0; i <= 5; i++); // semicolon acts as the body (null statement)
// branching with multiple choices: switch()
switch (a) {
@ -441,8 +450,9 @@ int main (int argc, char** argv)
// without warning.
printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long)
// For determining the max value of a `char`, a `signed char` and an `unsigned char`,
// respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX macros from <limits.h>
// For determining the max value of a `char`, a `signed char` and an
// `unsigned char`, respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX
// macros from <limits.h>
// Integral types can be cast to floating-point types, and vice-versa.
printf("%f\n", (double) 100); // %f always formats a double...
@ -453,9 +463,9 @@ int main (int argc, char** argv)
// Pointers
///////////////////////////////////////
// A pointer is a variable declared to store a memory address. Its declaration will
// also tell you the type of data it points to. You can retrieve the memory address
// of your variables, then mess with them.
// A pointer is a variable declared to store a memory address. Its declaration
// will also tell you the type of data it points to. You can retrieve the
// memory address of your variables, then mess with them.
int x = 0;
printf("%p\n", (void *)&x); // Use & to retrieve the address of a variable
@ -493,9 +503,9 @@ int main (int argc, char** argv)
int* x_ptr = x_array;
// x_ptr now points to the first element in the array (the integer 20).
// This works because arrays often decay into pointers to their first element.
// For example, when an array is passed to a function or is assigned to a pointer,
// it decays into (implicitly converted to) a pointer.
// Exceptions: when the array is the argument of the `&` (address-of) operator:
// For example, when an array is passed to a function or is assigned to a
// pointer, it decays into (implicitly converted to) a pointer.
// Exception: when the array is the argument of the `&` (address-of) operator:
int arr[10];
int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`!
// It's of type "pointer to array" (of ten `int`s).
@ -514,22 +524,23 @@ int main (int argc, char** argv)
// You can also dynamically allocate contiguous blocks of memory with the
// standard library function malloc, which takes one argument of type size_t
// representing the number of bytes to allocate (usually from the heap, although this
// may not be true on e.g. embedded systems - the C standard says nothing about it).
// representing the number of bytes to allocate (usually from the heap,
// although this may not be true on e.g. embedded systems - the C standard
// says nothing about it).
int *my_ptr = malloc(sizeof(*my_ptr) * 20);
for (xx = 0; xx < 20; xx++) {
*(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints)
// Be careful passing user-provided values to malloc! If you want
// to be safe, you can use calloc instead (which, unlike malloc, also zeros out the memory)
// Be careful passing user-provided values to malloc! If you want to be safe,
// you can use calloc instead (which, unlike malloc, also zeros the memory)
int* my_other_ptr = calloc(20, sizeof(int));
// Note that there is no standard way to get the length of a
// dynamically allocated array in C. Because of this, if your arrays are
// going to be passed around your program a lot, you need another variable
// to keep track of the number of elements (size) of an array. See the
// functions section for more info.
// Note that there is no standard way to get the length of a dynamically
// allocated array in C. Because of this, if your arrays are going to be
// passed around your program a lot, you need another variable to keep track
// of the number of elements (size) of an array. See the functions section
// for more info.
size_t size = 10;
int *my_arr = calloc(size, sizeof(int));
// Add an element to the array
@ -541,9 +552,9 @@ int main (int argc, char** argv)
}
my_arr[10] = 5;
// Dereferencing memory that you haven't allocated gives
// "unpredictable results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash.
// Dereferencing memory that you haven't allocated gives "unpredictable
// results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash
// When you're done with a malloc'd block of memory, you need to free it,
// or else no one else can use it until your program terminates
@ -552,8 +563,8 @@ int main (int argc, char** argv)
// Strings are arrays of char, but they are usually represented as a
// pointer-to-char (which is a pointer to the first element of the array).
// It's good practice to use `const char *' when referring to a string literal,
// since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.)
// It's good practice to use `const char *' when referring to a string literal
// since string literals shall not be modified (i.e. foo[0] = 'a' is ILLEGAL.)
const char *my_str = "This is my very own string literal";
printf("%c\n", *my_str); // => 'T'
@ -635,11 +646,11 @@ printf("first: %d\nsecond: %d\n", first, second);
// Return multiple values.
// C does not allow for returning multiple values with the return statement. If
// you would like to return multiple values, then the caller must pass in the
// variables where they would like the returned values to go. These variables must
// be passed in as pointers such that the function can modify them.
int return_multiple( int *array_of_3, int *ret1, int *ret2, int *ret3)
// variables where they would like the returned values to go. These variables
// must be passed in as pointers such that the function can modify them.
int return_multiple(int *array_of_3, int *ret1, int *ret2, int *ret3)
{
if(array_of_3 == NULL)
if (array_of_3 == NULL)
return 0; //return error code (false)
//de-reference the pointer so we modify its value
@ -672,15 +683,15 @@ printIntArray(my_arr, size);
// will print "arr[0] is: 1" etc
*/
// if referring to external variables outside function, you should use the extern keyword.
// if referring to external variables outside function, use the extern keyword.
int i = 0;
void testFunc() {
void testFunc(void) {
extern int i; //i here is now using external variable i
}
// make external variables private to source file with static:
static int j = 0; //other files using testFunc2() cannot access variable j
void testFunc2() {
void testFunc2(void) {
extern int j;
}
// The static keyword makes a variable inaccessible to code outside the
@ -693,6 +704,18 @@ void testFunc2() {
// declared with some other starting value.
//**You may also declare functions as static to make them private**
// Note that before C23, and unlike C++, functions taking no arguments without
// an explicit `void` inside the parameter list will be treated as taking an
// unknown number of arguments rather than no arguments.
void testFunc3(void) {
// Functions can be prototyped inside other functions
void foobie();
void bletch(void);
foobie(1, 2, 3); // This will give a warning at most, not an error
bletch(1, 2, 3); // This will produce an error
}
///////////////////////////////////////
// User-defined types and structs
///////////////////////////////////////
@ -713,10 +736,16 @@ struct rectangle {
// due to potential padding between the structure members (this is for alignment
// reasons). [1]
void function_1()
void function_1(void)
{
struct rectangle my_rec = { 1, 2 }; // Fields can be initialized immediately
// Fields can also be initialized in an arbitrary order with the field name
struct rectangle my_rec2 = {
.height = 2,
.width = 1
};
// Access struct members with .
my_rec.width = 10;
my_rec.height = 20;
@ -760,10 +789,10 @@ int areaptr(const rect *r)
// Function pointers
///////////////////////////////////////
/*
At run time, functions are located at known memory addresses. Function pointers are
much like any other pointer (they just store a memory address), but can be used
to invoke functions directly, and to pass handlers (or callback functions) around.
However, definition syntax may be initially confusing.
At run time, functions are located at known memory addresses. Function pointers
are much like any other pointer (they just store a memory address), but can be
used to invoke functions directly, and to pass handlers (or callback functions)
around. However, definition syntax may be initially confusing.
Example: use str_reverse from a pointer
*/
@ -773,12 +802,13 @@ void str_reverse_through_pointer(char *str_in) {
f = &str_reverse; // Assign the address for the actual function (determined at run time)
// f = str_reverse; would work as well - functions decay into pointers, similar to arrays
(*f)(str_in); // Just calling the function through the pointer
// f(str_in); // That's an alternative but equally valid syntax for calling it.
// f(str_in); // That's an alternative but equally valid syntax for calling it
}
/*
As long as function signatures match, you can assign any function to the same pointer.
Function pointers are usually typedef'd for simplicity and readability, as follows:
As long as function signatures match, you can assign any function to the same
pointer. Function pointers are usually typedef'd for simplicity and readability,
as follows:
*/
typedef void (*my_fnp_type)(char *);
@ -864,8 +894,8 @@ as the C file.
*/
/* A safe guard to prevent the header from being defined too many times. This */
/* happens in the case of circle dependency, the contents of the header is */
/* already defined. */
/* happens in the case of circlular dependencies, or such as when a header is */
/* included alongside a header that includes the same header itself. */
#ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */
#define EXAMPLE_H /* Define the macro EXAMPLE_H. */
@ -895,9 +925,10 @@ typedef struct Node
/* So can enumerations. */
enum traffic_light_state {GREEN, YELLOW, RED};
/* Function prototypes can also be defined here for use in multiple files, */
/* but it is bad practice to define the function in the header. Definitions */
/* should instead be put in a C file. */
/* Function prototypes can also be defined here for use in multiple files, */
/* but it is bad practice to define the function in the header. Definitions */
/* should instead be put in a C file. An uncommon exception is when defining */
/* static inline functions. */
Node createLinkedList(int *vals, int len);
/* Beyond the above elements, other definitions should be left to a C source */

View File

@ -30,7 +30,8 @@ program example ! declare a program called example.
! All declarations must come before statements and expressions.
implicit none ! prevents dynamic declaration of variables (recommended!)
implicit none ! prevents dynamic declaration of variables
! Recommended!
! Implicit none must be redeclared in every function/program/module...
! IMPORTANT - Fortran is case insensitive.
@ -45,10 +46,14 @@ program example ! declare a program called example.
complex :: w = (0, 1) ! sqrt(-1)
character(len=3) :: month ! string of 3 characters.
real :: array(6) ! declare an array of 6 reals.
real, dimension(4) :: arrayb ! another way to declare an array.
integer :: arrayc(-10:10) ! an array with a custom index.
real :: array2d(3, 2) ! multidimensional array.
! declare an array of 6 reals.
real :: array(6)
! another way to declare an array.
real, dimension(4) :: arrayb
! an array with a custom index -10 to 10 (inclusive)
integer :: arrayc(-10:10)
! A multidimensional array.
real :: array2d(3, 2)
! The '::' separators are not always necessary but are recommended.
@ -76,8 +81,8 @@ program example ! declare a program called example.
! Assignment & Arithmetic
! =======================
Z = 1 ! assign to variable z declared above (case insensitive).
Z = 1 ! assign to variable z declared above
j = 10 + 2 - 3
a = 11.54/(2.3*3.1)
b = 2**3 ! exponentiation
@ -86,7 +91,7 @@ program example ! declare a program called example.
! ===================================
! Single-line if statement
if (z == a) b = 4 ! condition always need surrounding parentheses.
if (z == a) b = 4 ! conditions always need parentheses.
if (z /= a) then ! z not equal to a
! Other symbolic comparisons are < > <= >= == /=
@ -98,13 +103,13 @@ program example ! declare a program called example.
b = 5 ! execution block must be on a new line.
else
b = 10
end if ! end statement needs the 'if' (or can use 'endif').
end if ! end statement needs the 'if'
if (.NOT. (x < c .AND. v >= a .OR. z == z)) then ! boolean operators.
inner: if (.TRUE.) then ! can name if-construct.
b = 1
end if inner ! then must name endif statement.
end if
endif ! 'endif' is equivalent to 'end if'
i = 20
select case (i)
@ -128,16 +133,16 @@ program example ! declare a program called example.
j = -1
end select monthly
do i = 2, 10, 2 ! loops from 2 to 10 (inclusive) in increments of 2.
do i = 2, 10, 2 ! loops from 2 to 10 (inclusive) in steps of 2.
innerloop: do j = 1, 3 ! loops can be named too.
exit ! quits the loop.
end do innerloop
cycle ! jump to next loop iteration.
end do
! Goto statement exists but it is heavily discouraged though.
! Goto statement exists but it is heavily discouraged.
goto 10
stop 1 ! stops code immediately (returning specified condition code).
stop 1 ! stops the program, returns condition code 1.
10 j = 201 ! this line is labeled as line 10
! Arrays
@ -209,8 +214,12 @@ program example ! declare a program called example.
! we can have multiple format specifications.
print "(I5,F6.2,E6.2)", 120, 43.41, 43.41
print "(3I5)", 10, 20, 30 ! 3 repeats of integers (field width = 5).
print "(2(I5,F6.2))", 120, 43.42, 340, 65.3 ! repeated grouping of formats.
! 3 repeats of integers (field width = 5).
print "(3I5)", 10, 20, 30
! repeated grouping of formats.
print "(2(I5,F6.2))", 120, 43.42, 340, 65.3
! We can also read input from the terminal.
read (*, *) v
@ -225,8 +234,9 @@ program example ! declare a program called example.
! To read a file.
open (newunit=m, file="records.txt", status="old")
! The file is referred to by a 'new unit number', an integer that the compiler
! picks for you.
! The file is referred to by a 'new unit number',
! an integer that the compiler picks for you.
read (unit=m, fmt="(3F10.2)") a, b, c
close (m)
@ -241,7 +251,7 @@ program example ! declare a program called example.
call cpu_time(v) ! sets 'v' to a time in seconds.
k = ior(i, j) ! bitwise OR of 2 integers.
v = log10(x) ! log base 10.
i = floor(b) ! returns the closest integer less than or equal to x.
i = floor(b) ! converts b to integer by rounding down.
v = aimag(w) ! imaginary part of a complex number.
! Functions & Subroutines
@ -252,7 +262,7 @@ program example ! declare a program called example.
call routine(a, c, v) ! subroutine call.
! A function takes a list of input parameters and returns a single value.
! A function takes several input parameters and returns a single value.
! However the input parameters may still be modified and side effects
! executed.
@ -261,21 +271,22 @@ program example ! declare a program called example.
! Function calls can also be evoked within expressions.
print *, func2(3, 2, k)
! A pure function is a function that doesn't modify its input parameters
! or cause any side-effects.
! A pure function is a function that doesn't modify its input
! parameters or cause any side-effects.
m = func3(3, 2, k)
contains ! Zone for defining sub-programs internal to the program.
contains ! Start defining the program's internal procedures:
! Fortran has a couple of slightly different ways to define functions.
integer function func(a, b, c) ! a function returning an integer value.
! implicit none ! subvariable fields can no longer declare implicit none
integer, intent(in) :: a, b, c ! type of input parameters defined inside the function.
! implicit none ! - no longer used in subvariable fields
integer, intent(in) :: a, b, c ! type of input parameters
! the return variable defaults to the function name.
if (a >= 2) then
func = a + b + c ! the return variable defaults to the function name.
return ! can return the current value from the function at any time.
func = a + b + c
return ! returns the current value at 'func'
end if
func = a + c
@ -286,24 +297,29 @@ contains ! Zone for defining sub-programs internal to the pro
integer, intent(in) :: a, b ! can declare and enforce that variables
!are not modified by the function.
integer, intent(inout) :: c
integer :: f ! function return type declared inside the function.
integer :: cnt = 0 ! GOTCHA - initialisation implies variable is
!saved between function calls.
integer :: f
! function return type declared inside the function.
integer :: cnt = 0 ! GOTCHA -
! assigning a value at initalization
! implies that the variable is
! saved between function calls.
f = a + b - c
c = 4 ! altering the value of an input variable.
c = 4 ! changing value of input variable c.
cnt = cnt + 1 ! count number of function calls.
end function func2
pure function func3(a, b, c) ! a pure function can have no side-effects.
pure function func3(a, b, c) ! a pure function has no side-effects.
integer, intent(in) :: a, b, c
integer :: func3
func3 = a*b*c
end function func3
! a subroutine does not return anything,
! but can change the value of arguments.
subroutine routine(d, e, f)
real, intent(inout) :: f
real, intent(in) :: d, e
@ -312,7 +328,8 @@ contains ! Zone for defining sub-programs internal to the pro
end subroutine routine
end program example ! End of Program Definition -----------------------
end program example
! End of Program Definition -----------------------
! Functions and Subroutines declared externally to the program listing need
! to be declared to the program using an Interface declaration (even if they
@ -350,7 +367,8 @@ module fruity
use fruit, only: apple, pear ! use apple and pear from fruit module.
implicit none ! comes after module imports.
private ! make things private to the module (default is public).
! By default all module data and functions will be public
private ! Instead set default to private
! Declare some variables/functions explicitly public.
public :: apple, mycar, create_mycar
! Declare some variables/functions private to the module (redundant here).