github.com/gotranspile/cxgo@v0.3.7/runtime/stdio/scan.go (about) 1 package stdio 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 11 "github.com/gotranspile/cxgo/runtime/libc" 12 ) 13 14 func FscanfGo(r io.Reader, format string, args ...interface{}) (int, error) { 15 left := args 16 var ( 17 goformat bytes.Buffer 18 goargs []interface{} 19 post []func() error 20 ) 21 popArg := func() (interface{}, error) { 22 if len(left) == 0 { 23 return nil, errors.New("not enough arguments to scan") 24 } 25 v := left[0] 26 left = left[1:] 27 return v, nil 28 } 29 processArg := func(typ string) error { 30 switch typ { 31 case "%%": 32 goformat.WriteString("%%") 33 return nil 34 case "%s": 35 p, err := popArg() 36 if err != nil { 37 return err 38 } 39 goformat.WriteString("%s") 40 var v string 41 goargs = append(goargs, &v) 42 43 rv, err := libc.AsPtr(p) 44 if err != nil { 45 panic(err) 46 } 47 pv := (*byte)(rv) 48 post = append(post, func() error { 49 libc.StrCpyGoZero(pv, []byte(v)) 50 return nil 51 }) 52 return nil 53 case "%d": 54 p, err := popArg() 55 if err != nil { 56 return err 57 } 58 goformat.WriteString("%d") 59 var v int 60 goargs = append(goargs, &v) 61 rv, err := libc.AsPtr(p) 62 if err != nil { 63 panic(err) 64 } 65 pv := (*int)(rv) 66 post = append(post, func() error { 67 *pv = v 68 return nil 69 }) 70 return nil 71 case "%f": 72 p, err := popArg() 73 if err != nil { 74 return err 75 } 76 goformat.WriteString("%f") 77 var v float32 78 goargs = append(goargs, &v) 79 rv, err := libc.AsPtr(p) 80 if err != nil { 81 panic(err) 82 } 83 pv := (*float32)(rv) 84 post = append(post, func() error { 85 *pv = v 86 return nil 87 }) 88 return nil 89 case "%*s": 90 goformat.WriteString("%s") 91 var v string 92 goargs = append(goargs, &v) 93 // ignore 94 return nil 95 default: 96 panic(typ) 97 } 98 } 99 for _, w := range parseFormat(format) { 100 if !w.Verb { 101 goformat.WriteString(w.Str) 102 continue 103 } 104 if err := processArg(w.Str); err != nil { 105 log.Printf("fscanf(%q, %+v): process: %v", format, args, err) 106 return 0, err 107 } 108 } 109 r = &spaceReader{r: r} 110 _, err := fmt.Fscanf(r, goformat.String(), goargs...) 111 if err != nil { 112 log.Printf("fscanf(%q, %+v): scan: %v", format, args, err) 113 return 0, err 114 } 115 for _, fnc := range post { 116 if err := fnc(); err != nil { 117 log.Printf("fscanf(%q, %+v): post: %v", format, args, err) 118 return 0, err 119 } 120 } 121 return len(args), nil 122 } 123 124 // spaceReader replaces new line characters with spaces. 125 // It's needed because scanf in C interprets new lines as spaces, while Go's fmt.Scanf requires new lines to be included 126 // in the format string. 127 type spaceReader struct { 128 r io.Reader 129 } 130 131 func (r *spaceReader) Read(p []byte) (int, error) { 132 n, err := r.r.Read(p) 133 for i := range p[:n] { 134 switch p[i] { 135 case '\n', '\r': 136 p[i] = ' ' 137 } 138 } 139 return n, err 140 } 141 142 func Scanf(format string, args ...interface{}) int { 143 n, err := FscanfGo(os.Stdin, format, args...) 144 if err != nil { 145 libc.SetErr(err) 146 return -1 147 } 148 return n 149 } 150 151 func Vscanf(format string, args libc.ArgList) int { 152 return Scanf(format, args.Args()...) 153 } 154 155 func Sscanf(buf *byte, format string, args ...interface{}) int { 156 str := libc.GoBytes(buf) 157 n, err := FscanfGo(bytes.NewReader(str), format, args...) 158 if err != nil { 159 libc.SetErr(err) 160 return -1 161 } 162 return n 163 } 164 165 func Vsscanf(buf *byte, format string, args libc.ArgList) int { 166 return Sscanf(buf, format, args.Args()...) 167 }