c.AllocaCStrs; ssa: AllocaU/ArrayAlloca/Times/AllocaCStrs
This commit is contained in:
@@ -1,20 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/goplus/llgo/c"
|
"github.com/goplus/llgo/c"
|
||||||
"github.com/goplus/llgo/c/os"
|
"github.com/goplus/llgo/c/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ls := c.Str("ls")
|
ls := c.Str("ls")
|
||||||
args := []*c.Char{ls, c.Str("-l"), nil}
|
os.Execlp(ls, ls, c.Str("-l"), nil)
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
ls = c.Str("dir")
|
|
||||||
args = []*c.Char{ls, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Execvp(ls, unsafe.SliceData(args))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ls := "ls"
|
ls := "ls"
|
||||||
args := []string{"ls", "-l"}
|
args := []string{ls, "-l"}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
ls = "dir"
|
ls = "dir"
|
||||||
args = nil
|
args = []string{ls}
|
||||||
}
|
}
|
||||||
lspath, _ := exec.LookPath(ls)
|
lspath, _ := exec.LookPath(ls)
|
||||||
if lspath != "" {
|
if lspath != "" {
|
||||||
|
|||||||
3
c/c.go
3
c/c.go
@@ -68,6 +68,9 @@ func Alloca(size uintptr) Pointer
|
|||||||
//go:linkname AllocaCStr llgo.allocaCStr
|
//go:linkname AllocaCStr llgo.allocaCStr
|
||||||
func AllocaCStr(s string) *Char
|
func AllocaCStr(s string) *Char
|
||||||
|
|
||||||
|
//go:linkname AllocaCStrs llgo.allocaCStrs
|
||||||
|
func AllocaCStrs(strs []string, endWithNil bool) **Char
|
||||||
|
|
||||||
// TODO(xsw):
|
// TODO(xsw):
|
||||||
// llgo:link AllocaNew llgo.allocaNew
|
// llgo:link AllocaNew llgo.allocaNew
|
||||||
func AllocaNew[T any]() *T { return nil }
|
func AllocaNew[T any]() *T { return nil }
|
||||||
|
|||||||
21
c/syscall/unix/unix.go
Normal file
21
c/syscall/unix/unix.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
const (
|
||||||
|
LLGoPackage = "decl"
|
||||||
|
)
|
||||||
@@ -143,6 +143,7 @@ func TestErrBuiltin(t *testing.T) {
|
|||||||
test("advance", func(ctx *context) { ctx.advance(nil, nil) })
|
test("advance", func(ctx *context) { ctx.advance(nil, nil) })
|
||||||
test("alloca", func(ctx *context) { ctx.alloca(nil, nil) })
|
test("alloca", func(ctx *context) { ctx.alloca(nil, nil) })
|
||||||
test("allocaCStr", func(ctx *context) { ctx.allocaCStr(nil, nil) })
|
test("allocaCStr", func(ctx *context) { ctx.allocaCStr(nil, nil) })
|
||||||
|
test("allocaCStrs", func(ctx *context) { ctx.allocaCStrs(nil, nil) })
|
||||||
test("string", func(ctx *context) { ctx.string(nil, nil) })
|
test("string", func(ctx *context) { ctx.string(nil, nil) })
|
||||||
test("stringData", func(ctx *context) { ctx.stringData(nil, nil) })
|
test("stringData", func(ctx *context) { ctx.stringData(nil, nil) })
|
||||||
test("funcAddr", func(ctx *context) { ctx.funcAddr(nil, nil) })
|
test("funcAddr", func(ctx *context) { ctx.funcAddr(nil, nil) })
|
||||||
|
|||||||
10
cl/import.go
10
cl/import.go
@@ -381,17 +381,19 @@ const (
|
|||||||
llgoCstr = llgoInstrBase + 1
|
llgoCstr = llgoInstrBase + 1
|
||||||
llgoAlloca = llgoInstrBase + 2
|
llgoAlloca = llgoInstrBase + 2
|
||||||
llgoAllocaCStr = llgoInstrBase + 3
|
llgoAllocaCStr = llgoInstrBase + 3
|
||||||
llgoAdvance = llgoInstrBase + 4
|
llgoAllocaCStrs = llgoInstrBase + 4
|
||||||
llgoIndex = llgoInstrBase + 5
|
llgoAdvance = llgoInstrBase + 5
|
||||||
llgoDeferData = llgoInstrBase + 6
|
llgoIndex = llgoInstrBase + 6
|
||||||
llgoStringData = llgoInstrBase + 7
|
llgoStringData = llgoInstrBase + 7
|
||||||
llgoString = llgoInstrBase + 8
|
llgoString = llgoInstrBase + 8
|
||||||
llgoFuncAddr = llgoInstrBase + 9
|
llgoDeferData = llgoInstrBase + 9
|
||||||
|
|
||||||
llgoSigjmpbuf = llgoInstrBase + 0xa
|
llgoSigjmpbuf = llgoInstrBase + 0xa
|
||||||
llgoSigsetjmp = llgoInstrBase + 0xb
|
llgoSigsetjmp = llgoInstrBase + 0xb
|
||||||
llgoSiglongjmp = llgoInstrBase + 0xc
|
llgoSiglongjmp = llgoInstrBase + 0xc
|
||||||
|
|
||||||
|
llgoFuncAddr = llgoInstrBase + 0xd
|
||||||
|
|
||||||
llgoPyList = llgoInstrBase + 0x10
|
llgoPyList = llgoInstrBase + 0x10
|
||||||
llgoPyStr = llgoInstrBase + 0x11
|
llgoPyStr = llgoInstrBase + 0x11
|
||||||
|
|
||||||
|
|||||||
48
cl/instr.go
48
cl/instr.go
@@ -28,14 +28,29 @@ import (
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func constStr(v ssa.Value) (ret string, ok bool) {
|
||||||
|
if c, ok := v.(*ssa.Const); ok {
|
||||||
|
if v := c.Value; v.Kind() == constant.String {
|
||||||
|
return constant.StringVal(v), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func constBool(v ssa.Value) (ret bool, ok bool) {
|
||||||
|
if c, ok := v.(*ssa.Const); ok {
|
||||||
|
if v := c.Value; v.Kind() == constant.Bool {
|
||||||
|
return constant.BoolVal(v), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// func pystr(string) *py.Object
|
// func pystr(string) *py.Object
|
||||||
func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
if c, ok := args[0].(*ssa.Const); ok {
|
if sv, ok := constStr(args[0]); ok {
|
||||||
if v := c.Value; v.Kind() == constant.String {
|
return b.PyStr(sv)
|
||||||
sv := constant.StringVal(v)
|
|
||||||
return b.PyStr(sv)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("pystr(<string-literal>): invalid arguments")
|
panic("pystr(<string-literal>): invalid arguments")
|
||||||
@@ -44,11 +59,8 @@ func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
|||||||
// func cstr(string) *int8
|
// func cstr(string) *int8
|
||||||
func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
if c, ok := args[0].(*ssa.Const); ok {
|
if sv, ok := constStr(args[0]); ok {
|
||||||
if v := c.Value; v.Kind() == constant.String {
|
return b.CStr(sv)
|
||||||
sv := constant.StringVal(v)
|
|
||||||
return b.CStr(sv)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("cstr(<string-literal>): invalid arguments")
|
panic("cstr(<string-literal>): invalid arguments")
|
||||||
@@ -87,6 +99,19 @@ func (p *context) allocaCStr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr)
|
|||||||
panic("allocaCStr(s string): invalid arguments")
|
panic("allocaCStr(s string): invalid arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func allocaCStrs(strs []string, endWithNil bool) **int8
|
||||||
|
func (p *context) allocaCStrs(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
||||||
|
if len(args) == 2 {
|
||||||
|
endWithNil, ok := constBool(args[1])
|
||||||
|
if !ok {
|
||||||
|
panic("allocaCStrs(strs, endWithNil): endWithNil should be constant bool")
|
||||||
|
}
|
||||||
|
strs := p.compileValue(b, args[0])
|
||||||
|
return b.AllocaCStrs(strs, endWithNil)
|
||||||
|
}
|
||||||
|
panic("allocaCStrs(strs []string, endWithNil bool): invalid arguments")
|
||||||
|
}
|
||||||
|
|
||||||
// func string(cstr *int8, n ...int) *int8
|
// func string(cstr *int8, n ...int) *int8
|
||||||
func (p *context) string(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
func (p *context) string(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
@@ -185,6 +210,7 @@ var llgoInstrs = map[string]int{
|
|||||||
"index": llgoIndex,
|
"index": llgoIndex,
|
||||||
"alloca": llgoAlloca,
|
"alloca": llgoAlloca,
|
||||||
"allocaCStr": llgoAllocaCStr,
|
"allocaCStr": llgoAllocaCStr,
|
||||||
|
"allocaCStrs": llgoAllocaCStrs,
|
||||||
"string": llgoString,
|
"string": llgoString,
|
||||||
"stringData": llgoStringData,
|
"stringData": llgoStringData,
|
||||||
"funcAddr": llgoFuncAddr,
|
"funcAddr": llgoFuncAddr,
|
||||||
@@ -340,6 +366,8 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon
|
|||||||
ret = p.alloca(b, args)
|
ret = p.alloca(b, args)
|
||||||
case llgoAllocaCStr:
|
case llgoAllocaCStr:
|
||||||
ret = p.allocaCStr(b, args)
|
ret = p.allocaCStr(b, args)
|
||||||
|
case llgoAllocaCStrs:
|
||||||
|
ret = p.allocaCStrs(b, args)
|
||||||
case llgoString:
|
case llgoString:
|
||||||
ret = p.string(b, args)
|
ret = p.string(b, args)
|
||||||
case llgoStringData:
|
case llgoStringData:
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ package syscall
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/c"
|
||||||
|
"github.com/goplus/llgo/c/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ForkLock is used to synchronize creation of new file descriptors
|
// ForkLock is used to synchronize creation of new file descriptors
|
||||||
@@ -102,8 +106,10 @@ type ProcAttr struct {
|
|||||||
Sys *SysProcAttr
|
Sys *SysProcAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO(xsw):
|
||||||
var zeroProcAttr ProcAttr
|
var zeroProcAttr ProcAttr
|
||||||
var zeroSysProcAttr SysProcAttr
|
var zeroSysProcAttr SysProcAttr
|
||||||
|
*/
|
||||||
|
|
||||||
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
||||||
/* TODO(xsw):
|
/* TODO(xsw):
|
||||||
@@ -237,47 +243,9 @@ var execveOpenBSD func(path *byte, argv **byte, envp **byte) error
|
|||||||
|
|
||||||
// Exec invokes the execve(2) system call.
|
// Exec invokes the execve(2) system call.
|
||||||
func Exec(argv0 string, argv []string, envv []string) (err error) {
|
func Exec(argv0 string, argv []string, envv []string) (err error) {
|
||||||
/* TODO(xsw):
|
ret := os.Execve(c.AllocaCStr(argv0), c.AllocaCStrs(argv, true), c.AllocaCStrs(envv, true))
|
||||||
argv0p, err := BytePtrFromString(argv0)
|
if ret == 0 {
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
argvp, err := SlicePtrFromStrings(argv)
|
return syscall.Errno(ret)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
envvp, err := SlicePtrFromStrings(envv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
runtime_BeforeExec()
|
|
||||||
|
|
||||||
rlim, rlimOK := origRlimitNofile.Load().(Rlimit)
|
|
||||||
if rlimOK && rlim.Cur != 0 {
|
|
||||||
Setrlimit(RLIMIT_NOFILE, &rlim)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err1 error
|
|
||||||
if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "aix" {
|
|
||||||
// RawSyscall should never be used on Solaris, illumos, or AIX.
|
|
||||||
err1 = execveLibc(
|
|
||||||
uintptr(unsafe.Pointer(argv0p)),
|
|
||||||
uintptr(unsafe.Pointer(&argvp[0])),
|
|
||||||
uintptr(unsafe.Pointer(&envvp[0])))
|
|
||||||
} else if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
|
||||||
// Similarly on Darwin.
|
|
||||||
err1 = execveDarwin(argv0p, &argvp[0], &envvp[0])
|
|
||||||
} else if runtime.GOOS == "openbsd" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
|
|
||||||
// Similarly on OpenBSD.
|
|
||||||
err1 = execveOpenBSD(argv0p, &argvp[0], &envvp[0])
|
|
||||||
} else {
|
|
||||||
_, _, err1 = RawSyscall(SYS_EXECVE,
|
|
||||||
uintptr(unsafe.Pointer(argv0p)),
|
|
||||||
uintptr(unsafe.Pointer(&argvp[0])),
|
|
||||||
uintptr(unsafe.Pointer(&envvp[0])))
|
|
||||||
}
|
|
||||||
runtime_AfterExec()
|
|
||||||
return err1
|
|
||||||
*/
|
|
||||||
panic("todo: syscall.Exec")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,13 @@ func (b Builder) Alloca(n Expr) (ret Expr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllocaU allocates uninitialized space for n*sizeof(elem) bytes.
|
||||||
|
func (b Builder) AllocaU(elem Type, n ...int64) (ret Expr) {
|
||||||
|
prog := b.Prog
|
||||||
|
size := SizeOf(prog, elem, n...)
|
||||||
|
return Expr{b.Alloca(size).impl, prog.Pointer(elem)}
|
||||||
|
}
|
||||||
|
|
||||||
// AllocaCStr allocates space for copy it from a Go string.
|
// AllocaCStr allocates space for copy it from a Go string.
|
||||||
func (b Builder) AllocaCStr(gostr Expr) (ret Expr) {
|
func (b Builder) AllocaCStr(gostr Expr) (ret Expr) {
|
||||||
if debugInstr {
|
if debugInstr {
|
||||||
@@ -155,6 +162,29 @@ func (b Builder) AllocaCStr(gostr Expr) (ret Expr) {
|
|||||||
return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr)
|
return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func allocaCStrs(strs []string, endWithNil bool) **int8
|
||||||
|
func (b Builder) AllocaCStrs(strs Expr, endWithNil bool) (cstrs Expr) {
|
||||||
|
if debugInstr {
|
||||||
|
log.Printf("AllocaCStrs %v, %v\n", strs.impl, endWithNil)
|
||||||
|
}
|
||||||
|
prog := b.Prog
|
||||||
|
n := b.SliceLen(strs)
|
||||||
|
n1 := n
|
||||||
|
if endWithNil {
|
||||||
|
n1 = b.BinOp(token.ADD, n, prog.Val(1))
|
||||||
|
}
|
||||||
|
tcstr := prog.CStr()
|
||||||
|
cstrs = b.ArrayAlloca(tcstr, n1)
|
||||||
|
b.Times(n, func(i Expr) {
|
||||||
|
s := b.Index(strs, i, nil)
|
||||||
|
b.Store(b.Advance(cstrs, i), b.AllocaCStr(s))
|
||||||
|
})
|
||||||
|
if endWithNil {
|
||||||
|
b.Store(b.Advance(cstrs, n), prog.Nil(tcstr))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
func (p Program) tyMalloc() *types.Signature {
|
func (p Program) tyMalloc() *types.Signature {
|
||||||
@@ -189,17 +219,15 @@ func (b Builder) free(ptr Expr) Expr {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
/*
|
|
||||||
// ArrayAlloca reserves space for an array of n elements of type telem.
|
// ArrayAlloca reserves space for an array of n elements of type telem.
|
||||||
func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) {
|
func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) {
|
||||||
if debugInstr {
|
if debugInstr {
|
||||||
log.Printf("ArrayAlloca %v, %v\n", telem.t, n.impl)
|
log.Printf("ArrayAlloca %v, %v\n", telem.raw.Type, n.impl)
|
||||||
}
|
}
|
||||||
ret.impl = llvm.CreateArrayAlloca(b.impl, telem.ll, n.impl)
|
ret.impl = llvm.CreateArrayAlloca(b.impl, telem.ll, n.impl)
|
||||||
ret.Type = b.Prog.Pointer(telem)
|
ret.Type = b.Prog.Pointer(telem)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// ArrayAlloc allocates zero initialized space for an array of n elements of type telem.
|
// ArrayAlloc allocates zero initialized space for an array of n elements of type telem.
|
||||||
func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) {
|
func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package ssa
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -248,6 +249,44 @@ func (b Builder) IfThen(cond Expr, then func()) {
|
|||||||
b.blk.last = blks[1].last
|
b.blk.last = blks[1].last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO(xsw):
|
||||||
|
// For emits a for-loop instruction.
|
||||||
|
func (b Builder) For(cond func() Expr, loop func()) {
|
||||||
|
blks := b.Func.MakeBlocks(3)
|
||||||
|
b.Jump(blks[0])
|
||||||
|
b.SetBlockEx(blks[0], AtEnd, false)
|
||||||
|
b.If(cond(), blks[1], blks[2])
|
||||||
|
b.SetBlockEx(blks[1], AtEnd, false)
|
||||||
|
loop()
|
||||||
|
b.Jump(blks[0])
|
||||||
|
b.SetBlockEx(blks[2], AtEnd, false)
|
||||||
|
b.blk.last = blks[2].last
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Times emits a times-loop instruction.
|
||||||
|
func (b Builder) Times(n Expr, loop func(i Expr)) {
|
||||||
|
at := b.blk
|
||||||
|
blks := b.Func.MakeBlocks(3)
|
||||||
|
b.Jump(blks[0])
|
||||||
|
b.SetBlockEx(blks[0], AtEnd, false)
|
||||||
|
typ := n.Type
|
||||||
|
phi := b.Phi(typ)
|
||||||
|
b.If(b.BinOp(token.LSS, phi.Expr, n), blks[1], blks[2])
|
||||||
|
b.SetBlockEx(blks[1], AtEnd, false)
|
||||||
|
loop(phi.Expr)
|
||||||
|
post := b.BinOp(token.ADD, phi.Expr, b.Prog.IntVal(1, typ))
|
||||||
|
b.Jump(blks[0])
|
||||||
|
b.SetBlockEx(blks[2], AtEnd, false)
|
||||||
|
b.blk.last = blks[2].last
|
||||||
|
phi.AddIncoming(b, []BasicBlock{at, blks[1]}, func(i int, blk BasicBlock) Expr {
|
||||||
|
if i == 0 {
|
||||||
|
return b.Prog.IntVal(0, typ)
|
||||||
|
}
|
||||||
|
return post
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
type caseStmt struct {
|
type caseStmt struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user