Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

474 рядки
12KB

  1. package template
  2. import (
  3. "errors"
  4. "fmt"
  5. "maps"
  6. "math"
  7. "strconv"
  8. "git.wtrh.nl/patterns/gopatterns/pkg/path"
  9. "git.wtrh.nl/patterns/gopatterns/pkg/point"
  10. "git.wtrh.nl/patterns/gopatterns/pkg/util"
  11. "gopkg.in/Knetic/govaluate.v3"
  12. )
  13. const maxRecursionDepth = 100
  14. var (
  15. // ErrPointNotFound is returned when a required point is not defined.
  16. ErrPointNotFound = errors.New("required point not found")
  17. // ErrRelativePointRecursion is returned when a points are relative to itself.
  18. ErrRelativePointRecursion = errors.New("point cannot be relative to itself")
  19. ErrInvalidArguments = errors.New("invalid arguments to call function")
  20. )
  21. // Points contains a map with points.
  22. type Points map[util.ID]Point
  23. // Point contains the template information for a point.
  24. type Point struct {
  25. Position Position `yaml:"position"`
  26. RelativeTo *util.ID `yaml:"relativeTo,omitempty"`
  27. Description string `yaml:"description"`
  28. Between *BetweenPoint `yaml:"between"`
  29. Extend *ExtendPoint `yaml:"extend"`
  30. Hide bool `yaml:"hide"`
  31. Polar *PolarPoint `yaml:"polar"`
  32. }
  33. func (t Template) templatePoint(panelName string, id util.ID) (Point, error) {
  34. if id.Panel() != "" {
  35. panelName = id.Panel()
  36. }
  37. panel, err := t.Panel(panelName)
  38. if !errors.Is(err, ErrPanelNotFound) {
  39. p, ok := panel.Points[id.Deref()]
  40. if ok {
  41. return p, nil
  42. }
  43. }
  44. p, ok := t.Points[id.Deref()]
  45. if !ok {
  46. return Point{}, ErrPointNotFound
  47. }
  48. return p, nil
  49. }
  50. var ErrInvalidPointID = errors.New("type cannot be converted to a PointID")
  51. func (t Template) functions(req request) map[string]govaluate.ExpressionFunction {
  52. functions := t.evaluationFunctions()
  53. maps.Copy(functions, map[string]govaluate.ExpressionFunction{
  54. "DistanceBetween": func(args ...interface{}) (interface{}, error) {
  55. if len(args) != 2 {
  56. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  57. ErrInvalidArguments)
  58. }
  59. points, err := t.getOrCreatePointsFromArgs(req, args...)
  60. if err != nil {
  61. return nil, err
  62. }
  63. return points[0].Position().Distance(points[1].Position()), nil
  64. },
  65. "AngleBetween": func(args ...interface{}) (interface{}, error) {
  66. if len(args) != 2 {
  67. return nil, fmt.Errorf("function AngleBetween() requires 2 arguments: %w",
  68. ErrInvalidArguments)
  69. }
  70. points, err := t.getOrCreatePointsFromArgs(req, args...)
  71. if err != nil {
  72. return nil, err
  73. }
  74. return points[0].Vector().AngleBetween(points[1].Vector()), nil
  75. },
  76. "YDistanceBetween": func(args ...interface{}) (interface{}, error) {
  77. if len(args) != 2 {
  78. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  79. ErrInvalidArguments)
  80. }
  81. points, err := t.getOrCreatePointsFromArgs(req, args...)
  82. if err != nil {
  83. return nil, err
  84. }
  85. return points[1].Vector().Y - points[0].Vector().Y, nil
  86. },
  87. "XDistanceBetween": func(args ...interface{}) (interface{}, error) {
  88. if len(args) != 2 {
  89. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  90. ErrInvalidArguments)
  91. }
  92. points, err := t.getOrCreatePointsFromArgs(req, args...)
  93. if err != nil {
  94. return nil, err
  95. }
  96. return points[1].Vector().X - points[0].Vector().X, nil
  97. },
  98. "LineLength": func(args ...interface{}) (interface{}, error) {
  99. if len(args) != 1 {
  100. return nil, fmt.Errorf("function LineLength() requires 2 arguments: %w", ErrInvalidArguments)
  101. }
  102. line, err := t.getOrCreateLinesFromArgs(req, args...)
  103. if err != nil {
  104. return nil, err
  105. }
  106. return line[0].Length()
  107. },
  108. })
  109. return functions
  110. }
  111. func (t Template) getOrCreatePointsFromArgs(req request, args ...interface{}) ([]point.Point, error) {
  112. points := make([]point.Point, 0, len(args))
  113. for i, arg := range args {
  114. id, err := toID(arg)
  115. if err != nil {
  116. return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err)
  117. }
  118. newPoint, err := t.getOrCreatePoint(id, req, 0)
  119. if err != nil {
  120. return nil, fmt.Errorf("get or create point %q: %w", id, err)
  121. }
  122. points = append(points, newPoint)
  123. }
  124. return points, nil
  125. }
  126. func (t Template) getOrCreateLinesFromArgs(req request, args ...interface{}) ([]path.Path, error) {
  127. points := make([]path.Path, 0, len(args))
  128. for i, arg := range args {
  129. id, err := toID(arg)
  130. if err != nil {
  131. return nil, fmt.Errorf("parsing args[%d] to ID: %w", i, err)
  132. }
  133. newPoint, err := t.getOrCreateLine(id, req, 0)
  134. if err != nil {
  135. return nil, fmt.Errorf("get or create line %q: %w", id, err)
  136. }
  137. points = append(points, newPoint)
  138. }
  139. return points, nil
  140. }
  141. func toID(arg interface{}) (util.ID, error) {
  142. v1, ok := arg.(string)
  143. if !ok {
  144. f, ok := arg.(float64)
  145. if !ok {
  146. return "", fmt.Errorf("parsing %v as PointID: %w", arg, ErrInvalidPointID)
  147. }
  148. v1 = strconv.FormatFloat(f, 'f', -1, 64)
  149. }
  150. return util.ID(v1), nil
  151. }
  152. // BetweenPoint contains the template information for a point in between two other points.
  153. type BetweenPoint struct {
  154. From util.ID `yaml:"from"`
  155. To util.ID `yaml:"to"`
  156. Offset *Value `yaml:"offset"`
  157. }
  158. func (t Template) evaluationFunctions() map[string]govaluate.ExpressionFunction {
  159. return map[string]govaluate.ExpressionFunction{
  160. "acos": func(args ...interface{}) (interface{}, error) {
  161. if len(args) != 1 {
  162. return nil, fmt.Errorf("function acos() requires 1 argument: %w", ErrInvalidArguments)
  163. }
  164. x, ok := args[0].(float64)
  165. if !ok {
  166. return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
  167. }
  168. return math.Acos(x), nil
  169. },
  170. "asin": func(args ...interface{}) (interface{}, error) {
  171. if len(args) != 1 {
  172. return nil, fmt.Errorf("function asin() requires 1 argument: %w", ErrInvalidArguments)
  173. }
  174. x, ok := args[0].(float64)
  175. if !ok {
  176. return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
  177. }
  178. return math.Asin(x), nil
  179. },
  180. "abs": func(args ...interface{}) (interface{}, error) {
  181. if len(args) != 1 {
  182. return nil, fmt.Errorf("function abs() requires 1 argument: %w", ErrInvalidArguments)
  183. }
  184. x, ok := args[0].(float64)
  185. if !ok {
  186. return nil, fmt.Errorf("evaluate abs(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
  187. }
  188. return math.Abs(x), nil
  189. },
  190. "atan2": func(args ...interface{}) (interface{}, error) {
  191. if len(args) != 2 {
  192. return nil, fmt.Errorf("function atan2() requires 2 arguments: %w", ErrInvalidArguments)
  193. }
  194. x, ok := args[0].(float64)
  195. if !ok {
  196. return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
  197. }
  198. y, ok := args[1].(float64)
  199. if !ok {
  200. return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0], ErrInvalidArguments)
  201. }
  202. return math.Atan2(x, y), nil
  203. },
  204. }
  205. }
  206. func (t Template) createPoint(id util.ID, req request, depth int) (p point.Point, err error) {
  207. templatePoint, err := t.templatePoint(req.name, id)
  208. if err != nil {
  209. return nil, fmt.Errorf("creating point: %w", err)
  210. }
  211. var newPoint point.Point
  212. switch {
  213. case templatePoint.RelativeTo != nil && templatePoint.Polar != nil:
  214. newPoint, err = t.createPolar(id, req, depth)
  215. if err != nil {
  216. return nil, err
  217. }
  218. case templatePoint.RelativeTo != nil:
  219. newPoint, err = t.createRelative(id, req, depth)
  220. if err != nil {
  221. return nil, err
  222. }
  223. case templatePoint.Between != nil:
  224. newPoint, err = t.createBetween(id, req, depth)
  225. if err != nil {
  226. return nil, err
  227. }
  228. case templatePoint.Extend != nil:
  229. newPoint, err = t.createExtend(id, req, depth)
  230. if err != nil {
  231. return nil, err
  232. }
  233. default:
  234. x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req))
  235. if err != nil {
  236. return nil, err
  237. }
  238. newPoint = point.NewAbsolutePoint(x, y, r, id)
  239. }
  240. if templatePoint.Hide {
  241. newPoint.SetHide()
  242. }
  243. return newPoint, nil
  244. }
  245. func (t Template) createRelative(id util.ID, req request, depth int) (*point.RelativePoint, error) {
  246. templatePoint, err := t.templatePoint(req.name, id)
  247. if err != nil {
  248. return nil, err
  249. }
  250. relativePointID := *templatePoint.RelativeTo
  251. if relativePointID == id || depth > maxRecursionDepth {
  252. return nil, ErrRelativePointRecursion
  253. }
  254. relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth)
  255. if err != nil {
  256. return nil, err
  257. }
  258. x, y, r, err := templatePoint.Position.evaluate(req.dimensions.Parameters(), t.functions(req))
  259. if err != nil {
  260. return nil, err
  261. }
  262. return point.NewRelativePoint(relativePoint).
  263. WithXOffset(x).WithYOffset(y).WithRotationOffset(r).
  264. MarkWith(id), nil
  265. }
  266. //nolint:ireturn,dupl
  267. func (t Template) createBetween(id util.ID, req request, depth int) (point.Point, error) {
  268. newPoint, err := t.templatePoint(req.name, id)
  269. if err != nil {
  270. return nil, err
  271. }
  272. if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth {
  273. return nil, ErrRelativePointRecursion
  274. }
  275. fromPoint, err := t.getOrCreatePoint(newPoint.Between.From, req, depth)
  276. if err != nil {
  277. return nil, err
  278. }
  279. toPoint, err := t.getOrCreatePoint(newPoint.Between.To, req, depth)
  280. if err != nil {
  281. return nil, err
  282. }
  283. params := req.dimensions.Parameters()
  284. offset, err := newPoint.Between.Offset.Evaluate(params, t.functions(req))
  285. if err != nil {
  286. return nil, err
  287. }
  288. return point.NewBetweenPoint(fromPoint, toPoint, offset, id), nil
  289. }
  290. func (t Template) getOrCreatePoints(ids []util.ID, req request, depth int) ([]point.Point, error) {
  291. points := make([]point.Point, 0, len(ids))
  292. for _, id := range ids {
  293. createPoint, err := t.getOrCreatePoint(id, req, depth)
  294. if err != nil {
  295. return nil, err
  296. }
  297. points = append(points, createPoint)
  298. }
  299. return points, nil
  300. }
  301. //nolint:ireturn
  302. func (t Template) getOrCreatePoint(id util.ID, req request, depth int) (point.Point, error) {
  303. p, ok := req.points[id]
  304. if ok {
  305. return p, nil
  306. }
  307. newPoint, err := t.createPoint(id, req, depth+1)
  308. if err != nil {
  309. return nil, fmt.Errorf("creating point %q: %w", id, err)
  310. }
  311. req.points[id] = newPoint
  312. return newPoint, nil
  313. }
  314. //nolint:ireturn,dupl
  315. func (t Template) createExtend(id util.ID, req request, depth int) (point.Point, error) {
  316. newPoint, err := t.templatePoint(req.name, id)
  317. if err != nil {
  318. return nil, err
  319. }
  320. if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth {
  321. return nil, ErrRelativePointRecursion
  322. }
  323. fromPoint, err := t.getOrCreatePoint(newPoint.Extend.From, req, depth)
  324. if err != nil {
  325. return nil, err
  326. }
  327. toPoint, err := t.getOrCreatePoint(newPoint.Extend.To, req, depth)
  328. if err != nil {
  329. return nil, err
  330. }
  331. offset, err := newPoint.Extend.Offset.Evaluate(req.dimensions.Parameters(), t.functions(req))
  332. if err != nil {
  333. return nil, err
  334. }
  335. return point.NewExtendPoint(fromPoint, toPoint, offset, id), nil
  336. }
  337. //nolint:ireturn
  338. func (t Template) createPolar(id util.ID, req request, depth int) (point.Point, error) {
  339. templatePoint, err := t.templatePoint(req.name, id)
  340. if err != nil {
  341. return nil, err
  342. }
  343. relativePointID := *templatePoint.RelativeTo
  344. if relativePointID == id || depth > maxRecursionDepth {
  345. return nil, ErrRelativePointRecursion
  346. }
  347. relativePoint, err := t.getOrCreatePoint(relativePointID, req, depth)
  348. if err != nil {
  349. return nil, err
  350. }
  351. x, y, r, err := templatePoint.Polar.evaluate(req.dimensions.Parameters(), t.functions(req))
  352. if err != nil {
  353. return nil, err
  354. }
  355. return point.NewRelativePoint(relativePoint).
  356. WithXOffset(x).WithYOffset(y).MarkWith(id).WithRotationOffset(r), nil
  357. }
  358. // ExtendPoint describes how to draw a new point that extends in line with two points.
  359. type ExtendPoint struct {
  360. From util.ID `yaml:"from"`
  361. To util.ID `yaml:"to"`
  362. Offset *Value `yaml:"offset"`
  363. }
  364. // PolarPoint describes how to draw a new point with a direction and a distance from the current
  365. // position.
  366. type PolarPoint struct {
  367. Length *Value `yaml:"length"`
  368. Rotation *Value `yaml:"rotation"`
  369. }
  370. func (p PolarPoint) evaluate(
  371. params govaluate.MapParameters,
  372. funcs map[string]govaluate.ExpressionFunction,
  373. ) (x, y, r float64, err error) {
  374. rotation, err := p.Rotation.Evaluate(params, funcs)
  375. if err != nil {
  376. return 0, 0, 0, err
  377. }
  378. length, err := p.Length.Evaluate(params, funcs)
  379. if err != nil {
  380. return 0, 0, 0, err
  381. }
  382. return math.Cos(rotation) * length, math.Sin(rotation) * length, rotation, nil
  383. }