Jallib Style Guide (JSG) defines the standards used to write jalv2 code
There're 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 howto write code, this guide is here to help you not forget things like author(s), licence, and remember you some basic principles.
Headers in library
Every jal files published on jallib repository1 must have the following headers (comments), as the very beginning of the file:
-- Title: [title, very small description if needed] -- Author: [author's name], Copyright (c) YEAR..YEAR, all rights reserved. -- Adapted-by: [adapters' name, comma seperated] -- Compiler: [specify which version of compiler is needed. Ex: >=2.4g, =2.3, ???] -- -- This file is part of jallib (http://jallib.googlecode.com) -- Released under the BSD license (http://www.opensource.org/licenses/bsd-license.php) -- -- Sources: [ if relevant, specify what sources of informations you use: website, article, specifications, appnotes, etc... ] -- -- Description: [describe what is the functional purpose of this lib] -- -- Notes: [put here information not related to functional description] -- [code start 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 has been used to test this file (no space between operator and version: >=2.4g). sources, description 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.
-- Title: USART hardware control -- Author: Stef Mientki, Copyright (c) 2002..2008, all rights reserved. -- Adapted-by: Sebastien Lelong, Someone Else -- Compiler: >=2.4g -- -- This file is part of jallib (http://jallib.googlecode.com) -- Released under the BSD license (http://www.opensource.org/licenses/bsd-license.php) -- -- Description: Routines for sending and receiving through the PIC-usart, -- both RS232 and SPI are supported (for SPI see spi_hardware.jal) -- Baudrate can simply be set through a human constant, -- because the baudrate depending registers are calculated by this unit. -- Baudrate is calculated, starting at the high baudrate flag, -- which will ensure the highest possible accuracy. -- -- -- SPI routines are setup for more SPI-devices, -- in which case CS should be controled outside this unit. -- -- Sources: excellent article on USART: http://www.google.fr/search?q=usart -- -- Notes: an incredible note --
In the /tools directory jallib SVN repository, you'll find jallib.py. Amongst many things, you can run "validate" action, and check lots of JSG requirements. You can (must) use it to make sure your jal files are JSG compliant. This script will help you to identify problems:
bash$ python jallib.py validate my_file.jal File: my_file.jal 6 errors found ERROR: Cannot find references to jallib (should have: '^-- This file is part of jallib\\s+\\(http://jallib.googlecode.com\\)') ERROR: Cannot find references to license (should have: '^-- Released under the BSD license\\s+\\(http://www.opensource.org/licenses/bsd-license.php\\)') 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 found
Rules Of Thumb
Filenames, variables, procedures naming convention
- <function>_<implementation|other>.jal for PIC-specific libraries (peripherals). function gives clues about what the library is about. Then implementation or other is 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, ...).
Ex: serial_hardware.jal, serial_software.jal, pwm_ccp1.jal.
- <device-family>_<device>.jal for external libraries. device-family describes the device family (...), and is often the directory name where the lib is. device
Ex: lcd_hd44780_4.jal, rtc_ds1302.jal, co2_t6603.jalprecisely sets the part.
Constants, variables, procedures, functions
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.
- <device>_<whatever> if you want to avoid namespace 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 namespace 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, we'll discuss...
Ex: 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 adressable. 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.
- <device>_<external_pin_name> if you want to avoid namespace 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...
Samples and tests
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 (eg. 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, ie. 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.
Ex: how to use your library (doing amazing things with the GP2D02 IR ranger)
-- declare in/out pins for the ranger alias ranger_pin_in is pin_A0 alias ranger_pin_out is pin_A1
and 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 = output
and now include the library:
-- now include the library which uses ranger_pin_in and ranger_pin_out include gp2d02 -- ranger library
Exception: 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)no
Ex: a 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 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 namespace 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...
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 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.
Avoid the use of inline Assembler. If you cannot do without it use standard asm opcodes and avoid nasty Assembler statements. So:
Warnings 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.
Indent your code
It helps following the code structure (flows). Code must be indented using 3 spaces (no tab). You can use python jallib.py reindent <file.jal> for this.
var byte char forever loop if serial_hw_read(char) then echo(char) end if end loop
var byte char forever loop if serial_hw_read(char) then echo(char) end if end loop
Use lower_case_with_underscores ...
var byte this_is_a_variable var byte another_one
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.
const RESET_CHAR = "*" SSPCON1_CKP = 1
const reset_CHAR = "*" sspCON1_Ckp = 1
Be 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 !).
-- 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_var
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 procedure
Filenames are lowercased, includes statements too
All jal files must be lowercased. So:
$ ls 16f88.jal
$ ls 16F88.jal
Being consistent, include statements are lowercased, too:
Inform readers what should be considered private
Functions, procedures, variables, etc... starting with an underscore is 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 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.
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, amongst the many scripts used to deal jal code base.