Files
five/hbrtl/transform.go
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

225 lines
4.6 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Transform function — PICTURE format processing for @ SAY and GET.
// Harbour: src/rtl/transfrm.c
//
// Picture string format:
// @B Left-align numeric
// @C Append " CR" for positive numbers
// @D Date format
// @E European format (swap . and ,)
// @( Parentheses for negative
// @R Non-template chars in mask are inserted (not stored)
// @! Convert to uppercase
// @Z Display blank for zero
// @S<n> Scroll width
// @L Pad with leading zeros
//
// Mask chars: 9 # ! A N X Y L . ,
package hbrtl
import (
"five/hbrt"
"fmt"
"strings"
)
// transformHbValue applies a PICTURE format to an hbrt.Value.
func transformHbValue(v hbrt.Value, pic string) string {
if pic == "" {
return valueToDisplay(v)
}
funcFlags, mask := parsePicture(pic)
if v.IsString() {
return transformString(v.AsString(), funcFlags, mask)
}
if v.IsNumeric() {
if v.IsNumInt() {
return transformNumeric(float64(v.AsNumInt()), funcFlags, mask)
}
return transformNumeric(v.AsNumDouble(), funcFlags, mask)
}
if v.IsLogical() {
if v.AsBool() {
return "T"
}
return "F"
}
return valueToDisplay(v)
}
// parsePicture splits "@flags mask" into flags and mask.
func parsePicture(pic string) (string, string) {
if len(pic) == 0 || pic[0] != '@' {
return "", pic
}
// Find space separating function from mask
idx := strings.IndexByte(pic, ' ')
if idx < 0 {
return strings.ToUpper(pic[1:]), ""
}
return strings.ToUpper(pic[1:idx]), pic[idx+1:]
}
func transformString(s string, flags string, mask string) string {
upper := strings.ContainsRune(flags, '!')
if mask == "" {
if upper {
return strings.ToUpper(s)
}
return s
}
// Apply mask
var out strings.Builder
si := 0
for _, m := range mask {
if si >= len(s) {
break
}
ch := s[si]
switch m {
case '!':
if ch >= 'a' && ch <= 'z' {
ch = ch - 32
}
out.WriteByte(ch)
si++
case 'A', 'N', 'X', '9', '#':
if upper && ch >= 'a' && ch <= 'z' {
ch = ch - 32
}
out.WriteByte(ch)
si++
default:
// Non-template char: if @R, insert literal; else consume
if strings.ContainsRune(flags, 'R') {
out.WriteRune(m)
} else {
out.WriteByte(ch)
si++
}
}
}
return out.String()
}
func transformNumeric(n float64, flags string, mask string) string {
blankZero := strings.ContainsRune(flags, 'Z')
leftAlign := strings.ContainsRune(flags, 'B')
credit := strings.ContainsRune(flags, 'C')
debit := strings.ContainsRune(flags, 'X')
paren := strings.ContainsRune(flags, '(')
padZero := strings.ContainsRune(flags, 'L')
if blankZero && n == 0 {
if mask != "" {
return strings.Repeat(" ", len(mask))
}
return " "
}
if mask == "" {
s := fmt.Sprintf("%g", n)
if leftAlign {
s = strings.TrimLeft(s, " ")
}
return s
}
// Count digit positions and decimal
nDec := 0
nInt := 0
decIdx := strings.IndexByte(mask, '.')
for _, m := range mask {
if m == '9' || m == '#' {
if decIdx >= 0 && nInt+nDec >= decIdx {
nDec++
} else {
nInt++
}
}
}
// Format the number
var formatted string
if nDec > 0 {
formatted = fmt.Sprintf("%*.*f", len(mask), nDec, n)
} else {
formatted = fmt.Sprintf("%*.0f", len(mask), n)
}
// Pad with zeros if @L
if padZero {
neg := n < 0
stripped := strings.TrimLeft(formatted, " ")
if neg {
stripped = strings.TrimLeft(stripped, "-")
formatted = "-" + strings.Repeat("0", len(mask)-len(stripped)-1) + stripped
} else {
formatted = strings.Repeat("0", len(mask)-len(stripped)) + stripped
}
}
// Apply mask character by character
var out strings.Builder
fi := 0
if len(formatted) > len(mask) {
fi = len(formatted) - len(mask)
}
for _, m := range mask {
if fi >= len(formatted) {
out.WriteRune(m)
continue
}
switch m {
case '9', '#':
out.WriteByte(formatted[fi])
fi++
case '.':
out.WriteByte('.')
fi++
case ',':
if fi < len(formatted) && formatted[fi] == ',' {
out.WriteByte(',')
fi++
} else if fi < len(formatted) && (formatted[fi] >= '0' && formatted[fi] <= '9') {
out.WriteByte(',')
} else {
out.WriteByte(' ')
fi++
}
default:
out.WriteRune(m)
fi++
}
}
result := out.String()
if leftAlign {
result = strings.TrimLeft(result, " ")
result = result + strings.Repeat(" ", len(mask)-len(result))
}
// Parentheses for negative
if paren && n < 0 {
result = strings.Replace(result, "-", "(", 1)
result = strings.TrimRight(result, " ") + ")"
}
// Credit/Debit suffix
if credit && n >= 0 {
result += " CR"
}
if debit && n < 0 {
result += " DB"
}
return result
}