Jallib Style Guide
Why a style guide?
The Jallib Style Guide (JSG) defines the standards used to write Jalv2 code. It is recipe to write a standard jalv2 library.
There are many ways to write code, whatever the programming language is. Each language has its preferences. For instance, java prefers CamelCase whereas python prefers underscore_lowercase.
While this seems a real constraint, not necessarily needed, it actually helps a lot while sharing code with everyone: it improves readability, and readability is important because code is read much more often than it is written.
Finally, more than a how to write code, this guide is here to help you not forget things like author(s), licence, and remind you of some basic principles.
Headers in a library
Every jal file published on this repository must have the following headers (comments), as the very beginning of the file:
-- Title: [very small description if needed]
-- Author: [author's name], Copyright (c) YEAR..YEAR, all rights reserved.
-- Adapted-by: [adapter author's name, comma separated]
-- Compiler: [which version of compiler is needed. Ex: >=2.4g, 2.5]
--
-- This file is part of jallib (https://github.com/jallib/jallib)
-- Released under the ZLIB license (http://www.opensource.org/licenses/zlib-license.html)
--
-- Description: [describe what is the functional purpose of this lib]
--
-- Sources: [if relevant, specify which sources you used: website, specifications, etc...]
--
-- Notes: [put here information not related to functional description]
--
[code starts here...]The author is the original author's name. The library may have been modified and adapted by adapters. The compiler helps readers to know which compiler version is needed to use this file (no space between operator and version: >=2.5r6). Description, sources and notes fields must be followed by an empty line (just `--`) to declare the end of the field content. As a consequence, those fields cannot have empty lines within them.
JSG Header example:
-- -----------------------------------------------------------------------------
-- Title: Library for the DS3231 Real Time Clock IC.
-- Author: Rob Jansen, Copyright (c) 2021..2022, all rights reserved.
-- Adapted-by:
-- Compiler: 2.5r6
--
-- This file is part of jallib (https://github.com/jallib/jallib)
-- Released under the ZLIB license (http://www.opensource.org/licenses/zlib-license.html)
--
-- Description: Library for controlling the SD3231 Real Time Clock IC.
-- The chip uses an IIC interface. The library provides all
-- functions and procedures to support the rtc_common.jal library and
-- includes extra functions and procedures specific for the DS3231.
-- For all common rtc procedures and functions see rtc_common.jal.
--
-- Sources: Maxim datasheet rtc. 19-5170; Rev 10; 3/15
--
-- Notes: This library supports the control of the DS3231 via IIC.
-- The default is hardware IIC control but this can be overruled using
-- software IIC control by defining the following constant:
-- -) const RTC_SOFTWARE_IIC = TRUE
--
-- The following pins must be defined by the main program before
-- including this library. Common pins for using IIC:
-- -) alias rtc_sck -- IIC to sck of rtc
-- -) alias rtc_sck_direction
-- -) alias rtc_sdo -- IIC to sda of rtc
-- -) alias rtc_sdo_direction
-- Note: if you need to create a new paragraph within a multiline field, use the "--" special chars. See example in Notes field: "The following pins ..." is part of the Notes field, but visually separated from the beginning of the field content.
In the
/tools directory on
GitHub you'll find jallib.py
and jallib3.py for Python version 3 and higher. Among
many things, you can run the "validate" action, and check lots of JSG
requirements. You can (must) use it to make sure that all your jal files
are JSG compliant. This script will help you to identify
problems:
Example:
bash$ python jallib.py validate my_file.jal
File: my_file.jal
4 errors found
ERROR: Cannot find field Title (searched for '^-- Title:\s**(.**)')
ERROR: Cannot find field Author (searched for '^-- Author:\s**(.**)')
ERROR: Cannot find field Compiler (searched for '^-- Compiler:\s**(.**)')
ERROR: Cannot find field Description (searched for '^-- Description:\s**(.**)')
0 warnings foundFilenames naming convention
A library must be named as:
<function>_<implementation|other>.jalfor PIC-specific libraries (peripherals).functiongives clues about what the library is about. Thenimplementationorotheris here to differentiate libraries, and is more about implementations (serial_hardware.jal,serial_software.jal), things specific to the function (pwm_ccp1.jal,pwm_ccp2.jal, ...).<device-family>_<device>.jalfor external libraries.device-familydescribes the device family (...), and is often the directory name where the lib is.deviceprecisely sets the part (lcd_hd44780_4.jal,rtc_ds1302.jal,co2_t6603.jal.).
Variables, constants and procedures naming convention
All external names (of global variables, constants, procedures and functions available to application programs) must start with a prefix unique to the library. Names of other global entities (not supposed being used by application programs) should use this prefix and use an additional underscore at the beginning.
Variables, constants, procedures and functions must be named as:
<device>*<whatever>if you want to avoid name space collision<device-family>*<whatever>if you want to have a common API
For example, co2_t6603.jal library have all
its procedures starting with t6603_ (and
*t6603* for internal names). This makes all these
procedures very specific to this library. If you have another CO2 sensor,
you'll be able to use both at the same time, because they'll be no name
space collision. This is the purpose of the
<device>*<whatever> naming
convention.
Another example: the names of the procedures in the LCD
libaries start with `lcd*` (and `*lcd*` for internal names). There are
many different LCD types, but all implements the same API, because
procedures, variables, etc... are named according to the device-family,
not the device itself. This is the purpose of the
<device-family>_<whatever> naming
convention.
Now, how do you know which to follow? Ask and we will discuss.
Example: There are two implementations of i2c and serial:
hardware and software. Having both i2c implementation within a same PIC is
not useful, since i2c is addressable. Thus, all const/var/... are prefixed
by i2c_<whatever>.jal. On the contrary, it can be
useful to have two serial implementation within a same PIC (eg. one
talking a PIC, another talking to a external device). Thus, serial libs'
const/var/... are prefixed by
serial_hw_<whatever>.jal or
serial_sw_<whatever>.jal.
Headers of procedures and functions
It is recommended to use the following header layout for procedures and functions. Return values are of course only applicable for functions. Notes are optional. When no parameters are passed, mention 'None'.
--------------------------------------------------------------------------------
-- Description: <purpose of this procedure or function>
-- - <this may be a long description, continue on this line>
-- Parameters:
-- - <parameter> - <description>
-- - <parameter> - <description>
-- Returns:
-- - <returnvalue> - <description>
-- Notes: <some optional notes can go here for this procedure>
-- - <this may be a long description, continue on this line>
-------------------------------------------------------------------So for example:
--------------------------------------------------------------------------------
-- Description: Calculate CRC7 for byte array (one-shot calculation)
-- Parameters:
-- - data - array of bytes to process
-- - length - number of bytes
-- Returns:
-- - calculated CRC7 value
-- Notes:
--------------------------------------------------------------------------------Pin names naming convention
The pins are named as:
<device>_<external_pin_name>if you want to avoid names pace collision<device-family>_<external_pin_name>if you want to have a common API
This is almost the same as for variables, contants, ...
except the <whatever> part now corresponds the pin
name of the external device (usually found in datasheets). Using the
<device-family>_<external_pin_name>
convention to build a common API may cause problems, if pin names aren't
named the same in all supported devices. In that case, the pin name should
be as explicit as possible.
Important: See also the very important rules about pin names within a library: "Don't use port and pin names".
Samples and test naming convention
Tests are
named as test_<whatever>.jal. That is, they should
starts with the prefix test_. That is, samples must
not start with test_.
Board files are named
as board_<pic>*<whatever>.jal.
Samples
are named as <pic>*<whatever>.jal.
<whatever> can be whatever, but should give users
hints about what the sample is (e.g.
16f88_serial_hardware.jal).
Why such a pain?
The main purpose of this is to control the naming conflicts between libraries and application code. Bear in mind that this is about source-level libraries which are combined by the compiler to form a single application program.
Having naming convention is also a great optimize process, saving time, by scripting and generating code. This is good.
Don't use port and pin names
Don't use port
and pin names like portA or pin_a5
in your great library, because someone may (will) want to use your library
on another port or pin. It also helps to make your great library PIC
independent.
Name your pins according to the context, to what your library is doing. Client code, i.e. users, will have to define those variables before actually include your great library.
Let the user set the pin directions, except if the library is supposed to modify direction during execution.
-- declare in/out pins for the ranger
alias ranger_pin_in is pin_A0
alias ranger_pin_out is pin_A1and make sure the pins work as required:
-- specify the direction of the pins
-- Since directions won't change during execution, this is
-- done here, during the setup, before including the library
pin_A0_direction = input
pin_A1_direction = outputand now include the library:
-- now include the library which uses ranger_pin_in and ranger_pin_out
include gp2d02 -- ranger libraryException: If your library uses a special PIC feature, it may use the name defined in the device files / datasheet. Not so much an exception, as you'll use the pin name given the context (feature, peripheral).
"var ... is ..." is now deprecated in favor of
"alias ... is ..." and must not be used anymore. The
"alias" keyword is more powerful as it allows to create
synonyms for any type of names (variables, constants, procedures,
functions, pseudo-variables).Example: An i2c hardware library (using built-in PIC i2c) may refer to `SCK` and `SDA`. Those pin names are set into the device include file (prefixed with the portname!).
Let the user initialize the library
Most of the
time, a library needs to be configured (you define variables/constants
before including the file), then initialized (you call
<libname>_init()). While having the init step
automatically called when the library is called can be convenient, this
results in a lack of flexibility. Indeed, you may want to initialize one
library or the other, or initialization step can take quite a long time,
so you want to have control about when you can "waste" such
time.
So, a library must never call its own init procedures, the
user will. And the init procedure must be named either as
<device>_init or
<function>_init, whether you want to avoid names
pace collision, or on the contrary, if you want to have different
implementation for the same API (see rules about naming convention
above).
Avoid weird default values in library
Don't put default values in your library, someone may (will) have a different opinion about what's a default value . Even if it's tempting because it can save time writing the same value again and again. Remember, your library is to be shared, nasty default value can be a real obstacle using it. If for some reason default values have to be used, make sure that you provide a means for the user to overrule oror change them.
Write examples
Write examples to show the world how to use your great library. Without it, people may (will) not use your library, because it's too complicated and too time-consuming reading code to actually discover what it does. Also remember writing examples can help you to design a usable, simple and clear API.
Assembler
Avoid the use of inline Assembler. If you cannot do without it use standard asm opcodes and avoid nasty Assembler statements. So:
Good
btfsc STATUS_ZBad
skpnzWarnings are errors
Don't be tempted to ignore warnings. Consider warnings as errors, until you've completely understand why there should be a warning (or not). Warnings can mask more relevant warnings and errors, so track them and try to avoid them. A library should compile without any warnings... if possible but it must compile without any errors.
Code layout
Indent your code. It helps following
the code structure (flows). Code must be indented using 3 spaces
(no tab). You can use python jallib3.py reindent
<file.jal> for this.
Good
var byte char
forever loop
if serial_hw_read(char) then
echo(char)
end if
end loopBad
var byte char
forever loop
if serial_hw_read(char) then
echo(char)
end if
end loopUse lower_case_with_underscores ...
Good
var byte this_is_a_variable
var byte another_oneBad
var byte ThisIsAVariable
var byte Another_One... except for constants
Uppercase variables should be used for constants, internal PIC function registers or for external PIN names, if they are uppercase in the datasheet as well.
Good
const RESET_CHAR = "*"
const SSPCON1_CKP = 1Bad
const reset_CHAR = "*"
const sspCON1_Ckp = 1Be explicit when calling procedures and functions
When a procedure (or a function) does not take any parameters, be explicit and help your readers: put parenthesis so everyone knows it's a call. Same when defining the function/procedures. Also note no space is allowed between the procedure/function name and the opening parenthesis. Finally, pseudo-variable must be defined with parenthesis, but not when used (heh, these are functions/procedures behaving like variables!).
Good-- Defining
procedure do_it_please() is
-- I will do it
end procedure
-- Calling
do_it_please()
-- pseudo-var
function my_pseudo_var'get() return byte is
-- I promise I'll do it
end function
var byte what = my_pseudo_varBad
procedure do_it_again is
-- this is bad
end procedure
do_it_again
function my_pseudo_var'get () return byte is
-- this is bad, too because there's a space !
end procedureFilenames are lower cased, include statements too
All jal files must be lowercased. So:
Good$ ls 16f88.jalBad
$ ls 16F88.jalBeing consistent, include statements are lower cased, too:
Goodinclude 16f88Bad
include 16F88Inform readers what should be considered private
Functions, procedures, variables, etc... starting with an underscore is a warning to users saying "you shouldn't use me, I'm for internal use only". Play carefully with this, remember users are quite curious and may want them anyway :).
Comment your code
It helps readers to understand what's going on. The comment should describe why your code does its thing, not what is does. That should be obvious from the code itself.
External data
When developing a library, you may need to collect and organize external / 3rd party data. For instance, the relation between a datasheet reference and the PICs described in this datasheet is what we call external data: it's not jal code, but often used to generate some, and always a source of information everyone can refer too.
External data must store in a structured format so everyone potentially is able to use it. Before we, developers, are also (kind of) humans, we want this format to be readable, and even writable, but also structured enough so a computer can also use and exploit it. That's why this format is JSON (and not XML), which is available in many languages. This is a way to share information, among the many scripts used to deal jal code base.