Giter Club home page Giter Club logo

test's People

Contributors

qtxie avatar

Watchers

 avatar

test's Issues

Coding Style Guide

Table of Contents

Introduction

Red is an homoiconic language where code is represented as data. A consequence of that feature is that the language is almost totally free-form, in order to cope with all possible ways to format data, as well as being flexible enough for DSL-specific formatting needs. The following content is just one of many, many ways to format Red code.

This document describes the official coding style used in the Red source code, so respecting this coding style is a prerequisite of every pull request submitted to red/red Github repository.

As Red/System is a dialect of Red, it shares the same syntax and coding style rules. Specific Red/System rules are marked as such.

The objectives of the following rules are to maximise readability including keeping an optimum number of lines of code visible on-screen while minimizing the need for comments.

Line of code length

There is no strictly-defined maximum number of columns for a single line of code, as it can vary according to the type of the font used (size, proportional vs fixed-width) or highlighting effects. It should be possible to read a full line of code (excluding comments) in an editor occupying at maximum, half of a 1080p monitor width. On the displays we use for Red codebase coding, it is about 100 columns. In the description below, excessive size or too long expressions will refer to line of code sizes which do not fit the aforementioned criteria.

Indentation

Red codebase uses tabulations with size of 4 columns to indent the source code. This gives a good trade-off between too small values (like 2 columns) and too big ones (like 8 columns). Using tabs also means that you can adjust it to your personal preference in your editor, while respecting the rule (just pay attention to right alignments using tabs then).

All the contributed Red files to red/red repo should contain the following field in their header:

Tabs: 4

Each time you go to a new line after opening a block or a parenthesis, you should indent by one tab.

Correct

func [
	arg1
	arg2
	...
][
	print arg1
	...
]

Incorrect

func [
arg1					;-- missing indentation!
arg2
...
][
		print arg1		;-- excessive indentation!
		...
]

Block layouts

All the following rules apply to blocks [] as well as parenthesis ().

Empty blocks do not contain any whitespace:

a: []

Contiguous blocks do not require a whitespace between the end of one and start of another:

[][]
[]()

[
	hello
][						;-- no spacing required
	world
]

However, it is acceptable to use whitespaces in-between nested blocks:

array: [[] [] [] []]
list:  [ [] [] [] [] ]

either a = 1 [["hello"]][["world"]]
either a = 1 [ ["hello"] ][ ["world"] ]

For expressions containing small blocks, they are usually opened and closed on the same line

b: either a = 1 [a + 1][3]

If the line is too long, the block should be wrapped over several lines with one level of indentation:

Correct

b: either a = 1 [
	a + 1
][
	123 + length? mold a
]

Incorrect

b: either a = 1 
	[a + 1]
	[123 + length? mold a]

That style is wrong because it breaks the ability to copy/paste code to the Red console (either will be evaluated before the block arguments are detected).

If the first block is small enough and can fit on the same line, then only the subsequent blocks are wrapped over several lines:

print either a = 1 ["hello"][
	append mold a "this is a very long expression"
]

while [not tail? series][
	print series/1
	series: next series
]

Naming conventions

Variable names should be single-word nouns. Choose words which are short and capturing the meaning as best as possible. Common words should be used first (especially if they are already used in existing Red source code in the same context). If needed, use a synonyms dictionary, to find the best word for the usage. Single-letter or abbreviated words (unless the abbreviated word is in common usage) should be avoided as much as possible.

Names made of multiple words are separated with a dash - character. Use a two-words name only when a fitting single-word cannot be found or would be too confusing with already used ones. Variable names made of more than two words should only be used in rare cases. Using single-words as much as possible makes the code horizontally much more compact, improving readability greatly. Avoid useless verbosity.

Correct

code: 123456
name: "John"
table: [2 6 8 4 3]
lost-items: []

unless tail? list [author: select list index]

Incorrect

code_for_article: 123456
Mytable: [2 6 8 4 3]
lostItems: []

unless tail? list-of-books [author-property: select list-of-books selected-index]

Function names should strive to be single-word verbs, in order to express an action, though two or three words names are often necessary. More than three words should be avoided as much as possible. Variable naming conventions also apply to function names. A noun or an adjective followed by a question mark is also accepted. Often, it denotes that the return value is of logic! type, but this is not a strict rule, as it is handy to form single-word action names for retrieving a property (e.g. length?, index?). When forming function names with two or more words, always put the verb in the first position. If names are picked carefully for variables and function names, the code becomes almost self-documented, often reducing the need for comments.

Correct

make: 	func [...
reduce: func [...
allow: 	func [...
crunch: func [...

Incorrect

length:    func [...
future:    func [...
position:  func [...
blue-fill: func [...		;-- should be fill-blue

There is an exception to those naming rules which applies to OS or non-Red third-party API names. In order to make API-specific function and structures field names easily recognizable, their original name should be used. It visually helps distinguish such imported names from regular Red or Red/System code. For example:

tagMSG: alias struct! [
	hWnd	[handle!]
	msg		[integer!]
	wParam	[integer!]
	lParam	[integer!]
	time	[integer!]
	x		[integer!]
	y		[integer!]	
]

#import [
	"User32.dll" stdcall [
		CreateWindowEx: "CreateWindowExW" [
			dwExStyle	 [integer!]
			lpClassName	 [c-string!]
			lpWindowName [c-string!]
			dwStyle		 [integer!]
			x			 [integer!]
			y			 [integer!]
			nWidth		 [integer!]
			nHeight		 [integer!]
			hWndParent	 [handle!]
			hMenu	 	 [handle!]
			hInstance	 [handle!]
			lpParam		 [int-ptr!]
			return:		 [handle!]
		]
	]
]

Casing

All variable and function names should be lowercase by default, unless there is a good reason for using uppercasing such as:

  • name is an acronym e.g. GMT (Greenwich Mean Time)
  • name is OS or non-Red third-party API-related

Macros (Red/System)

Apply the same naming conventions for picking up Red/System macros names. Macros generally use uppercase for names, as a way to visually distinguish them easily from the rest of the code (unless the intention is explicitely to make it look like regular code, like pseudo-custom datatype definitions). When multiple words are used, they are separated by an underscore _ character to increase even more the difference with regular code.

(TBD: extract all single-word names used in the Red codebase as examples)

Function definitions

The general rule is to keep the spec block on a single line. The body block can be on the same line or over several lines. In case of Red/System, as the spec blocks tend to be longer, most functions spec blocks are wrapped over several lines, so, for sake of visual consistency, often even small spec block are wrapped.

Correct

do-nothing: func [][]
increment: func [n [integer!]][n + 1]

increment: func [n [integer!]][
	n + 1
]

increment: func [
	n [integer!]
][
	n + 1
]

Incorrect

do-nothing: func [
][
]

do-nothing: func [

][

]

increment: func [
	n [integer!]
][n + 1]

When the spec block is too long, it should be wrapped over several lines. When wrapping the spec block, each type definition must be on the same line as its argument. The optional attributes block should be on its own line. Each refinement starts on a new line. If followed by a single argument, the argument can be on the same line or a new line with an indentation (just be consistent with other refinements in the same spec block). For /local refinement, if the local words are not followed by type annotation, they can be put on the same line.

When wrapping the spec block over several lines, it is recommended to align the datatype definitions for consecutive arguments, on the same column for easier reading. Such alignment is preferably done using tabs (if you strictly follow these coding style rules) or else, using spaces.

Correct

make-world: func [
	earth	[word!]
	wind	[bitset!]
	fire	[binary!]
	water	[string!]
	/with
		thunder [url!]
	/only
	/into
		space [block! none!]
	/local
		plants animals men women computers robots
][
	...
]

Incorrect

make-world: func [
	[throw] earth [word!]		;-- attributes block not on its own line
	wind	[bitset!]
	fire [binary!]				;-- unaligned type definition
	water	[string!]
	/with
		thunder [url!]
	/only
	/into space [block! none!]	;-- inconsistent with /with formatting
	/local
		plants animals			;-- breaking line too early
		men women computers robots
][
	...
]

For docstrings, the main one (describing the function) should be on its own line if the spec block is wrapped. The argument and refinement docstrings should be on the same line as the item they are describing. Docstrings start with a capital letter and do not require a ending dot (it's added automatically when printed on screen by help function).

  • Doc strings SHOULD NOT be more than 70 chars
  • They SHOULD NOT contain line breaks
  • They SHOULD provide a brief description of the functions purpose or behavior
  • They SHOULD use terminology consistent with similar or related functions

Correct

increment: func ["Add 1 to the argument value" n][n + 1]

make-world: func [
	"Build a new World"
	earth	[word!]		"1st element"
	wind	[bitset!]	"2nd element"
	fire	[binary!]	"3rd element"
	water	[string!]
	/with 				"Additional element"
		thunder [url!]
	/only				"Not implemented yet"
	/into				"Provides a container"
		space [unset!]	"The container"
	/local
		plants animals men women computers robots
][
	...
]

Incorrect

make-world: func ["Build a new World"	;-- should be on a newline
	earth	[word!]		"1st element"
	wind	[bitset!]	  "2nd element"	;-- excessive indentation
	fire	[binary!]
	"3rd element"				;-- should be on same line as `fire`
	water	[string!]
	/with 				"Additional element"
		thunder [url!]
	/only "Not implemented yet"	;-- should be aligned with other docstrings
	/into
		"Provides a container"
		space [unset!]	"The container"
	/local
		plants animals men women computers robots
][
	...
]

Function calls

Arguments are following the function call on the same line. If the line becomes too long, arguments can be wrapped over several lines (one argument per line) with an indentation.

Correct

foo arg1 arg2 arg3 arg4 arg5

process-many
	argument1
	argument2
	argument3
	argument4
	argument5

Incorrect

foo arg1 arg2 arg3
	arg4 arg5

foo
	arg1 arg2 arg3
	arg4 arg5

process-many
	argument1
		argument2
			argument3
			argument4
				argument5

For long expressions with many nested parts, spotting the bounds of each expression can be sometimes difficult. Using parenthesis for grouping a nested call with its arguments is acceptable (but not mandatory).

head insert (copy/part [1 2 3 4] 2) (length? mold (2 + index? find "Hello" #"o"))

head insert 
	copy/part [1 2 3 4] 2
	length? mold (2 + index? find "Hello" #"o")

Comments

In Red codebase:

  • comments are written using the ;-- prefix (stronger visual clue)
  • single-line comments start at column 57 (works best on average, else column 53)
  • multi-line comments are done using several single-line prefixes rather than comment {...} constructions.

The general rule is to put comments on the same line as the beginning of the corresponding code instead of on a new line in order to save significant vertical space. Though, if the comment is used for separating chunks of code, then putting it on a new line is fine.

String syntax

Use "" for single-line strings. The {} form is reserved for multi-line strings. Respecting this rule ensures:

  • a more consistent source representation before and after LOADing code
  • better convey of meaning

One exception to the rule is when a single-line string includes the " character itself. In this case, it is preferred to use the {} form rather than escaping the quote ^" as it is more readable.

New line usage

TBD

Transition to 64-bit

Workload

Light * Heavy ** Very heavy ***

Phase 1: cross-compiling 64-bit apps from 32-bit platform (2019)

macOS is the first platform that is moving out of 32-bit support, so we can start by implementing support for that platform only. We can cross-compile from a 32-bit platform using the existing Rebol-based 32-bit toolchain to macOS 64-bit (with some little limitations, like lack of literal 64-bit values support).

  1. Implement Mach-o files 64-bit support. *
  2. Implement x64 target backend for R/S. **
  3. Make all the R/S unit tests pass in 64-bit, by cross-compiling (mostly debugging work). **
  4. Modify the Red runtime library in R/S to support 64-bit pointers (decide on cell! size). ***
  5. Modify the View backend for macOS to use the 64-bit OS API. **

The results are:

  • A 64-bit Red console with GUI support, allowing to run most of existing Red scripts unchanged.
  • Ability to cross-compile to 64-bit any Red and R/S user app.

The second platform to support once all that is done is Linux, which can then be trivially achieved using cross-compilation and one extra development step:

  1. Implement ELF64 support. *

Last would be Windows 64-bit support:

  1. Implement PE64 support. *
  2. Modify the View backend for Windows to use the 64-bit OS API. **

At this point, we can compile and run any Red script with GUI support on all big 3 platforms in 64-bit, but using cross-compilation from a 32-bit platform for binaries generation.

Note: Red/Pro could be used as replacement for steps 1) and 2), but Red/Pro development would take significantly more time to achieve. Moreover, it would provide only a commercial 64-bit toolchain, leaving the "community" version to 32-bit only. This probably would not be a satisfying solution for all the macOS and Linux users.

Phase 2: moving the 32-bit toolchain to 64-bit (2020)

The current toolchain runs on Rebol2 32-bit, so it needs to be replaced for 64-bit support. The different options are:

a) Keep the codebase and use R2 64-bit, if proven stable enough. The issue then is that only Linux platform is supported and no possibility to use encapping to generate standalone toolchain binaries. So this can only be a temporary solution, and need a replacement for 1.0. *

b) Use R3 64-bit. It's alpha quality and unmaintained by its author. It would require changing the toolchain codebase significantly to adjust for different semantics and syntax in R3. Some of the core R/S compiler constructs would need to be replaced (e.g.: function names decorators paths, used as words). It took about one month in 2015 (as a feasibility test) to make the Red toolchain run on R3 for simple scripts compilation, so I estimate such effort to be bigger now. **

c) Port the codebase to Red, trying to minimize the changes. It would be a much less effort than for porting to R3, given closer semantics of Red to R2. It would also have the advantage of self-hosting Red earlier than planned, ensuring that R2 is out the 1.0 release. Though, the toolchain would keep its monolitic, undocumented and throw-away code nature. **

d) Refactor entirely the toolchain to adopt the staged architecture of Red 2.0. Basically, rewritting the ~25k LOC of the toolchain codebase, but without implementing any of the Red 2.0 features (like JIT-compilation and optimization stages). In addition to self-hosting, it would allow to document properly the whole toolchain code, adopt a contribution-friendly modular architecture and clean-up the toolchain code (after 8 years of existence, that would be a sane thing to do). This would take more work than option 3), though, that work could be more easily split among the core team members, and probably result in higher implication of the core and extended team (and removing me as a single-point of failure for the project). ***

Note: once that phase done, the ARM64 support can be added to the "community" Red codebase, by adding an ARM64 target implementation. Though, even before that, Red/Pro should provide ARM64 support out of the box for paying users.

`to` vs `make`

There is a reason we have to and make rather than a single action that serves both purposes. The rules for conversion and the rules for construction can differ. That is not an inconsistency, it is a purposeful design choice, separating rules into these two categories in order to offer more features while keeping the meaning clear to users.

We don't have any principle of least surprise to abide by, as Red lives in its own abstraction layer. There isn't any mainstream language which comes close to it. What matters is internal consistency, not consistency with languages living at the opposite end of the abstraction scale (e.g. C).

Logic and integers

In Red, all values are true except false and none. Thus:

>> to logic! 1
== true
>> to logic! 0
== true

is the expected result. Consequently, true (and by extension false), cannot be mapped back to an integer value, so conversion from logic! to integer! is an error. For the sake of boolean logic support, we also provide a mechanism to construct logic values from 1 and 0, using make. That is make logic! <integer> is allowed. For consistency and to reduce confusion, make integer! <logic> could throw an error too. Though that would remove a feature and introduce an arbitrary incompatibility with Rebol3.

Current behaviors:

>> make integer! false
== 0
>> make integer! true
== 1
>> to integer! false
*** Script Error: cannot MAKE/TO integer! from: false
*** Where: to
*** Stack:  
>> to integer! true
*** Script Error: cannot MAKE/TO integer! from: true
*** Where: to
*** Stack:  
>> 
>> make logic! 0
== false
>> make logic! 1
== true
>> to logic! 0
== true
>> to logic! 1
== true

In a more concise way:

 MAKE	    TO
------	  --------
T -> 1	  T -> n/a
F -> 0	  F -> n/a

 MAKE	    TO
------	  ------
1 -> T	  1 -> T
0 -> F	  0 -> T

So we can say that make relyies on boolean logic rules for constructing those values, while to relies on Red semantics for its behavior.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.