Skip to content

Commit bf221d1

Browse files
committed
✨ feat: implement net_listen helper and webserver sample
Add a net_listen helper function to enable socket operations within the ELF emitter. This change enhances the codebase by providing a low-level socket implementation. Introduce a new sample web server script that utilizes the net_listen function, demonstrating basic HTTP server capabilities. This allows users to easily see how the socket API works in practice. Refactor the lexer to handle escape sequences in string literals more robustly, improving string processing accuracy.
1 parent 4a65f1c commit bf221d1

3 files changed

Lines changed: 224 additions & 2 deletions

File tree

compiler/emitter/elf/elf.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,158 @@ func (e *Emitter) generateTextSegment() []byte {
611611
}
612612
code = append(code, readStrHelper...)
613613

614+
// Append net_listen helper
615+
labelPCs["net_listen"] = len(code)
616+
netListenHelperParts := [][]byte{
617+
/* 0: entry & socket call */
618+
{0x50}, // push rax (save port)
619+
{0x48, 0xc7, 0xc7, 0x02, 0x00, 0x00, 0x00}, // mov rdi, 2 (AF_INET)
620+
{0x48, 0xc7, 0xc6, 0x01, 0x00, 0x00, 0x00}, // mov rsi, 1 (SOCK_STREAM)
621+
{0x48, 0xc7, 0xc2, 0x00, 0x00, 0x00, 0x00}, // mov rdx, 0 (IPPROTO_IP)
622+
{0x48, 0xc7, 0xc0, 0x29, 0x00, 0x00, 0x00}, // mov rax, 41 (sys_socket)
623+
{0x0f, 0x05}, // syscall
624+
{0x48, 0x85, 0xc0}, // test rax, rax
625+
{0x78, 0x00}, // js socket_error (placeholder offset at idx 7)
626+
627+
/* 8: bind preparation */
628+
{0x50}, // push rax (save socket fd)
629+
{0x48, 0x8b, 0x44, 0x24, 0x08}, // mov rax, [rsp + 8] (get port)
630+
{0x66, 0xc1, 0xc0, 0x08}, // rol ax, 8
631+
{0x48, 0xc1, 0xe0, 0x10}, // shl rax, 16
632+
{0x48, 0x83, 0xc8, 0x02}, // or rax, 2
633+
{0x48, 0x31, 0xd2}, // xor rdx, rdx
634+
{0x52}, // push rdx (sin_zero)
635+
{0x50}, // push rax (family, port, addr)
636+
637+
/* 16: bind call */
638+
{0x48, 0x8b, 0x7c, 0x24, 0x10}, // mov rdi, [rsp + 16] (socket fd)
639+
{0x48, 0x89, 0xe6}, // mov rsi, rsp
640+
{0x48, 0xc7, 0xc2, 0x10, 0x00, 0x00, 0x00}, // mov rdx, 16
641+
{0x48, 0xc7, 0xc0, 0x31, 0x00, 0x00, 0x00}, // mov rax, 49 (sys_bind)
642+
{0x0f, 0x05}, // syscall
643+
{0x48, 0x85, 0xc0}, // test rax, rax
644+
{0x78, 0x00}, // js bind_error (placeholder offset at idx 22)
645+
646+
/* 23: listen preparation */
647+
{0x48, 0x83, 0xc4, 0x10}, // add rsp, 16
648+
{0x48, 0x8b, 0x3c, 0x24}, // mov rdi, [rsp]
649+
{0x48, 0xc7, 0xc6, 0x80, 0x00, 0x00, 0x00}, // mov rsi, 128
650+
{0x48, 0xc7, 0xc0, 0x32, 0x00, 0x00, 0x00}, // mov rax, 50 (sys_listen)
651+
{0x0f, 0x05}, // syscall
652+
{0x48, 0x85, 0xc0}, // test rax, rax
653+
{0x78, 0x00}, // js listen_error (placeholder offset at idx 29)
654+
655+
/* 30: listen success */
656+
{0x58}, // pop rax (return fd)
657+
{0x5e}, // pop rsi (clean port)
658+
{0xc3}, // ret
659+
660+
/* 33: bind_error block */
661+
{0x48, 0x83, 0xc4, 0x10}, // add rsp, 16 (bind_error target)
662+
663+
/* 34: listen_error block */
664+
{0x5f}, // pop rdi (listen_error target)
665+
{0x5e}, // pop rsi
666+
{0x57}, // push rdi
667+
{0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00}, // mov rax, 3
668+
{0x0f, 0x05}, // syscall
669+
{0x58}, // pop rax
670+
{0x48, 0xc7, 0xc0, 0xff, 0xff, 0xff, 0xff}, // mov rax, -1
671+
{0xc3}, // ret
672+
673+
/* 42: socket_error block */
674+
{0x5e}, // pop rsi (socket_error target)
675+
{0x48, 0xc7, 0xc0, 0xff, 0xff, 0xff, 0xff}, // mov rax, -1
676+
{0xc3}, // ret
677+
}
678+
listenOffsets := make([]int, len(netListenHelperParts))
679+
currentListenOffset := 0
680+
for i, part := range netListenHelperParts {
681+
listenOffsets[i] = currentListenOffset
682+
currentListenOffset += len(part)
683+
}
684+
netListenHelperParts[7][1] = byte(listenOffsets[42] - listenOffsets[8])
685+
netListenHelperParts[22][1] = byte(listenOffsets[33] - listenOffsets[23])
686+
netListenHelperParts[29][1] = byte(listenOffsets[34] - listenOffsets[30])
687+
for _, part := range netListenHelperParts {
688+
code = append(code, part...)
689+
}
690+
691+
// Append net_accept helper
692+
labelPCs["net_accept"] = len(code)
693+
netAcceptHelper := []byte{
694+
0x48, 0x89, 0xc7, // mov rdi, rax
695+
0x48, 0x31, 0xf6, // xor rsi, rsi
696+
0x48, 0x31, 0xd2, // xor rdx, rdx
697+
0x48, 0xc7, 0xc0, 0x2b, 0x00, 0x00, 0x00, // mov rax, 43 (sys_accept)
698+
0x0f, 0x05, // syscall
699+
0xc3, // ret
700+
}
701+
code = append(code, netAcceptHelper...)
702+
703+
// Append net_read helper
704+
labelPCs["net_read"] = len(code)
705+
netReadStart := len(code)
706+
netReadHelperParts := [][]byte{
707+
/* 0: read syscall */
708+
{0x48, 0x89, 0xc7}, // mov rdi, rax
709+
{0x48, 0x8d, 0x35, 0x00, 0x00, 0x00, 0x00}, // lea rsi, [rip + __net_buf] (placeholder)
710+
{0x48, 0xc7, 0xc2, 0x00, 0x10, 0x00, 0x00}, // mov rdx, 4096
711+
{0x48, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00}, // mov rax, 0
712+
{0x0f, 0x05}, // syscall
713+
{0x48, 0x85, 0xc0}, // test rax, rax
714+
{0x7e, 0x00}, // jle net_read_empty (placeholder offset at idx 6)
715+
716+
/* 7: success path */
717+
{0xc6, 0x04, 0x06, 0x00}, // mov byte ptr [rsi + rax], 0
718+
{0x48, 0x89, 0xf0}, // mov rax, rsi
719+
{0xc3}, // ret
720+
721+
/* 10: empty path */
722+
{0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00}, // lea rax, [rip + __empty_str] (placeholder)
723+
{0xc3}, // ret
724+
}
725+
readOffsets := make([]int, len(netReadHelperParts))
726+
currentReadOffset := 0
727+
for i, part := range netReadHelperParts {
728+
readOffsets[i] = currentReadOffset
729+
currentReadOffset += len(part)
730+
}
731+
netReadHelperParts[6][1] = byte(readOffsets[10] - readOffsets[7])
732+
for _, part := range netReadHelperParts {
733+
code = append(code, part...)
734+
}
735+
736+
// Append net_write helper
737+
labelPCs["net_write"] = len(code)
738+
netWriteHelper := []byte{
739+
0x57, // push rdi
740+
0x56, // push rsi
741+
0x48, 0x31, 0xd2, // xor rdx, rdx
742+
// net_write_len_loop:
743+
0x80, 0x3c, 0x16, 0x00, // cmp byte ptr [rsi + rdx], 0
744+
0x74, 0x05, // je net_write_len_done
745+
0x48, 0xff, 0xc2, // inc rdx
746+
0xeb, 0xf5, // jmp net_write_len_loop
747+
// net_write_len_done:
748+
0x5e, // pop rsi
749+
0x5f, // pop rdi
750+
0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 (sys_write)
751+
0x0f, 0x05, // syscall
752+
0xc3, // ret
753+
}
754+
code = append(code, netWriteHelper...)
755+
756+
// Append net_close helper
757+
labelPCs["net_close"] = len(code)
758+
netCloseHelper := []byte{
759+
0x48, 0x89, 0xc7, // mov rdi, rax
760+
0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00, // mov rax, 3 (sys_close)
761+
0x0f, 0x05, // syscall
762+
0xc3, // ret
763+
}
764+
code = append(code, netCloseHelper...)
765+
614766
// Append static data buffers
615767
emptyStrPC := len(code)
616768
code = append(code, 0x00)
@@ -623,6 +775,10 @@ func (e *Emitter) generateTextSegment() []byte {
623775
readFileBufBytes := make([]byte, 65536)
624776
code = append(code, readFileBufBytes...)
625777

778+
netBufPC := len(code)
779+
netBufBytes := make([]byte, 4096)
780+
code = append(code, netBufBytes...)
781+
626782
// Relocate readFileHelper placeholders
627783
binary.LittleEndian.PutUint32(code[readFileStart+34:readFileStart+38], uint32(readFileBufPC-(readFileStart+38)))
628784
binary.LittleEndian.PutUint32(code[readFileStart+65:readFileStart+69], uint32(readFileBufPC-(readFileStart+69)))
@@ -635,6 +791,12 @@ func (e *Emitter) generateTextSegment() []byte {
635791
binary.LittleEndian.PutUint32(code[readStrStart+82:readStrStart+86], uint32(inputBufPC-(readStrStart+86)))
636792
binary.LittleEndian.PutUint32(code[readStrStart+90:readStrStart+94], uint32(emptyStrPC-(readStrStart+94)))
637793

794+
// Relocate net_read placeholders
795+
// lea rsi, [rip + __net_buf] at netReadStart + readOffsets[1]
796+
binary.LittleEndian.PutUint32(code[netReadStart+readOffsets[1]+3:netReadStart+readOffsets[1]+7], uint32(netBufPC-(netReadStart+readOffsets[2])))
797+
// lea rax, [rip + __empty_str] at netReadStart + readOffsets[10]
798+
binary.LittleEndian.PutUint32(code[netReadStart+readOffsets[10]+3:netReadStart+readOffsets[10]+7], uint32(emptyStrPC-(netReadStart+readOffsets[11])))
799+
638800
// Pass 2: label offsets resolution
639801
for _, ref := range refs {
640802
targetPC, ok := labelPCs[ref.targetLabel]

compiler/lexer/lexer.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,38 @@ func (l *Lexer) readNumber() string {
173173
}
174174

175175
func (l *Lexer) readString() string {
176-
position := l.position + 1
176+
var out []byte
177177
for {
178178
l.readChar()
179179
if l.ch == '"' || l.ch == 0 {
180180
break
181181
}
182+
if l.ch == '\\' {
183+
next := l.peekChar()
184+
switch next {
185+
case 'n':
186+
out = append(out, '\n')
187+
l.readChar()
188+
case 'r':
189+
out = append(out, '\r')
190+
l.readChar()
191+
case 't':
192+
out = append(out, '\t')
193+
l.readChar()
194+
case '\\':
195+
out = append(out, '\\')
196+
l.readChar()
197+
case '"':
198+
out = append(out, '"')
199+
l.readChar()
200+
default:
201+
out = append(out, '\\')
202+
}
203+
} else {
204+
out = append(out, l.ch)
205+
}
182206
}
183-
return l.input[position:l.position]
207+
return string(out)
184208
}
185209

186210
func (l *Lexer) skipWhitespace() {

samples/webserver.ship

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// webserver.ship: A simple HTTP web server written in Ship using low-level socket API.
2+
// Run it directly: ship samples/webserver.ship
3+
4+
fn main() -> int {
5+
let port = 8082;
6+
print("Starting web server on port 8082...\n");
7+
8+
let listener = net_listen(port);
9+
if (listener == -1) {
10+
print("Failed to start listener\n");
11+
return 1;
12+
}
13+
14+
print("Listening for incoming connection...\n");
15+
16+
let client = net_accept(listener);
17+
if (client == -1) {
18+
print("Failed to accept client connection\n");
19+
net_close(listener);
20+
return 1;
21+
}
22+
23+
print("Client connected! Reading request:\n");
24+
25+
let request = net_read(client);
26+
print(request);
27+
28+
let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: close\r\n\r\nHello World!";
29+
net_write(client, response);
30+
31+
print("\nResponse sent. Closing connection.\n");
32+
net_close(client);
33+
net_close(listener);
34+
35+
return 0;
36+
}

0 commit comments

Comments
 (0)