Created
July 4, 2025 16:32
-
-
Save lukehoban/c6657e4e9a6fa8cacc8efa871a70b68e to your computer and use it in GitHub Desktop.
TODO web app in assembly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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