github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/scripts/engine.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package scripts 20 21 import ( 22 "fmt" 23 "os" 24 "strconv" 25 26 "github.com/hashicorp/go-multierror" 27 "github.com/pkg/errors" 28 "go.uber.org/atomic" 29 30 . "github.com/e154/smart-home/common" 31 "github.com/e154/smart-home/common/apperr" 32 m "github.com/e154/smart-home/models" 33 ) 34 35 // IScript ... 36 type IScript interface { 37 Init() error 38 Do() (string, error) 39 AssertFunction(string, ...interface{}) (string, error) 40 Compile() error 41 PushStruct(string, interface{}) 42 PushFunction(string, interface{}) 43 EvalString(string) (string, error) 44 CreateProgram(name, source string) (err error) 45 RunProgram(name string) (result string, err error) 46 } 47 48 // Engine ... 49 type Engine struct { 50 model *m.Script 51 script IScript 52 buf []string 53 IsRun atomic.Bool 54 functions *Pull 55 structures *Pull 56 } 57 58 // NewEngine ... 59 func NewEngine(s *m.Script, functions, structures *Pull) (engine *Engine, err error) { 60 61 if s == nil { 62 s = &m.Script{ 63 Lang: ScriptLangJavascript, 64 } 65 } 66 67 if functions == nil { 68 functions = NewPull() 69 } 70 71 if structures == nil { 72 structures = NewPull() 73 } 74 75 engine = &Engine{ 76 model: s, 77 buf: make([]string, 0), 78 functions: functions, 79 structures: structures, 80 } 81 82 if s.Lang == "" { 83 err = errors.Wrap(apperr.ErrNotFound, fmt.Sprintf("language not specified")) 84 return 85 } 86 87 switch s.Lang { 88 case ScriptLangTs, ScriptLangCoffee, ScriptLangJavascript: 89 engine.script = NewJavascript(engine) 90 default: 91 err = errors.Wrap(apperr.ErrNotFound, fmt.Sprintf("i don't know this language: \"%s\"", s.Lang)) 92 return 93 } 94 95 err = engine.script.Init() 96 97 return 98 } 99 100 // Compile ... 101 func (s *Engine) Compile() (err error) { 102 if err = s.script.Compile(); err != nil { 103 err = errors.Wrapf(err, "script id: %d", s.model.Id) 104 } 105 return 106 } 107 108 // PushStruct ... 109 func (s *Engine) PushStruct(name string, i interface{}) { 110 s.script.PushStruct(name, i) 111 } 112 113 // PushFunction ... 114 func (s *Engine) PushFunction(name string, i interface{}) { 115 s.script.PushFunction(name, i) 116 } 117 118 // EvalString ... 119 func (s *Engine) EvalString(str ...string) (result string, errs error) { 120 var err error 121 if len(str) == 0 { 122 if result, err = s.script.Do(); err != nil { 123 errs = multierror.Append(err, errs) 124 } 125 return 126 } 127 for _, st := range str { 128 if result, err = s.script.EvalString(st); err != nil { 129 errs = multierror.Append(err, errs) 130 } 131 } 132 return 133 } 134 135 // EvalScript ... 136 func (s *Engine) EvalScript(script *m.Script) (result string, err error) { 137 programName := strconv.Itoa(int(script.Id)) 138 if result, err = s.script.RunProgram(programName); err == nil { 139 return 140 } 141 if errors.Is(err, apperr.ErrNotFound) { 142 if err = s.script.CreateProgram(programName, script.Compiled); err != nil { 143 err = errors.Wrap(apperr.ErrInternal, err.Error()) 144 return 145 } 146 result, err = s.script.RunProgram(programName) 147 } 148 return 149 } 150 151 // DoFull ... 152 func (s *Engine) DoFull() (res string, err error) { 153 if !s.IsRun.CompareAndSwap(false, true) { 154 return 155 } 156 defer s.IsRun.Store(false) 157 158 var result string 159 result, err = s.script.Do() 160 if err != nil { 161 err = errors.Wrap(err, "do full") 162 return 163 } 164 for _, r := range s.buf { 165 res += r + "\n" 166 } 167 168 res += result + "\n" 169 170 // reset buffer 171 s.buf = []string{} 172 173 return 174 } 175 176 // Do ... 177 func (s *Engine) Do() (string, error) { 178 return s.script.Do() 179 } 180 181 // AssertFunction ... 182 func (s *Engine) AssertFunction(f string, arg ...interface{}) (result string, err error) { 183 if !s.IsRun.CompareAndSwap(false, true) { 184 return 185 } 186 defer s.IsRun.Store(false) 187 188 result, err = s.script.AssertFunction(f, arg...) 189 if err != nil { 190 if s.ScriptId() != 0 { 191 err = errors.Wrapf(err, "script id:%d ", s.ScriptId()) 192 return 193 } 194 return 195 } 196 197 // reset buffer 198 s.buf = []string{} 199 200 return 201 } 202 203 // Print ... 204 func (s *Engine) Print(v ...interface{}) { 205 fmt.Println(v...) 206 s.buf = append(s.buf, fmt.Sprint(v...)) 207 } 208 209 // Get ... 210 func (s *Engine) Get() IScript { 211 return s.script 212 } 213 214 // File ... 215 func (s *Engine) File(path string) ([]byte, error) { 216 b, err := os.ReadFile(path) 217 if err != nil { 218 return nil, err 219 } 220 return b, nil 221 } 222 223 func (s *Engine) ScriptId() int64 { 224 return s.model.Id 225 }