Skip to content

Instantly share code, notes, and snippets.

@lukehoban
Created July 4, 2025 16:32
Show Gist options
  • Save lukehoban/c6657e4e9a6fa8cacc8efa871a70b68e to your computer and use it in GitHub Desktop.
Save lukehoban/c6657e4e9a6fa8cacc8efa871a70b68e to your computer and use it in GitHub Desktop.
TODO web app in assembly
.data
http_200_html:
.ascii "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n"
http_200_html_len = . - http_200_html
http_200_json:
.ascii "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE\r\nAccess-Control-Allow-Headers: Content-Type\r\nConnection: close\r\n\r\n"
http_200_json_len = . - http_200_json
options_response:
.ascii "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
options_len = . - options_response
html_page:
.ascii "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Assembly TODO App</title><style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:700px;margin:0 auto;padding:20px;background:#f8f9fa;line-height:1.6}.container{background:white;padding:40px;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.1)}.header{text-align:center;margin-bottom:40px}.header h1{color:#2c3e50;margin:0;font-size:2.5em;font-weight:300}.header p{color:#7f8c8d;margin:10px 0 0 0;font-size:1.1em}.input-section{display:flex;gap:12px;margin-bottom:30px;padding:20px;background:#f8f9fa;border-radius:8px}#newTodo{flex:1;padding:14px;border:2px solid #e9ecef;border-radius:6px;font-size:16px;transition:border-color 0.3s}#newTodo:focus{outline:none;border-color:#007bff;box-shadow:0 0 0 3px rgba(0,123,255,0.1)}.add-btn{padding:14px 24px;background:linear-gradient(135deg,#28a745,#20c997);color:white;border:none;border-radius:6px;cursor:pointer;font-size:16px;font-weight:600;transition:transform 0.2s,box-shadow 0.2s}.add-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(40,167,69,0.3)}.todo{background:white;margin:12px 0;padding:20px;border-radius:8px;border:1px solid #e9ecef;display:flex;align-items:center;gap:15px;transition:all 0.3s;position:relative}.todo:hover{box-shadow:0 2px 8px rgba(0,0,0,0.1);transform:translateY(-1px)}.todo.completed{background:#f8fff9;border-color:#28a745;opacity:0.9}.todo.completed .todo-text{text-decoration:line-through;color:#6c757d}.todo-text{flex:1;font-size:16px;color:#2c3e50}.todo-id{background:#6c757d;color:white;padding:4px 8px;border-radius:12px;font-size:12px;font-weight:bold;min-width:30px;text-align:center}.toggle-btn{padding:10px 16px;background:#007bff;color:white;border:none;border-radius:5px;cursor:pointer;font-weight:500;transition:all 0.2s}.toggle-btn:hover{background:#0056b3}.completed .toggle-btn{background:#28a745}.completed .toggle-btn:hover{background:#1e7e34}.delete-btn{padding:10px 16px;background:#dc3545;color:white;border:none;border-radius:5px;cursor:pointer;font-weight:500;transition:all 0.2s}.delete-btn:hover{background:#c82333;transform:scale(1.05)}#status{margin-top:30px;padding:15px;background:linear-gradient(135deg,#e3f2fd,#f3e5f5);border-radius:8px;text-align:center;font-weight:500;color:#2c3e50}.empty-state{text-align:center;padding:60px 20px;color:#6c757d}.empty-state h3{margin:0 0 10px 0;font-size:1.5em;font-weight:300}.empty-state p{margin:0;font-size:1.1em}</style></head><body><div class='container'><div class='header'><h1>Assembly TODO App</h1><p>Built entirely in ARM64 Assembly Language</p></div><div class='input-section'><input id='newTodo' placeholder='Enter a new todo item...' onkeypress='if(event.key===\"Enter\")addTodo()'><button class='add-btn' onclick='addTodo()'>Add TODO</button></div><div id='todoList'></div><div id='status'>Loading...</div></div><script>let todos=[{id:0,text:'Learn Assembly Language',completed:false},{id:1,text:'Build HTTP Server in Assembly',completed:true},{id:2,text:'Create TODO API',completed:false},{id:3,text:'Add Frontend Interface',completed:true}];let nextId=4;function render(){const list=document.getElementById('todoList');if(todos.length===0){list.innerHTML='<div class=\"empty-state\"><h3>No todos yet!</h3><p>Add your first todo above to get started.</p></div>';updateStatus();return}list.innerHTML='';todos.forEach((todo,index)=>{const div=document.createElement('div');div.className='todo'+(todo.completed?' completed':'');div.innerHTML=`<span class='todo-id'>${todo.id}</span><span class='todo-text'>${escapeHtml(todo.text)}</span><button class='toggle-btn' onclick='toggleTodo(${index})'>${todo.completed?'Undo':'Done'}</button><button class='delete-btn' onclick='deleteTodo(${index})'>Delete</button>`;list.appendChild(div)});updateStatus()}function escapeHtml(text){const div=document.createElement('div');div.textContent=text;return div.innerHTML}function updateStatus(){const total=todos.length;const completed=todos.filter(t=>t.completed).length;const remaining=total-completed;document.getElementById('status').innerHTML=`<strong>${completed}/${total}</strong> todos completed ${remaining>0?`(${remaining} remaining)`:' - Great job!'}`}async function addTodo(){const input=document.getElementById('newTodo');const text=input.value.trim();if(!text)return;const newTodo={id:nextId++,text:text,completed:false};try{const response=await fetch('/api/todos',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(newTodo)});if(response.ok){todos.push(newTodo);input.value='';render();console.log('Added todo:',newTodo)}else{console.error('Failed to add todo, status:',response.status)}}catch(e){console.error('Failed to add todo:',e);todos.push(newTodo);input.value='';render()}}async function toggleTodo(index){if(index<0||index>=todos.length)return;const todo=todos[index];try{const response=await fetch(`/api/todos/${todo.id}`,{method:'PUT'});if(response.ok){todos[index].completed=!todos[index].completed;render();console.log('Toggled todo:',todo.id)}else{console.error('Failed to toggle todo, status:',response.status)}}catch(e){console.error('Failed to toggle todo:',e);todos[index].completed=!todos[index].completed;render()}}async function deleteTodo(index){if(index<0||index>=todos.length)return;const todo=todos[index];if(!confirm(`Delete \"${todo.text}\"?`))return;try{const response=await fetch(`/api/todos/${todo.id}`,{method:'DELETE'});if(response.ok){todos.splice(index,1);render();console.log('Deleted todo:',todo.id)}else{console.error('Failed to delete todo, status:',response.status,await response.text())}}catch(e){console.error('Failed to delete todo:',e);todos.splice(index,1);render()}}async function loadTodos(){try{const response=await fetch('/api/todos');if(response.ok){const data=await response.json();if(Array.isArray(data)&&data.length>0){todos=data;nextId=Math.max(...todos.map(t=>t.id||0))+1;console.log('Loaded todos from server:',todos)}}}catch(e){console.log('Using default todos, server not available:',e)}render()}loadTodos()</script></body></html>"
html_len = . - html_page
socket_addr:
.hword 2
.hword 0x5000
.word 0
.space 8
request_buffer: .space 4096
path_buffer: .space 256
todos_data:
.ascii "[{\"id\":0,\"text\":\"Learn Assembly Language\",\"completed\":false},{\"id\":1,\"text\":\"Build HTTP Server\",\"completed\":true},{\"id\":2,\"text\":\"Create TODO API\",\"completed\":false},{\"id\":3,\"text\":\"Add Frontend Interface\",\"completed\":true}]"
todos_len = . - todos_data
success_response:
.ascii "{\"status\":\"success\"}"
success_len = . - success_response
.text
.global _start
_start:
mov x8, #198
mov x0, #2
mov x1, #1
mov x2, #0
svc #0
mov x19, x0
mov x8, #208
mov x0, x19
adrp x1, socket_addr
add x1, x1, :lo12:socket_addr
mov x2, #1
mov x3, #2
mov x4, #1
mov x5, #4
svc #0
mov x8, #200
mov x0, x19
adrp x1, socket_addr
add x1, x1, :lo12:socket_addr
mov x2, #16
svc #0
mov x8, #201
mov x0, x19
mov x1, #10
svc #0
server_loop:
mov x8, #202
mov x0, x19
mov x1, #0
mov x2, #0
svc #0
mov x20, x0
mov x8, #63
mov x0, x20
adrp x1, request_buffer
add x1, x1, :lo12:request_buffer
mov x2, #4095
svc #0
adrp x0, request_buffer
add x0, x0, :lo12:request_buffer
bl parse_request
mov x8, #57
mov x0, x20
svc #0
b server_loop
.align 2
parse_request:
stp x29, x30, [sp, #-16]!
mov x29, sp
ldrb w1, [x0]
cmp w1, #'G'
b.eq handle_get
cmp w1, #'P'
b.eq check_post_put
cmp w1, #'D'
b.eq handle_delete
cmp w1, #'O'
b.eq handle_options
b send_404
.align 2
check_post_put:
ldrb w1, [x0, #1]
cmp w1, #'O'
b.eq handle_post
cmp w1, #'U'
b.eq handle_put
b send_404
.align 2
handle_get:
bl extract_path
adrp x1, path_buffer
add x1, x1, :lo12:path_buffer
ldrb w2, [x1]
cmp w2, #'/'
b.ne serve_html
ldrb w2, [x1, #1]
cmp w2, #0
b.eq serve_html
add x1, x1, #1
bl check_api_path
cmp x0, #1
b.eq serve_todos_json
b serve_html
.align 2
handle_post:
bl extract_path
adrp x1, path_buffer
add x1, x1, :lo12:path_buffer
add x1, x1, #1
bl check_api_path
cmp x0, #1
b.eq handle_add_todo
b send_404
.align 2
handle_put:
bl extract_path
adrp x1, path_buffer
add x1, x1, :lo12:path_buffer
add x1, x1, #1
bl check_api_path
cmp x0, #1
b.eq handle_toggle_todo
b send_404
.align 2
handle_delete:
bl extract_path
adrp x1, path_buffer
add x1, x1, :lo12:path_buffer
add x1, x1, #1
bl check_api_path
cmp x0, #1
b.eq handle_delete_todo
b send_404
.align 2
handle_options:
adrp x1, options_response
add x1, x1, :lo12:options_response
mov x2, options_len
bl send_response
b parse_end
.align 2
extract_path:
stp x29, x30, [sp, #-16]!
mov x29, sp
adrp x0, request_buffer
add x0, x0, :lo12:request_buffer
find_space:
ldrb w1, [x0]
cmp w1, #' '
b.eq found_space
add x0, x0, #1
b find_space
found_space:
add x0, x0, #1
copy_path:
adrp x2, path_buffer
add x2, x2, :lo12:path_buffer
mov x3, #0
copy_loop:
ldrb w1, [x0, x3]
cmp w1, #' '
b.eq path_done
cmp w1, #'?'
b.eq path_done
cmp w1, #0
b.eq path_done
strb w1, [x2, x3]
add x3, x3, #1
cmp x3, #255
b.lt copy_loop
path_done:
strb wzr, [x2, x3]
ldp x29, x30, [sp], #16
ret
.align 2
check_api_path:
stp x29, x30, [sp, #-16]!
mov x29, sp
adrp x2, api_todos_path
add x2, x2, :lo12:api_todos_path
mov x3, #0
check_loop:
ldrb w4, [x1, x3]
ldrb w5, [x2, x3]
cmp w4, w5
b.ne check_partial
cbz w5, is_api
add x3, x3, #1
b check_loop
check_partial:
cbz w5, check_if_slash
b not_api
check_if_slash:
ldrb w4, [x1, x3]
cmp w4, #0
b.eq is_api
cmp w4, #'/'
b.eq is_api
b not_api
is_api:
mov x0, #1
b check_end
not_api:
mov x0, #0
check_end:
ldp x29, x30, [sp], #16
ret
api_todos_path:
.ascii "api/todos\0"
.align 2
serve_html:
adrp x1, http_200_html
add x1, x1, :lo12:http_200_html
mov x2, http_200_html_len
bl send_response
adrp x1, html_page
add x1, x1, :lo12:html_page
mov x2, html_len
bl send_response
b parse_end
.align 2
serve_todos_json:
adrp x1, http_200_json
add x1, x1, :lo12:http_200_json
mov x2, http_200_json_len
bl send_response
adrp x1, todos_data
add x1, x1, :lo12:todos_data
mov x2, todos_len
bl send_response
b parse_end
.align 2
handle_add_todo:
adrp x1, http_200_json
add x1, x1, :lo12:http_200_json
mov x2, http_200_json_len
bl send_response
adrp x1, success_response
add x1, x1, :lo12:success_response
mov x2, success_len
bl send_response
b parse_end
.align 2
handle_toggle_todo:
adrp x1, http_200_json
add x1, x1, :lo12:http_200_json
mov x2, http_200_json_len
bl send_response
adrp x1, success_response
add x1, x1, :lo12:success_response
mov x2, success_len
bl send_response
b parse_end
.align 2
handle_delete_todo:
adrp x1, http_200_json
add x1, x1, :lo12:http_200_json
mov x2, http_200_json_len
bl send_response
adrp x1, success_response
add x1, x1, :lo12:success_response
mov x2, success_len
bl send_response
b parse_end
.align 2
send_404:
adrp x1, not_found_response
add x1, x1, :lo12:not_found_response
mov x2, not_found_len
bl send_response
b parse_end
not_found_response:
.ascii "HTTP/1.1 404 Not Found\r\nContent-Length: 13\r\nConnection: close\r\n\r\n404 Not Found"
not_found_len = . - not_found_response
.align 2
send_response:
mov x8, #64
mov x0, x20
svc #0
ret
.align 2
parse_end:
ldp x29, x30, [sp], #16
ret
mov x8, #93
mov x0, #0
svc #0
Create a web server in raw assembly, build, test and run it within a Docker container locally on my machine.
Make the server the backend for a TODO app with on disk storage of TODO data, and an API and web front end with nice HTML/CSS rendering of TODOs. Everyhing should still be in raw assembly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment