Files
five/examples/go_websocket.prg
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

159 lines
4.1 KiB
Plaintext

// Five Example: Real-time WebSocket Chat Server
//
// Complete chat server in ONE .prg file.
// Go handles WebSocket, HTTP, concurrency.
// PRG handles message processing logic.
//
// Open http://localhost:9090 in multiple browser tabs to test.
PROCEDURE Main()
? "=== Five WebSocket Chat Server ==="
? "Open http://localhost:9090 in your browser"
? "Press Ctrl+C to stop"
?
GoStartChat("9090")
RETURN
FUNCTION ProcessMessage(cUser, cMessage)
LOCAL cResult
DO CASE
CASE Upper(Left(cMessage, 5)) == "/HELP"
cResult := "Commands: /help /time /users /shout <msg>"
CASE Upper(Left(cMessage, 5)) == "/TIME"
cResult := "Server time: " + Time() + " " + DToC(Date())
CASE Upper(Left(cMessage, 6)) == "/SHOUT"
cResult := Upper(SubStr(cMessage, 7))
OTHERWISE
cResult := cMessage
ENDCASE
RETURN "[" + cUser + "] " + cResult
#pragma BEGINDUMP
import (
"five/hbrt"
"fmt"
"net/http"
"sync"
"time"
"golang.org/x/net/websocket"
)
func init() {
hbrt.HB_FUNC("GOSTARTCHAT", goStartChat)
}
type chatServer struct {
mu sync.RWMutex
clients map[*websocket.Conn]string
history []string
}
var chat = &chatServer{
clients: make(map[*websocket.Conn]string),
}
func goStartChat(ctx *hbrt.HBContext) {
port := ctx.ParC(1)
if port == "" {
port = "9090"
}
http.HandleFunc("/", serveHome)
http.Handle("/ws", websocket.Handler(handleWS))
fmt.Printf("Chat server listening on :%s\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
ctx.RetC("Error: " + err.Error())
}
}
func handleWS(ws *websocket.Conn) {
name := fmt.Sprintf("User_%d", time.Now().UnixNano()%10000)
chat.mu.Lock()
chat.clients[ws] = name
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s joined (%d online) *", name, len(chat.clients)))
chat.mu.RLock()
for _, msg := range chat.history {
websocket.Message.Send(ws, msg)
}
chat.mu.RUnlock()
for {
var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil {
break
}
if msg == "" {
continue
}
if len(msg) > 6 && msg[:6] == "/name " {
oldName := name
name = msg[6:]
chat.mu.Lock()
chat.clients[ws] = name
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s is now %s *", oldName, name))
continue
}
broadcast(fmt.Sprintf("[%s] %s", name, msg))
}
chat.mu.Lock()
delete(chat.clients, ws)
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s left (%d online) *", name, len(chat.clients)))
}
func broadcast(msg string) {
chat.mu.Lock()
chat.history = append(chat.history, msg)
if len(chat.history) > 100 {
chat.history = chat.history[len(chat.history)-100:]
}
snapshot := make(map[*websocket.Conn]bool)
for k := range chat.clients {
snapshot[k] = true
}
chat.mu.Unlock()
for ws := range snapshot {
websocket.Message.Send(ws, msg)
}
}
func serveHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, chatHTML)
}
const chatHTML = `<!DOCTYPE html>
<html><head><title>Five Chat</title>
<style>
body{font-family:monospace;background:#1a1a2e;color:#eee;margin:20px}
h1{color:#e94560}
#log{background:#16213e;padding:15px;height:400px;overflow-y:auto;border:1px solid #0f3460;border-radius:8px;white-space:pre-wrap}
#msg{width:80%;padding:10px;background:#0f3460;color:#eee;border:1px solid #e94560;border-radius:4px;font-family:monospace}
button{padding:10px 20px;background:#e94560;color:#fff;border:none;border-radius:4px;cursor:pointer}
</style></head><body>
<h1>Five Chat</h1><p>Harbour + Go WebSocket</p>
<div id="log"></div><br>
<input id="msg" placeholder="Type message... /name YourName /help" autofocus>
<button onclick="send()">Send</button>
<script>
var ws=new WebSocket("ws://"+location.host+"/ws"),log=document.getElementById("log");
ws.onmessage=function(e){log.textContent+=e.data+"\n";log.scrollTop=log.scrollHeight};
ws.onclose=function(){log.textContent+="* Disconnected *\n"};
function send(){var m=document.getElementById("msg");if(m.value){ws.send(m.value);m.value=""}}
document.getElementById("msg").onkeypress=function(e){if(e.key==="Enter")send()};
</script></body></html>`
#pragma ENDDUMP