To solve this optimization problem, we first need to convert the natural language description into a symbolic representation. Let's define the variables as follows:

- $x_1$ represents the number of soft cake-pops.
- $x_2$ represents the number of smooth cake-pops.
- $x_3$ represents the number of crunchy cake-pops.

The objective function, which is to maximize profit, can be represented algebraically using these variables. The profit per soft cake-pop is $4, per smooth cake-pop is $6, and per crunchy cake-pop is $5. Therefore, the objective function is:

\[ \text{Maximize:} \quad 4x_1 + 6x_2 + 5x_3 \]

The constraints based on the availability of batter and milk can be represented as follows:

- The total amount of batter used (20 grams per soft cake-pop, 15 grams per smooth cake-pop, and 12 grams per crunchy cake-pop) cannot exceed 1000 grams. This gives us:
\[ 20x_1 + 15x_2 + 12x_3 \leq 1000 \]
- The total amount of milk used (10 grams per soft cake-pop, 15 grams per smooth cake-pop, and 18 grams per crunchy cake-pop) cannot exceed 800 grams. This gives us:
\[ 10x_1 + 15x_2 + 18x_3 \leq 800 \]
- Additionally, the number of each type of cake-pop must be non-negative since we cannot produce a negative number of items:
\[ x_1 \geq 0, \quad x_2 \geq 0, \quad x_3 \geq 0 \]

In symbolic notation with natural language objects, this can be summarized as:

```json
{
    'sym_variables': [('x1', 'soft cake-pops'), ('x2', 'smooth cake-pops'), ('x3', 'crunchy cake-pops')],
    'objective_function': '4*x1 + 6*x2 + 5*x3',
    'constraints': [
        '20*x1 + 15*x2 + 12*x3 <= 1000',
        '10*x1 + 15*x2 + 18*x3 <= 800',
        'x1 >= 0', 'x2 >= 0', 'x3 >= 0'
    ]
}
```

To solve this linear programming problem, we will use the Gurobi solver in Python. Here's how you can implement it:

```python
from gurobipy import *

# Create a new model
m = Model("cake_pops")

# Define variables
x1 = m.addVar(lb=0, vtype=GRB.CONTINUOUS, name="soft_cake_pops")
x2 = m.addVar(lb=0, vtype=GRB.CONTINUOUS, name="smooth_cake_pops")
x3 = m.addVar(lb=0, vtype=GRB.CONTINUOUS, name="crunchy_cake_pops")

# Define the objective function
m.setObjective(4*x1 + 6*x2 + 5*x3, GRB.MAXIMIZE)

# Add constraints
m.addConstr(20*x1 + 15*x2 + 12*x3 <= 1000, "batter_limit")
m.addConstr(10*x1 + 15*x2 + 18*x3 <= 800, "milk_limit")

# Optimize the model
m.optimize()

# Print the solution
for v in m.getVars():
    print(f"{v.varName}: {v.x}")
print(f"Objective: {m.objVal}")
```