Skip to content

Instantly share code, notes, and snippets.

@ZoomTen
Last active September 11, 2021 14:05
Show Gist options
  • Save ZoomTen/3935b50915bec9e0d2d5c6bb3067631a to your computer and use it in GitHub Desktop.
Save ZoomTen/3935b50915bec9e0d2d5c6bb3067631a to your computer and use it in GitHub Desktop.
ASMDOC (lol)

ASMDOC

(a.k.a Hell on Earth, I'm Sure)

Essentially, this is just a Javadoc-esque thing loosely applied to assembly language (primarily geared towards RGBDS ASM)

The opening and closing tags are just a line containing only ;;--, everything inside the ASMDOC must begin wih ;; .

Directly below the ASMDOC would be a label, an equate (that is, constant), a SECTION directive, or nothing at all.

Here's an example of a label ASMDOC:

;;--
;; Fill memory in HL with D for BC bytes
;;
;; @param D    value to fill in
;; @param HL   beginning of memory address
;; @param BC   how many bytes to fill
;;
;; @return HL  HL + BC + 1
;; @return BC  0
;;--
FillMem2::
    ld a, d
    ld [hli], a
    dec bc
    ld a, c
    or b
    jr nz, FillMem2
    ret

An equate ASMDOC:

;;--
;; Work memory starts here
;;--
wRAMStart EQU $C000

A section ASMDOC:

;;--
;; Fill and copy functions
;;--
SECTION "Functions", ROM0

This will show up as an "annotation":

;;--
;; Random comment passing through
;;--

And this will show up as the project info (there must be only one):

;;--
;; @project Test Game
;; @version 2021.09
;; @author  Zumi
;;
;; This is a game made as a coding test.
;;--

As with Javadoc, paragraph breaks are marked with <p>:

;;--
;; Poll joypad input.
;;
;; <p> Unlike the hardware register, button
;; presses are indicated by a set bit.
;;
;; @param  rJOYP     raw Game Boy input
;; @return hJoyInput filtered input, safe for use
;;--
ReadJoypad::

The following fields are parsed internally in mkdoc.py:

 @project [project name]
 @author  [function or project author]
 @version [project version]
 @date    [addition date]
 @url     [project URL]
 @return  [register or wram value] [describe the return value here]
 @returns [describe the return value here]
 @param [register or wram value] [describe the parameter here]

The following fields will show up in the HTML:

 @project [project name]
 @author  [function or project author]
 @url     [project URL] (only with a project field)
 @return  [register or wram value] [describe the return value here]
 @returns [describe the return value here]
 @param [register or wram value] [describe the parameter here]
#!/usr/bin/env python3
import os
import sys
import re
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from xml.dom import minidom
import pprint
from datetime import datetime
# usage directions
if len(sys.argv) < 2:
print('usage: python3 mkdoc.py [directory to scan for asm files] > index.html')
exit(0)
# scan files
files = []
for walk in os.walk(sys.argv[1]):
for file in walk[2]:
if file[-4:].lower() == '.asm':
files.append('%s/%s' % (walk[0], file))
re_nl = re.compile(r'\n')
# define documented sections
documents = {
'sections': {},
'constants': {},
'labels': {},
'paragraphs': [],
'project': {}
}
# over all files
for file in files:
# one file
with open(file, 'r') as source:
# read docs and the thing below it
source_txt = source.read()
for doc in re.finditer(r";;--\n(.*?);;--\n(.+?)??$", source_txt, re.MULTILINE | re.DOTALL):
# separate what is documented with the docs themselves
bottom_line = doc.group(2) or ''
doc_content = re.sub(r';;\s+', '', doc.group(1))
# initialize keys
keys = {'desc':'', 'file': file}
# figure out which line number are we looking at
# https://stackoverflow.com/a/44478696
# if there is anything defined here
if bottom_line != '':
keys['line'] = len(re_nl.findall(source_txt, 0, doc.start(2)))+1
# start of doc itself
keys['doc_line'] = len(re_nl.findall(source_txt, 0, doc.start(1)))+1
# should this key be placed in the primary project key?
as_project = False
# process documentation and tags used
# valid tags at the moment (square brackets denote arguments):
# @project [project name]
# @author [function or project author]
# @version [project version] XXX
# @date [addition date] XXX
# @url [project URL] XXX
# @return [register or wram value] [describe the return value here]
# @returns [describe the return value here]
# @param [register or wram value] [describe the parameter here]
for line in doc_content.split('\n'):
has_tag = re.match(r'@(project|author|version|date|url|return\s+\w+|returns|param\s+\w+)\s+(.+)', line)
if has_tag:
if has_tag.group(1) == 'project':
as_project = True
keys[has_tag.group(1)] = has_tag.group(2)
else:
keys['desc'] += ' ' + line
# insert it into the documented code dict depending
# on what is below the doc
is_label = re.match('^(\w+):', bottom_line)
is_equ = re.match('^(\w+)\s+[Ee][Qq][Uu]\s+(.+)', bottom_line)
is_section = re.match('^[Ss][Ee][Cc][Tt][Ii][Oo][Nn]\s+["\'](.+)["\']', bottom_line)
if as_project:
documents['project'] = keys
else:
if is_label:
documents['labels'][is_label.group(1)] = keys
elif is_equ:
keys['value'] = is_equ.group(2)
documents['constants'][is_equ.group(1)] = keys
elif is_section:
documents['sections'][is_section.group(1)] = keys
else:
documents['paragraphs'].append(keys)
###################################################################
# try to make a web page
###################################################################
def make_safe_item_name(string):
return string.replace(' ','-')
def make_paragraphs(parent, string):
for paragraph in string.split('<p>'):
if paragraph.strip() != '':
desc = SubElement(parent, 'p')
desc.text = paragraph.strip()
html = Element('html')
head = SubElement(html, 'head')
body = SubElement(html, 'body')
sc_left = SubElement(html, 'div', {'class': 'navigation-container'})
header = SubElement(sc_left, 'header')
sc_right = SubElement(html, 'div', {'class': 'content-container'})
main = SubElement(sc_right, 'main')
# add meta tag
SubElement(head, 'meta', {'charset': 'utf-8'})
# add style tag
SubElement(head, 'link', {'rel': 'stylesheet', 'href':'style.css'})
# parse info variables
project_title = documents['project'].get('project', '(no project name)')
project_desc = documents['project'].get('desc', None)
project_author = documents['project'].get('author', None)
project_url = documents['project'].get('url', None)
# render info to page
page_title = SubElement(head, 'title')
page_title.text = project_title
project_h1 = SubElement(header, 'h1', {'id':'project-info'})
project_h1.text = project_title
if project_author:
SubElement(project_h1, 'br')
author = SubElement(project_h1, 'small')
author.text = 'by ' + project_author
info_header = SubElement(main, 'h2')
info_header.text = 'Project info'
if project_desc:
desc_container = SubElement(main, 'div', {'id':'project-description'})
make_paragraphs(desc_container, project_desc)
if project_url:
url_ = SubElement(desc_container, 'span')
url_.text = 'For more info, see: '
url = SubElement(url_, 'a', {'href': project_url})
url.text = project_url
main_menu = SubElement(header, 'ul', {'id':'nav-menu'})
for section in documents.keys():
# exclude these
if section == 'project':
break
if section == 'paragraphs':
break
menu_item = SubElement(main_menu, 'li')
menu_item_ = SubElement(menu_item, 'a', {'href': '#%s' % section})
menu_item_.text = section.capitalize()
if type(documents[section]) is dict:
if len(documents[section]) > 0:
item_entry_container = SubElement(menu_item, 'ul')
for item in documents[section].keys():
item_entry = SubElement(item_entry_container, 'li')
item_entry = SubElement(item_entry, 'a', {'href': '#%s-%s' % (section, make_safe_item_name(item))})
item_entry.text = item
# individual annotations
reorganized_annotations = {}
for annotation in documents['paragraphs']:
if annotation['file'] not in reorganized_annotations:
reorganized_annotations[annotation['file']] = []
new_key = {}
new_key['line'] = annotation['doc_line']
new_key['desc'] = annotation['desc']
reorganized_annotations[annotation['file']].append(new_key)
if len(reorganized_annotations) > 0:
menu_item = SubElement(main_menu, 'li')
menu_item_ = SubElement(menu_item, 'a', {'href': '#annotations'})
menu_item_.text = 'Annotations'
item_entry_container = SubElement(menu_item, 'ul')
counter = 0
for filename in reorganized_annotations.keys():
counter += 1
item_entry = SubElement(item_entry_container, 'li')
item_entry = SubElement(item_entry, 'a', {'href': '#%s-%d' % ('annotations', counter)})
item_entry.text = filename
# Sections
for section_name, content in documents.items():
# exclude these
if section_name == 'project':
break
if section_name == 'paragraphs':
section = SubElement(main, 'section', {'id': 'annotations'})
section_title = SubElement(section, 'h2')
section_title.text = 'Annotations'
counter = 0
for filename, annotations in reorganized_annotations.items():
counter += 1
entry_title = SubElement(section, 'h3', {'id': '%s-%d' % ('annotations', counter)})
entry_title.text = filename
dl = SubElement(section, 'dl')
for i in annotations:
a = SubElement(dl, 'dt')
a.text = 'Line %d' % i['line']
a = SubElement(dl, 'dd')
make_paragraphs(a, i['desc'])
break
section = SubElement(main, 'section', {'id': section_name})
section_title = SubElement(section, 'h2')
section_title.text = section_name.capitalize()
if type(content) is dict:
for name, params in content.items():
entry_title = SubElement(section, 'h3', {'id': '%s-%s' % (section_name, make_safe_item_name(name))})
entry_title.text = name
deflist = SubElement(section, 'dl')
# description item
defitem = SubElement(deflist, 'dt')
defitem.text = 'Description'
defitem = SubElement(deflist, 'dd')
desctext = params.get('desc', 'No description defined.')
make_paragraphs(defitem, desctext)
# params
func_params = {}
for key, key_contents in params.items():
if key.startswith('param'):
func_params[key.replace('param', '').strip()] = key_contents
if len(func_params) > 0:
# parameters item
defitem = SubElement(deflist, 'dt')
defitem.text = 'Parameters'
defitem = SubElement(deflist, 'dd')
itemlist = SubElement(defitem, 'ul')
for param_name, param_desc in func_params.items():
item_ = SubElement(itemlist, 'li')
item_var = SubElement(item_, 'var', {'style': 'param-name'})
item_var.text = param_name
item_var.tail = ' - '
item_var = SubElement(item_, 'span', {'style': 'param-desc'})
item_var.text = param_desc.strip()
# returns
return_params = {}
for key, key_contents in params.items():
if key.startswith('return'):
return_params[key.replace('return', '').strip()] = key_contents
if len(return_params) > 0:
# parameters item
defitem = SubElement(deflist, 'dt')
defitem.text = 'Returns'
defitem = SubElement(deflist, 'dd')
itemlist = SubElement(defitem, 'ul')
for param_name, param_desc in return_params.items():
item_ = SubElement(itemlist, 'li')
item_var = SubElement(item_, 'var', {'style': 'param-name'})
item_var.text = param_name
item_var.tail = ' - '
item_var = SubElement(item_, 'span', {'style': 'param-desc'})
item_var.text = param_desc.strip()
# output
returntext = params.get('returns', None)
if returntext:
defitem = SubElement(deflist, 'dt')
defitem.text = 'Output'
defitem = SubElement(deflist, 'dd')
make_paragraphs(defitem, returntext)
# in file
defitem = SubElement(deflist, 'dt')
defitem.text = 'Defined in'
defitem = SubElement(deflist, 'dd')
defitem.text = "%s line %d" % (
params.get('file', '???'),
params.get('line', 0)
)
SubElement(sc_right, 'hr')
footer = SubElement(sc_right, 'footer')
footer.text = "ASMDOC generated by mkdoc.py on %s" % datetime.now().strftime("%Y-%m-%d %H:%M:%S")
###################################################################
# render
###################################################################
prettified = \
minidom.parseString(tostring(html, 'utf-8')) \
.toprettyxml(indent=" ")
prettified = "<!DOCTYPE html>\n" + "\n".join(prettified.split("\n")[1:])
print(prettified)
#pp = pprint.PrettyPrinter(indent=4)
#pp.pprint(documents)
/* null margins and padding to give good cross-browser baseline */
html,body,address,blockquote,div,
form,fieldset,caption,
h1,h2,h3,h4,h5,h6,
hr,ul,li,ol,ul,
table,tr,td,th,p,img {
margin:0;
padding:0;
}
img, fieldset {
border:none;
}
table {
font-size:100%;
line-height:150%;
}
hr {
display:none;
}
/* my style */
html {
font-family: "Segoe UI", "Arial", sans-serif;
box-sizing: border-box;
}
*, *:before, *:after{
box-sizing: inherit;
}
body {
position: absolute;
width: 100%;
height: 100%;
overflow-y: hidden;
overflow-x: hidden;
line-height: 2.5em;
color: #111;
}
ul {
line-height: 1.5em;
}
.navigation-container {
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 16em;
overflow-y: auto;
background-color: #2980b9;
color: #fff;
background-image: linear-gradient(180deg, rgba(0,0,0,.3) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.3) 100%);
}
h1 {
padding: .5em;
}
#nav-menu * {
list-style-type: none;
}
#nav-menu {
font-weight: bold;
line-height: 2.25em;
}
#nav-menu > li {
background-color: #000;
}
#nav-menu > li a{
padding: 2em 1em;
}
#nav-menu > li ul{
padding: 0 1em;
/* max-height: 0;
transition: 3s max-height; */
overflow-y: hidden;
line-height: 2.25em;
}
#nav-menu > li:hover ul{
/* max-height: 100em; */
}
#nav-menu ul {
font-weight: normal;
text-decoration: underline;
background-color: #222;
}
.navigation-container a {
color: #fff;
text-decoration: none;
}
.content-container {
position: absolute;
left: 16em;
overflow-y: scroll;
height: 100%;
width: calc(100% - 16em);
}
.content-container main {
padding: 1.618em 3.236em 0 3.236em;
}
h1 {
font-size: 2em;
}
h1 small {
font-size: .65em;
font-weight: normal;
}
dl {
border: 1px solid #ccc;
margin: 0;
border-radius: 5px;
}
dt {
text-transform: uppercase;
background: #ccc;
padding: 0 1em;
font-weight: bold;
line-height: 2em;
font-size: .8em;
font-style: oblique;
}
dd {
margin: .5em 1em;
}
dd ul{
list-style-type: none;
}
var {
font-weight: bold;
font-style: normal;
font-family: "Courier New", monospace;
background: #dde;
padding: .5em;
}
footer {
background: #222;
color: white;
padding: 0 1em;
margin-top: 1em;
}
h2, h3 {
margin-top: 1em;
}
h3 {
color: #00a,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment