Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pdm run ./manage.py run 'fn main { "Hello world\n" . }'
pdm run ./manage.py run examples/fizzbuzz.aaa

# Run bare-bones HTTP server in Aaa
pdm run ./manage.py run examples/http_server.aaa
pdm run ./manage.py run examples/http/server.aaa

# Send request from different shell
curl http://localhost:8080
Expand Down
15 changes: 12 additions & 3 deletions aaa-stdlib/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use nix::{
fcntl::{open, OFlag},
sys::{
socket::{
accept, bind, connect, getpeername, listen, socket, AddressFamily, SockFlag, SockType,
SockaddrIn,
accept, bind, connect, getpeername, listen, setsockopt, socket, sockopt::ReuseAddr,
AddressFamily, SockFlag, SockType, SockaddrIn,
},
stat::Mode,
wait::{WaitPidFlag, WaitStatus},
Expand Down Expand Up @@ -600,7 +600,16 @@ where
let addr = SockaddrIn::from_str(&format!("{ip_addr}:{port}")).unwrap();

let result = bind(fd as i32, &addr);
self.push_bool(result.is_ok());

if !result.is_ok() {
self.push_bool(false);
}

// We allow reuse addresses for all bind() calls.
// Implementing it separately would get messy, because the
// second argument cannot be loaded from int without usage of unsafe.
let setsockopt_result = setsockopt(fd as i32, ReuseAddr, &true);
self.push_bool(setsockopt_result.is_ok());
}

pub fn listen(&mut self) {
Expand Down
4 changes: 4 additions & 0 deletions aaa/cross_referencer/cross_referencer.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ def _save_identifier(self, identifiable: Identifiable) -> None:
except KeyError:
self.identifiers[key] = identifiable
else:
if found.position == identifiable.position:
# Cannot collide with same item
return

self.exceptions += [CollidingIdentifier([identifiable, found])]

def run(self) -> CrossReferencerOutput:
Expand Down
25 changes: 25 additions & 0 deletions examples/http/client.aaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

// used to create socket for IPv4 connections
fn AF_INET return int {
2
}

// used to create TCP connections
fn SOCK_STREAM return int {
1
}

fn make_http_socket return int, bool {
AF_INET SOCK_STREAM 0 socket
}

fn raw_http_request args host as str return str { // TODO use Request from request.aaa
"GET / HTTP/1.1\r\n"

"Host: " str:append
host str:append
"\r\n" str:append

"User-Agent: Aaa\r\n" str:append
"Accept: */*\r\n\r\n" str:append
}
93 changes: 93 additions & 0 deletions examples/http/request.aaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from "util" import make_range

struct Request {
method as str,
path as str,
headers as map[str, str],
body as str,
}

enum RequestParseResult {
ok as Request,
err as str,
}

fn parse_request args raw as str return RequestParseResult { // TODO refactor
Request

use request {
raw "\r\n" str:split
use lines {
lines 0 vec:get
use first_line {
first_line " " str:split
use split_first_line {
if split_first_line vec:len 3 != {
"could not parse first line" RequestParseResult:err return
}

request "method" { split_first_line 0 vec:get } !
request "path" { split_first_line 1 vec:get } !
}
}


-1
use body_separator_line_offset {
1 lines vec:len 1 - make_range
foreach {
use offset {
lines offset vec:get
use line {
if line "" str:equals body_separator_line_offset -1 = and {
body_separator_line_offset <- { offset }
}
}
}
}

1 body_separator_line_offset make_range
foreach {
use offset {
lines offset vec:get ": " str:split

use parts {
if parts vec:len 2 != {
drop
"Found invalid line "
lines offset vec:get repr str:append
RequestParseResult:err return
}


if parts vec:len 2 >= {
parts 0 vec:get
parts 1 vec:get
use header_name, header_value {
request "headers" ?
header_name str:lower
header_value
map:set
}
}
}
}
}

""
use body {
body_separator_line_offset 1 + lines vec:len make_range
foreach {
use offset {
body <- { body lines offset vec:get str:append }
}
}

request "body" { body } !
}
}
}

request RequestParseResult:ok
}
}
83 changes: 83 additions & 0 deletions examples/http/response.aaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

struct Response {
status_code as int,
content as str,
headers as map[str, str]
}

fn make_internal_server_error_response return Response {
Response
dup 500 Response:set_status_code
dup "Internal Server Error\n" Response:set_body
}

fn make_not_found_response return Response {
Response
dup 404 Response:set_status_code
dup "Not Found\n" Response:set_body
}

fn make_response return Response {
Response
dup "" Response:set_body
}

fn Response:set_header args response as Response, header_name as str, header_value as str {
response "headers" ?
header_name str:lower
header_value str:lower
map:set
}

fn Response:set_status_code args response as Response, status_code as int {
response "status_code" { status_code } !
}

fn Response:set_body args response as Response, body as str {
response "content" { body } !
response "Content-Length" body str:len repr Response:set_header
}

fn Response:set_json_body args response as Response, body as str {
response body Response:set_body
response "Content-Type" "application/json" Response:set_header
}

fn Response:status_code_to_str args response as Response return str {
response "status_code" ?
use status_code {
if status_code 200 = { "200 OK" return }
if status_code 400 = { "400 Bad Request" return }
if status_code 404 = { "404 Not Found" return }
if status_code 500 = { "500 Internal Server Error" return }

"WARNING: Can't find string for status code " .
status_code .
"\n" .

"500 Internal Server Error"
}
}

fn Response:to_str args response as Response return str {
"HTTP/1.1 "
use raw {
raw <- { raw response Response:status_code_to_str str:append }
raw <- { raw "\r\n" str:append }

response "headers" ?
foreach {
use name, value {
raw <- { raw name copy swap drop str:append }
raw <- { raw ": " str:append }
raw <- { raw value str:append }
raw <- { raw "\r\n" str:append }
}
}

raw <- { raw "\r\n" str:append }
raw <- { raw response "content" ? str:append }

raw
}
}
122 changes: 122 additions & 0 deletions examples/http/router.aaa
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from "request" import Request
from "response" import make_not_found_response, Response

// TODO move into builtins
fn str_has_prefix args string as str, prefix as str return bool {
string 0 prefix str:len str:substr
use string_prefix, ok {
if ok not {
false return
}
string_prefix prefix str:equals
}
}

fn get_suffix args path_suffix as str, matched_prefix as str return str {
path_suffix matched_prefix str:len path_suffix str:len str:substr assert
}

struct EndpointRouterItem {
method as str,
path as str,
handler as fn[Request][Response],
}

fn make_endpoint_router_item args
method as str,
path as str,
handler as fn[Request][Response],
return EndpointRouterItem {
EndpointRouterItem
dup "method" { method } !
dup "path" { path } !
dup "handler" { handler } !
}

struct ChildRouterItem {
prefix as str,
child as Router,
}

fn make_child_router args prefix as str, child as Router return ChildRouterItem {
ChildRouterItem
dup "prefix" { prefix } !
dup "child" { child } !
}

enum RouterItem {
endpoint as EndpointRouterItem,
child_router_item as ChildRouterItem,
}

fn RouterItem:is_match args
router_item as RouterItem,
request as Request,
path_suffix as str,
return bool {
router_item
match {
case RouterItem:endpoint as endpoint {
request "method" ? endpoint "method" ? str:equals
path_suffix endpoint "path" ? str:equals and
}
case RouterItem:child_router_item as child_router_item {
path_suffix child_router_item "prefix" ? str_has_prefix
}
}
}

struct Router {
items as vec[RouterItem],
}

fn Router:add_router args router as Router, prefix as str, child_router as Router {
prefix child_router make_child_router RouterItem:child_router_item

use router_item {
router "items" ? router_item vec:push
}
}

fn Router:add_endpoint args router as Router, method as str, path as str, endpoint as fn[Request][Response] {
method path endpoint make_endpoint_router_item RouterItem:endpoint

use router_item {
router "items" ? router_item vec:push
}
}

fn Router:_route args router as Router, request as Request, path_suffix as str return Response {
router "items" ?
foreach {
use router_item {
if router_item request path_suffix RouterItem:is_match {
router_item
match {
case RouterItem:endpoint as endpoint {
drop
request endpoint "handler" ? call return
}
case RouterItem:child_router_item as child_router_item {
path_suffix child_router_item "prefix" ? get_suffix
use child_path_suffix {
drop
child_router_item "child" ?
use child_router {
child_router request child_path_suffix Router:_route return
}
}
}
}
}
}
}
make_not_found_response
}

fn Router:route args router as Router, request as Request return Response {
request "path" ?
use path_suffix {
router request path_suffix Router:_route
}
}
Loading