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  }